软硬件资源
-
STM32F103C8T6
-
直流减速电机+霍尔编码器
-
HAL 库
这个本来挺简单的,但是因为我看错教程了(看的野火的电机教程),导致多花好几天时间,一度怀疑是编码器的脉冲信号出了问题,最后没想到还是芯片型号的问题。野火用的是 STM32F4 系列,其编码器代码不能直接用到 STM32F103C8T6 上,会出现编码器不计数、也无法读取计数方向的问题。
用编码器模式测速需要两个定时器:一个定时器设置为编码器模式,一个定时器周期性的计算转速。
我的项目有两个电机,因此需要三个定时器。完整代码如下,代码含义见注释。
编码器配置
encoder. h
#ifndef __ENCODER_H
#define __ENCODER_H
#include "stm32f1xx.h"
/**************引脚定义**************/
#define Encoder_E1A_PIN GPIO_PIN_0 //电机 1 的 A 相编码值
#define Encoder_E1B_PIN GPIO_PIN_1 //电机 1 的 B 相编码值
#define Encoder_E1A_PORT GPIOA
#define Encoder_E1B_PORT GPIOA
#define Encoder_E1A_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define Encoder_E1B_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define Encoder_E2A_PIN GPIO_PIN_6 //电机 2 的A相编码值
#define Encoder_E2B_PIN GPIO_PIN_7 //电机 2 的B相编码值
#define Encoder_E2A_PORT GPIOA
#define Encoder_E2B_PORT GPIOA
#define Encoder_E2A_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define Encoder_E2B_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
/*************电机实际旋转方向*******/
extern __IO int8_t Motor_A_Direction;
extern __IO int8_t Motor_B_Direction;
#define MOTOR_FORWARD 0 //电机正转
#define MOTOR_REVERSE 1 //电机反转
/*************电机实际转速**********/
extern __IO float Motor_A_Speed;
extern __IO float Motor_B_Speed;
/********编码器对应定时器的计数值*****/
//当前时刻定时器计数值
extern __IO int32_t Motor_A_Count;
extern __IO int32_t Motor_B_Count;
/*************编码器定义************/
//定时器选择
#define ENCODER_A_TIM TIM2
#define ENCODER_B_TIM TIM3
#define ENCODER_A_TIMx 2
#define ENCODER_B_TIMx 3
#define ENCODER_A_TIM_CLK_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE()
#define ENCODER_B_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
//定时器溢出值
#define ENCODER_A_TIM_PERIOD 65535
#define ENCODER_B_TIM_PERIOD 65535
//定时器预分频值
#define ENCODER_A_TIM_PRESCALER 0
#define ENCODER_B_TIM_PRESCALER 0
//编码器倍频数
#define ENCODER_MODE TIM_ENCODERMODE_TI12//4倍频
//编码器输入捕获通道极性选择
#define ENCODER_A_IC1_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_A_IC2_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_B_IC1_POLARITY TIM_ICPOLARITY_RISING
#define ENCODER_B_IC2_POLARITY TIM_ICPOLARITY_RISING
//编码器物理分辨率
#define ENCODER_RESOLUTION 13.0
//直流减速电机减速比
#define REDUCTION_RATIO 30.0
//编码器时基单元
extern TIM_HandleTypeDef Encoder_A_TIM_Base;//电机1编码器
extern TIM_HandleTypeDef Encoder_B_TIM_Base;//电机2编码器
//编码器采样周期
#define Encoder_Period 5
//编码器采样频率
#define Encoder_Frequency 200.0
//编码器倍频数
#define EncoderMultiples 4.0
//行进速度
extern float Velocity_Motor_A;
extern float Velocity_Motor_B;
//圆周率
#define PI 3.14159265
//轮子直径(mm)
#define Diameter 67.0
//编码器初始化函数
void Encoder_Init(void);
//读取单位时间内的编码器的计数值
int Read_Encoder(uint8_t TIMX);
#endif
encoder. c
#include "encoder.h"
#include "stm32f1xx_hal_tim.h"
TIM_HandleTypeDef Encoder_A_TIM_Base;//电机1编码器
TIM_HandleTypeDef Encoder_B_TIM_Base;//电机2编码器
//电机实际旋转方向
__IO int8_t Motor_A_Direction = 0;
__IO int8_t Motor_B_Direction = 0;
//当前时刻定时器计数值
__IO int32_t Motor_A_Count = 0;
__IO int32_t Motor_B_Count = 0;
//电机实际转速
__IO float Motor_A_Speed = 0.0f;
__IO float Motor_B_Speed = 0.0f;
//行进速度
float Velocity_Motor_A = 0;
float Velocity_Motor_B = 0;
/**
* @brief 编码器接口引脚初始化
* @param 无
* @retval 无
*/
void Encoder_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
//GPIO时钟
Encoder_E1A_CLK_ENABLE();
Encoder_E1B_CLK_ENABLE();
Encoder_E2A_CLK_ENABLE();
Encoder_E2B_CLK_ENABLE();
/*********************初始化编码器输入引脚**********/
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;//输入
GPIO_InitStructure.Pin = Encoder_E1A_PIN;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(Encoder_E1A_PORT,&GPIO_InitStructure);
GPIO_InitStructure.Pin = Encoder_E1B_PIN;
HAL_GPIO_Init(Encoder_E1B_PORT,&GPIO_InitStructure);
GPIO_InitStructure.Pin = Encoder_E2A_PIN;
HAL_GPIO_Init(Encoder_E2A_PORT,&GPIO_InitStructure);
GPIO_InitStructure.Pin = Encoder_E2B_PIN;
HAL_GPIO_Init(Encoder_E2B_PORT,&GPIO_InitStructure);
}
/**
* @brief 定时器初始化
*
*/
void TIM_Encoder_Init(void){
TIM_Encoder_InitTypeDef TIM_Encoder_InitStructure = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
//编码器时钟
ENCODER_A_TIM_CLK_ENABLE();
ENCODER_B_TIM_CLK_ENABLE();
/********配置编码器对应定时器*******/
Encoder_A_TIM_Base.Instance = ENCODER_A_TIM;
Encoder_A_TIM_Base.Init.Prescaler = ENCODER_A_TIM_PRESCALER;
Encoder_A_TIM_Base.Init.CounterMode = TIM_COUNTERMODE_UP;
Encoder_A_TIM_Base.Init.Period = ENCODER_A_TIM_PERIOD;
Encoder_A_TIM_Base.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
Encoder_A_TIM_Base.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
Encoder_B_TIM_Base.Instance = ENCODER_B_TIM;
Encoder_B_TIM_Base.Init.Prescaler = ENCODER_B_TIM_PRESCALER;
Encoder_B_TIM_Base.Init.CounterMode = TIM_COUNTERMODE_UP;
Encoder_B_TIM_Base.Init.Period = ENCODER_B_TIM_PERIOD;
Encoder_B_TIM_Base.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
Encoder_B_TIM_Base.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
/*********************配置编码器接口***************/
//编码器倍频数
TIM_Encoder_InitStructure.EncoderMode = ENCODER_MODE;
//电机A对应编码器通道1(A相)设置
TIM_Encoder_InitStructure.IC1Polarity = ENCODER_A_IC1_POLARITY;
TIM_Encoder_InitStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
TIM_Encoder_InitStructure.IC1Prescaler = TIM_ICPSC_DIV1;
TIM_Encoder_InitStructure.IC1Filter = 10;
//电机A对应编码器通道2(B相)设置
TIM_Encoder_InitStructure.IC2Polarity = ENCODER_A_IC2_POLARITY;
TIM_Encoder_InitStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
TIM_Encoder_InitStructure.IC2Prescaler = TIM_ICPSC_DIV1;
TIM_Encoder_InitStructure.IC2Filter = 10;
//初始化电机A对应编码器接口
HAL_TIM_Encoder_Init(&Encoder_A_TIM_Base,&TIM_Encoder_InitStructure);
//电机B对应编码器通道1(A相)设置
TIM_Encoder_InitStructure.IC1Polarity = ENCODER_B_IC1_POLARITY;
//电机B对应编码器通道2(B相)设置
TIM_Encoder_InitStructure.IC2Polarity = ENCODER_B_IC2_POLARITY;
//初始化电机B对应编码器接口
HAL_TIM_Encoder_Init(&Encoder_B_TIM_Base,&TIM_Encoder_InitStructure);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&Encoder_A_TIM_Base,&sMasterConfig);
HAL_TIMEx_MasterConfigSynchronization(&Encoder_B_TIM_Base,&sMasterConfig);
/********************使能编码器接口****************/
HAL_TIM_Encoder_Start(&Encoder_A_TIM_Base,TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&Encoder_B_TIM_Base,TIM_CHANNEL_ALL);
}
/**
* @brief 初始化编码器
*
*/
void Encoder_Init(void){
Encoder_GPIO_Init();
TIM_Encoder_Init();
}
/**
* @brief 读取单位时间内的编码器计数值
*
* @param 编码器对应到的定时器
* @return 编码器计数值
*/
int Read_Encoder(uint8_t TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
//电机A对应编码器
case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break;
//电机B对应编码器
case 3: Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
计算转速
计算转速的定时器我用的是 systick 的回调函数,这个函数会在 systick(滴答定时器)每次计数时调用一次。
/**
* @brief systick 回调函数
*
*/
void HAL_SYSTICK_Callback(void){
static uint16_t i = 0;
i++;
//周期获取电机旋转方向和速度
if(i == Encoder_Period){
//电机旋转方向
Motor_A_Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&Encoder_A_TIM_Base);
Motor_B_Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&Encoder_B_TIM_Base);
//当前时刻定时器计数值
Motor_A_Count = Read_Encoder(ENCODER_A_TIMx);
Motor_B_Count = Read_Encoder(ENCODER_B_TIMx);
//计算转速:M法 单位时间计数值 / 倍频数 / 减速比 / 编码器分辨率(将单位转换为 /s)
Motor_A_Speed = Motor_A_Count * Encoder_Frequency / EncoderMultiples / REDUCTION_RATIO / ENCODER_RESOLUTION;
Motor_B_Speed = Motor_B_Count * Encoder_Frequency / EncoderMultiples / REDUCTION_RATIO / ENCODER_RESOLUTION;
//换算为行进速度:转速*轮子周长(mm/s)
Velocity_Motor_A = Motor_A_Speed * PI * Diameter;
Velocity_Motor_B = Motor_B_Speed * PI * Diameter;
i = 0;
}
}