编码器
编码器,是一种用来测量机械旋转或位移的传感器。这种传感器能够测量机械部件在旋转或直线运动时的位移位置或速度等信息,并将其转换成一系列电信号。
1.分类
编码器可以根据不同的方式分出很多种类型。例如根据检测原理,可分为光学式、磁式、感应式和电容式。根据内部机械结构的运动方式,可分为线性编码器和旋转编码器。根据其刻度实现方法及信号输出形式,又可分为增量式、绝对式以及混合式三种。
1.1增量式编码器
增量式旋转编码器是将设备运动时的位移信息变成连续的脉冲信号,脉冲个数表示位移量的大小。只有当设备运动的时候增量式编码器才会输出信号。编码器一般会把这些信号分为通道 A和通道 B 两组输出,并且这两组信号间有 90° 的相位差。同时采集这两组信号就可以知道设备的运动和方向。除了通道 A、通道 B 以外,很多增量式编码器还会设置一个额外的通道 Z 输出信号,用来表示编码器特定的参考位置,传感器转一圈 Z 轴信号才会输出一个脉冲。增量式编码器只输出设备的位置变化和运动方向,不会输出设备的绝对位置。
1.2绝对式编码器
绝对式旋转编码器是将设备运动时的位移信息通过二进制编码的方式变成数字量直接输出。这种编码器与增量式编码器的区别主要在内部的码盘。绝对式编码器的码盘利用若干透光和不透光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的,读取这些二进制码就能知道设备的绝对位置,所以叫它绝对式编码器。
2.编码器使用
2.1增量式编码器倍频技术
图中的方波信号如果只看其中一个通道的上升沿,那计数频率就等于这个通道信号的频率。如果在通道 A 的上升沿和下降沿都进行计数,计数频率就是通道 A 的两倍,即 2 倍频。如果同时对两个通道的上升沿和下降沿都计数,那计数频率就变成了原始信号的 4 倍,即 4 倍频。
假设有个增量式编码器它的分辨率是 600PPR,能分辨的最小角度是 0.6°,对它进行 4 倍频之后就相当于把分辨率提高到了 600*4=2400PPR,此时编码器能够分辨的最小角度为 0.15°。
“在 TI1 和 TI2 处均计数”。这种采样方式可以把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考,相当于原来仅在一个通道处计数的 2 倍,所以这种就能实现对原始信号的 4 倍频。
2.2 编码器测速
常用的编码器测速方法一般有三种:M 法、T 法和 M/T 法。
3.编程示例
/* 定时器选择 */
#define ENCODER_TIM TIM3
#define ENCODER_TIM_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
/* 定时器溢出值 */
#define ENCODER_TIM_PERIOD 65535
/* 定时器预分频值 */
#define ENCODER_TIM_PRESCALER 0
/* 定时器中断 */
#define ENCODER_TIM_IRQn TIM3_IRQn
#define ENCODER_TIM_IRQHandler TIM3_IRQHandler
/* 编码器接口引脚 */
#define ENCODER_TIM_CH1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define ENCODER_TIM_CH1_GPIO_PORT GPIOC
#define ENCODER_TIM_CH1_PIN GPIO_PIN_6
#define ENCODER_TIM_CH1_GPIO_AF GPIO_AF2_TIM3
#define ENCODER_TIM_CH2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define ENCODER_TIM_CH2_GPIO_PORT GPIOC
#define ENCODER_TIM_CH2_PIN GPIO_PIN_7
#define ENCODER_TIM_CH2_GPIO_AF GPIO_AF2_TIM3
/* 编码器接口倍频数 */
#define ENCODER_MODE TIM_ENCODERMODE_TI12
/* 编码器接口输入捕获通道相位设置 */
#define ENCODER_IC1_POLARITY TIM_ICPOLARITY_FALLING
#define ENCODER_IC2_POLARITY TIM_ICPOLARITY_RISING
/* 编码器物理分辨率 */
#define ENCODER_RESOLUTION 1000
/* 经过倍频之后的总分辨率 */
#if ((ENCODER_MODE == TIM_ENCODERMODE_TI1) || (ENCODER_MODE == TIM_ENCODERMODE_TI2))
#define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * 2) /* 2倍频后的总分辨率 */
#else
#define ENCODER_TOTAL_RESOLUTION (ENCODER_RESOLUTION * 4) /* 4倍频后的总分辨率 */
#endif
/* 定时器溢出次数 */
__IO int16_t Encoder_Overflow_Count = 0;
TIM_HandleTypeDef TIM_EncoderHandle;
/**
* @brief 编码器接口引脚初始化
* @param 无
* @retval 无
*/
static void Encoder_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 定时器通道引脚端口时钟使能 */
ENCODER_TIM_CH1_GPIO_CLK_ENABLE();
ENCODER_TIM_CH2_GPIO_CLK_ENABLE();
/* 设置输入类型 */
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
/* 设置上拉 */
GPIO_InitStruct.Pull = GPIO_PULLUP;
/* 设置引脚速率 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/* 选择要控制的GPIO引脚 */
GPIO_InitStruct.Pin = ENCODER_TIM_CH1_PIN;
/* 设置复用 */
GPIO_InitStruct.Alternate = ENCODER_TIM_CH1_GPIO_AF;
/* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
HAL_GPIO_Init(ENCODER_TIM_CH1_GPIO_PORT, &GPIO_InitStruct);
/* 选择要控制的GPIO引脚 */
GPIO_InitStruct.Pin = ENCODER_TIM_CH2_PIN;
/* 设置复用 */
GPIO_InitStruct.Alternate = ENCODER_TIM_CH2_GPIO_AF;
/* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
HAL_GPIO_Init(ENCODER_TIM_CH2_GPIO_PORT, &GPIO_InitStruct);
}
/**
* @brief 配置TIMx编码器模式
* @param 无
* @retval 无
*/
static void TIM_Encoder_Init(void)
{
TIM_Encoder_InitTypeDef Encoder_ConfigStructure;
/* 使能编码器接口时钟 */
ENCODER_TIM_CLK_ENABLE();
/* 定时器初始化设置 */
TIM_EncoderHandle.Instance = ENCODER_TIM;
TIM_EncoderHandle.Init.Prescaler = ENCODER_TIM_PRESCALER;
TIM_EncoderHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_EncoderHandle.Init.Period = ENCODER_TIM_PERIOD;
TIM_EncoderHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_EncoderHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
/* 设置编码器倍频数 */
Encoder_ConfigStructure.EncoderMode = ENCODER_MODE;
/* 编码器接口通道1设置 */
Encoder_ConfigStructure.IC1Polarity = ENCODER_IC1_POLARITY;
Encoder_ConfigStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
Encoder_ConfigStructure.IC1Prescaler = TIM_ICPSC_DIV1;
Encoder_ConfigStructure.IC1Filter = 0;
/* 编码器接口通道2设置 */
Encoder_ConfigStructure.IC2Polarity = ENCODER_IC2_POLARITY;
Encoder_ConfigStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
Encoder_ConfigStructure.IC2Prescaler = TIM_ICPSC_DIV1;
Encoder_ConfigStructure.IC2Filter = 0;
/* 初始化编码器接口 */
HAL_TIM_Encoder_Init(&TIM_EncoderHandle, &Encoder_ConfigStructure);
/* 清零计数器 */
__HAL_TIM_SET_COUNTER(&TIM_EncoderHandle, 0);
/* 清零中断标志位 */
__HAL_TIM_CLEAR_IT(&TIM_EncoderHandle,TIM_IT_UPDATE);
/* 使能定时器的更新事件中断 */
__HAL_TIM_ENABLE_IT(&TIM_EncoderHandle,TIM_IT_UPDATE);
/* 设置更新事件请求源为:计数器溢出 */
__HAL_TIM_URS_ENABLE(&TIM_EncoderHandle);
/* 设置中断优先级 */
HAL_NVIC_SetPriority(ENCODER_TIM_IRQn, 5, 1);
/* 使能定时器中断 */
HAL_NVIC_EnableIRQ(ENCODER_TIM_IRQn);
/* 使能编码器接口 */
HAL_TIM_Encoder_Start(&TIM_EncoderHandle, TIM_CHANNEL_ALL);
}
/**
* @brief 编码器接口初始化
* @param 无
* @retval 无
*/
void Encoder_Init(void)
{
Encoder_GPIO_Init(); /* 引脚初始化 */
TIM_Encoder_Init(); /* 配置编码器接口 */
}
/**
* @brief 定时器更新事件回调函数
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 判断当前计数器计数方向 */
if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))
/* 下溢 */
Encoder_Overflow_Count--;
else
/* 上溢 */
Encoder_Overflow_Count++;
}
int main(void)
{
.............
while(1)
{
/* 扫描KEY1,启动电机 */
if(Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON)
{
MOTOR_EN(ON);
}
/* 扫描KEY2,停止电机 */
if(Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON)
{
MOTOR_EN(OFF);
}
/* 扫描KEY3,改变方向 */
if(Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON)
{
static int j = 0;
j > 0 ? MOTOR_DIR(CCW) : MOTOR_DIR(CW);
j=!j;
}
/* 20ms计算一次 */
/* 电机旋转方向 = 计数器计数方向 */
motor_direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&TIM_EncoderHandle);
/* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD */
capture_count =__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * ENCODER_TIM_PERIOD);
/* 单位时间内总计数值 = 当前时刻总计数值 - 上一时刻总计数值 */
count_per_unit = capture_count - last_count;
/* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数 */
shaft_speed = (float)count_per_unit / ENCODER_TOTAL_RESOLUTION * 50 ;
/* 累积圈数 = 当前时刻总计数值 / 编码器总分辨率 */
number_of_rotations = (float)capture_count / ENCODER_TOTAL_RESOLUTION;
/* 记录当前总计数值,供下一时刻计算使用 */
last_count = capture_count;
if(i == 50)/* 1s报告一次 */
{
printf("\r\n电机方向:%d\r\n", motor_direction);
printf("单位时间内有效计数值:%d\r\n", (count_per_unit<0 ? abs(count_per_unit) : count_per_unit));
printf("步进电机转速:%.2f 转/秒\r\n", shaft_speed);
printf("累计圈数:%.2f 圈\r\n", number_of_rotations);
i = 0;
}
delay_ms(20);
i++;
}