用 STM32 的 TIM 编码器模式做高分辨率测速:溢出处理与低速抖动抑制的工程实现
在电机控制、机器人关节、云台等应用中,测速精度直接影响控制性能。STM32 的定时器编码器模式可以硬件自动计数正交编码器的脉冲,配合高线数编码器能获得很高的分辨率。但实际工程中,计数器溢出和低速抖动是两个绕不开的坑。本文从原理到代码,给出完整的解决方案。
1. 编码器模式原理回顾
STM32 的 TIM1/TIM2/TIM3/TIM4/TIM5/TIM8 等高级/通用定时器支持编码器接口模式。当配置为编码器模式时:
- 编码器 A、B 相分别接到 TIMx_CH1、TIMx_CH2。
- 硬件根据两相边沿自动增减 CNT,无需 CPU 干预。
- 计数方向由 A、B 相位关系决定,CNT 在 0~ARR 之间循环。
- 常用 4 倍频(TI1 和 TI2 都计数),分辨率 = 编码器线数 × 4。
例如 1000 线编码器,4 倍频后每转 4000 个计数。若定时器 ARR=65535,则每转计数远小于 ARR,不会溢出;但若编码器线数很高或需要长时间累积,溢出就不可避免。
2. 溢出处理:扩展计数器位宽
2.1 为什么需要溢出处理
假设每 10ms 读取一次速度,电机高速旋转时 10ms 内可能产生超过 65535 个计数,导致 CNT 回绕,直接读 CNT 会得到错误的小值。即使不溢出,频繁读取也会丢失计数。
2.2 硬件方案:利用溢出中断累加
最可靠的方法是开启定时器更新中断,在中断中记录溢出次数,将 16 位 CNT 扩展为 32 位或 64 位。
// 全局变量
volatile int32_t encoder_overflow = 0; // 溢出次数(有符号)
volatile uint16_t last_cnt = 0;
// TIM 更新中断服务函数
void TIM2_IRQHandler(void)
{
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
// 根据计数方向判断溢出方向
if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2))
encoder_overflow--;
else
encoder_overflow++;
}
}
2.3 软件读取:原子操作获取总计数
读取时需保证 encoder_overflow 和 CNT 的一致性。推荐先关中断再读:
int32_t Encoder_GetTotalCount(void)
{
uint16_t cnt;
int32_t ovf;
__disable_irq();
cnt = __HAL_TIM_GET_COUNTER(&htim2);
ovf = encoder_overflow;
__enable_irq();
return (ovf * 65536) + cnt;
}
2.4 测速计算
在固定周期 T(如 10ms)内读取两次总计数,差值即为增量:
int32_t delta = Encoder_GetTotalCount() - last_total;
last_total = Encoder_GetTotalCount();
float speed_rpm = (delta / (4.0f * ENCODER_LINES)) * (60.0f / T);
3. 低速抖动抑制:动态滤波与阈值
3.1 抖动来源
低速时,编码器脉冲稀疏,一个采样周期内可能只有 0~2 个脉冲,量化误差导致速度值剧烈跳动。此外,机械振动、电磁干扰也会引入噪声。
3.2 工程方案:一阶低通 + 死区
-
一阶低通滤波:
y = α*x + (1-α)*y,α 根据速度自适应。 - 死区:当 |delta| 小于阈值时,认为电机静止,速度置 0。
#define ALPHA_FAST 0.5f
#define ALPHA_SLOW 0.1f
#define DEAD_ZONE 2 // 计数阈值
float speed_filter(float raw, float prev)
{
if (abs(raw) < DEAD_ZONE)
return 0.0f;
float alpha = (fabsf(raw) > 100.0f) ? ALPHA_FAST : ALPHA_SLOW;
return alpha * raw + (1.0f - alpha) * prev;
}
3.3 更优方案:M/T 法混合测速
低速用 T 法(测脉冲间隔),高速用 M 法(测固定时间脉冲数),结合两者可在全速域获得平稳输出。实现稍复杂,但效果显著。
4. CubeMX 配置步骤
- 选择 TIMx,Combined Channels 设为 Encoder Mode。
- Encoder Mode 选 TI1 and TI2(4 倍频)。
- 极性根据实际接线调整,通常 Rising Edge。
- 开启 TIMx 全局中断(NVIC)。
- 生成代码,在
MX_TIMx_Init()后调用HAL_TIM_Encoder_Start(&htimx, TIM_CHANNEL_ALL)。
5. 完整代码示例(核心部分)
#include "stm32f4xx_hal.h"
extern TIM_HandleTypeDef htim2;
#define ENCODER_LINES 1000
#define SAMPLE_TIME_S 0.01f
static int32_t last_total = 0;
static float speed_filt = 0.0f;
void Encoder_Init(void)
{
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
}
int32_t Encoder_GetTotalCount(void)
{
uint16_t cnt;
int32_t ovf;
__disable_irq();
cnt = __HAL_TIM_GET_COUNTER(&htim2);
ovf = encoder_overflow;
__enable_irq();
return (ovf * 65536) + cnt;
}
float Encoder_GetSpeedRPM(void)
{
int32_t total = Encoder_GetTotalCount();
int32_t delta = total - last_total;
last_total = total;
float raw_rpm = (delta / (4.0f * ENCODER_LINES)) * (60.0f / SAMPLE_TIME_S);
speed_filt = speed_filter(raw_rpm, speed_filt);
return speed_filt;
}
6. 注意事项
- 中断优先级:编码器溢出中断不宜过高,避免影响其他实时任务。
- 计数器方向:不同定时器方向判断位可能不同,需查参考手册。
- 滤波参数:α 和死区需根据实际电机、负载调试,过大导致响应慢,过小滤波不足。
- 采样周期:与速度环控制周期一致,避免额外延迟。
- 编码器信号质量:建议使用差分编码器或加 RC 滤波,减少毛刺。
通过溢出扩展和动态滤波,STM32 编码器模式可以在全速域提供高分辨率、低抖动的速度反馈,为高性能控制打下基础。