目录
编码器
增量式编码器倍频技术
常用编码器测速方法:M法、T法和M/T法
STM32的编码器接口
编码器接口结构体
减速电机编码器测速实验
硬件设计
TIM3配置编码器
测速环节
步进电机编码器测速实验
编码器
增量式编码器倍频技术
增量式编码器输出的常见脉冲波形信号形式:
占空比为50%的方波,通道A和通道B相位差为90°。
正弦波的模拟信号,通道A和通道B相位差为90°。
对于占空比为50%的方波,通道A和通道B相位差为90°。先以下图为例子。
如果只在某一通道的上升沿计数,则计数频率 = 该通道频率。
如果在某一通道的上升沿和下降沿都计数,则计数频率 = 该通道频率 * 2,即2倍频。
如果在双通道的上升沿和下降沿都计数,则计数频率 = 该通道频率 * 4,即4倍频。
因此,至少在1/2个原始方波周期内就可以计数一次,最多在1/4个原始方波周期内就可以计数一次。这样计数频率就是原始方波信号的2或4倍,即编码器的分辨率提高了2倍到4倍。
假设有个增量式编码器的分辨率是600PPR,能分辨最小角度是0.6°,对它进行4倍频后就相当于把分辨率提高到600*4=2400PPR,此时编码器能够分辨的最小角度为0.15°。
编码器倍频计数还可以用来扩展一些测速方法的速度适用范围。例如电机测试通常使用M法测量,编码器4倍频后可以扩展M法的速度下限。
常用编码器测速方法:M法、T法和M/T法
对于电机转速的测试,可以把增量式编码器安装到电机上,用控制器对编码器脉冲进行计数,然后通过特定的方法求出电机转速。
M法(频率测量法):在一个固定的定时时间内(以秒为单位),统计这段时间的编码器脉冲数,计算速度值。
设编码器单圈脉冲数为C,在时间T内统计到的编码器脉冲数为M,则转速n = M / CT。编码器单圈脉冲数C是一个常数,所以转速n和M成正比,使得在高速测量时M变大可以获得较好的测量精度和平稳度。但如果速度很低,低到每个T只有几个脉冲,此时算出的转速误差就会比较大,并且不稳定。
有一些方法可以改善M法在低速测量的准确性,比如增量式编码器倍频技术。当原本捕获到的脉冲M只有4个,经过4倍频后,相同电机状态M变成了16个,也就提升了低速下的测量精度。
T法(周期测量法):建立在一个已知频率的高频脉冲并对其计数,计数时间由捕获到的编码器相邻两个脉冲的间隔时间T决定,计数值为M。设编码器单圈总脉冲数为C,高频脉冲的频率为F,则转速n = 1/CT=F/CM。C和F是常数,所以转速n和M成反比。在电机高转速时,编码器脉冲间隔时间T很小,使得测量周期内的高频脉冲计数值M也变得很少,导致测量误差变大。而在低转速时,T足够大,测量周期内的M也足够多。所以T法和M法刚好相反,更适合测量低速。
M/T法:综合了M法和T法各自的优势,即测量编码器脉冲数又测量一定时间内的高频脉冲数。
在一个相对固定的时间内,计数编码器脉冲数为M0,并计数一个已知频率为F的高频脉冲,计数值为M1,设编码器单圈总脉冲数为C,则转速n = FM0/CM1。由于F和C是常数,所以转速n只受M0和M1的影响。电机高速时,M0增大,M1减小,相当于M法;电机低速时,M1增大,M0减小,相当于T法。
STM32的编码器接口
STM32芯片内部有专门用来采集增量式编码器方波信号的接口,这些接口实际上是STM32定时器的其中一种功能。编码器接口功能只有高级定时器TIM1/8和通用定时器TIM2~TIM5才有。
编码器接口用到了定时器的输入捕获部分。
这个表格将编码器接口所有可能出现的工作情况全都列了出来,包括它是如何实现方向检测和倍频的。
STM32的编码器接口在计数时并不是单纯地采集某一通道信号地上升沿或下降沿,而是需要综合另一个通道信号的电平。
表中“相反信号的电平”指的是在计数时所参考的另一个通道信号的电平,这些电平决定了计数器的计数方向。
仅在TI1处计数:TI1比TI2提前1/4个周期,以TI1的信号边沿作为有效边沿。当检测到TI1的上升沿时,TI2为低电平,此时计数器向上计数一次,下一时刻检测到TI1的下降沿时,TI2为高电平,此时计数器仍然向上计数一次,以此类推。这样就能把TI1的上升沿和下降沿都用来计数,即实现了对原始信号的2倍频。
仅在TI2处计数:TI1比TI2滞后1/4个周期,以TI1的信号边沿作为有效边沿。当检测到TI1的上升沿时,TI2为高电平,此时计数器向下计数一次,下一时刻检测到TI1的下降沿时,TI2为低电平,此时计数器仍然向下计数一次,以此类推。这样就能把TI1的上升沿和下降沿都用来计数,即实现了对原始信号的2倍频。
在TI1和TI2处均计数:把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考,相当于仅在一个通道处计数的2倍,所以实现了对原始信号的4倍频。
编码器接口结构体
主要有时基初始化结构体TIM_Base_InitTypeDef和编码器初始化配置结构体TIM_Encoder_InitTypeDef。
编码器初始化配置结构体TIM_Encoder_InitTypeDef用于定时器的编码器接口模式,与HAL_TIM_Encoder_Init函数配合使用完成初始化配置工作。
高级定时器TIM1和TIM8以及通用定时器TIM2~TIM5都带有编码器接口,使用时都必须单独设置。
typedef struct
{
uint32_t EncoderMode; // 编码器模式。通道A计数/通道B计数/双通道计数。设定TIMx_SMCR:SMS[2:0]
uint32_t IC1Polarity; // 输入信号极性。设置定时器通道在编码器模式下的输入信号是否反相。设定TIMx_CCER:CCxNP、CCxP
uint32_t IC1Selection; // 输入通道。TIM_ICSELECTION_DIRECTTI/TIM_ICSELECTION_INDIRECTTI/TIM_ICSELECTION_TRC。设定TIMx_CCMRx:CCxS[1:0]
uint32_t IC1Prescaler; // 输入捕获预分频器。1/2/4/8,设定TIMx_CCMRx:ICxPSC[1:0]
uint32_t IC1Filter; // 输入捕获滤波器,0x0~0xf,设定TIMx_CCMRx:ICxF[3:0]
uint32_t IC2Polarity; // 输入信号极性。设置定时器通道在编码器模式下的输入信号是否反相。设定TIMx_CCER:CCxNP、CCxP
uint32_t IC2Selection; // 输入通道。TIM_ICSELECTION_DIRECTTI/TIM_ICSELECTION_INDIRECTTI/TIM_ICSELECTION_TRC。设定TIMx_CCMRx:CCxS[1:0]
uint32_t IC2Prescaler; // 输入捕获预分频器。1/2/4/8,设定TIMx_CCMRx:ICxPSC[1:0]
uint32_t IC2Filter; // 输入捕获滤波器,0x0~0xf,设定TIMx_CCMRx:ICxF[3:0]
}TIM_Encoder_InitTypeDef;
单通道计数设置编码器接口为2倍频,双通道计数设置编码器接口为4倍频。
编码器接口模式下只能使用这个TIM_ICSELECTION_DIRECTTI。
减速电机编码器测速实验
硬件设计
用到的减速电机和之前的减速电机按键控制例程相同,所以电机、开发板和驱动板的硬件连接也完全相同,只加上了编码器的连线。
TIM3配置编码器
/* 定时器溢出次数 */
__IO int16_t Encoder_Overflow_Count = 0;
TIM_HandleTypeDef TIM_EncoderHandle;
/**
* @brief 编码器接口初始化
* @param 无
* @retval 无
*/
void Encoder_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
TIM_Encoder_InitTypeDef Encoder_ConfigStructure;
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
// 编码器通道1
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 编码器通道2
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* 定时器初始化设置 */
TIM_EncoderHandle.Instance = TIM3;
TIM_EncoderHandle.Init.Prescaler = 0;
TIM_EncoderHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM_EncoderHandle.Init.Period = 65535;
TIM_EncoderHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM_EncoderHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
/* 设置编码器倍频数 */
Encoder_ConfigStructure.EncoderMode = TIM_ENCODERMODE_TI12;
Encoder_ConfigStructure.IC1Polarity = TIM_ICPOLARITY_RISING;
Encoder_ConfigStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
Encoder_ConfigStructure.IC1Prescaler = TIM_ICPSC_DIV1;
Encoder_ConfigStructure.IC1Filter = 0;
Encoder_ConfigStructure.IC2Polarity = TIM_ICPOLARITY_RISING;
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(TIM3_IRQn, 5, 1);
/* 使能定时器中断 */
HAL_NVIC_EnableIRQ(TIM3_IRQn);
/* 使能编码器接口 */
HAL_TIM_Encoder_Start(&TIM_EncoderHandle, TIM_CHANNEL_ALL);
}
/**
* @brief 定时器更新事件回调函数
* @param 无
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 判断当前计数器计数方向 */
if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&TIM_EncoderHandle))
{ /* 下溢 */
Encoder_Overflow_Count--;
}
else
{ /* 上溢 */
Encoder_Overflow_Count++;
}
}
测速环节
/* 电机旋转方向 */
__IO int8_t Motor_Direction = 0;
/* 当前时刻总计数值 */
__IO int32_t Capture_Count = 0;
/* 上一时刻总计数值 */
__IO int32_t Last_Count = 0;
/* 电机转轴转速 */
__IO float Shaft_Speed = 0.0f;
/* 配置1ms时基为SysTick */
HAL_InitTick(5);
/**
* @brief SysTick中断回调函数
* @param 无
* @retval 无
*/
void HAL_SYSTICK_Callback(void)
{
static uint16_t i = 0;
i++;
/* 100ms计算一次 */
if (i == 100)
{
/* 电机旋转方向 = 计数器计数方向 */
Motor_Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&TIM_EncoderHandle);
/* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * 65535 */
Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * ENCODER_TIM_PERIOD);
/* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率(4*16,4倍的物理分辨率) * 时间系数 */
Shaft_Speed = (float)(Capture_Count - Last_Count) / ENCODER_TOTAL_RESOLUTION * 10 ;
printf("电机方向:%d\r\n", Motor_Direction);
/* 单位时间计数值 = 当前时刻总计数值 - 上一时刻总计数值 */
printf("单位时间内有效计数值:%d\r\n", Capture_Count - Last_Count);
printf("电机转轴处转速:%.2f 转/秒 \r\n", Shaft_Speed);
/* 输出轴转速 = 转轴转速 / 减速比 */
printf("电机输出轴转速:%.2f 转/秒 \r\n", Shaft_Speed / REDUCTION_RATIO);
/* 记录当前总计数值,供下一时刻计算使用 */
Last_Count = Capture_Count;
i = 0;
}
}
步进电机编码器测速实验
类似