3、编码器接口与电机测速:正交编码器原理、STM32定时器编码器模式、速度采集与滤波
做机器人运动控制,绕不开一个核心问题:电机到底转了多少?
你想想看,你给电机发了PWM,它转了。但实际转了多少圈?有没有丢步?速度稳不稳?这些问题,光靠开环控制是回答不了的。这时候,编码器就派上用场了。
我个人习惯把编码器比作电机的「眼睛」。没有它,你的控制器就是个盲人。今天我们就来聊聊这个「眼睛」是怎么工作的,以及如何在STM32上把它用好。
3.1 正交编码器的工作原理
正交编码器,说白了就是能告诉你位置和方向的传感器。它输出两路方波信号,通常叫A相和B相。
核心概念:两路信号相位差90°,通过判断谁先跳变,就能知道电机是正转还是反转。
我在项目中遇到过一个问题:电机抖动时,编码器会输出错误的脉冲。后来发现,是没理解正交信号的真正含义。
3.1.1 信号时序与判向逻辑
来看一个典型的正交信号时序:
- 正转:A相上升沿时,B相为低电平
- 反转:A相上升沿时,B相为高电平
- 四倍频:利用A、B相的上升沿和下降沿,一个周期可以计4个脉冲
嗯,这里要注意。四倍频虽然提高了分辨率,但也放大了噪声。我一般建议:如果电机振动大,先用二倍频,稳定后再切四倍频。
| 信号边沿 | 正转计数 | 反转计数 |
|---|---|---|
| A上升沿,B=0 | +1 | -1 |
| A下降沿,B=1 | +1 | -1 |
| B上升沿,A=1 | +1 | -1 |
| B下降沿,A=0 | +1 | -1 |
这张表你记牢了,编码器接口的底层逻辑就通了。
3.2 STM32定时器编码器模式
STM32的定时器有个隐藏技能——编码器模式。说白了,就是硬件帮你把A、B相的逻辑判断、计数、方向识别全干了。你只需要读寄存器就行。
我记得第一次用这个功能时,还傻傻地用外部中断去读编码器。后来发现定时器自带这个模式,真是相见恨晚。
3.2.1 模式配置与寄存器
STM32的编码器模式有三种:
- 模式1:只在TI1(A相)边沿计数
- 模式2:只在TI2(B相)边沿计数
- 模式3:在TI1和TI2所有边沿计数(四倍频)
我一般用模式3,分辨率最高。但要注意,计数器溢出问题。
经验之谈:定时器是16位的,最大计数值65535。如果电机转得快,很容易溢出。我习惯把定时器设为中心对齐模式,配合DMA定期读取,避免丢数。
配置代码其实不复杂:
// 以STM32F407 TIM3为例
TIM_EncoderInterfaceConfig(TIM3, TIM_ENCODERMODE_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0F; // 滤波,后面会讲
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 同样的配置给Channel 2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 使能定时器
TIM_Cmd(TIM3, ENABLE);
这段代码的核心是 TIM_ENCODERMODE_TI12,告诉定时器:你帮我盯着A和B,我自己算方向。
3.2.2 计数器读取与方向判断
读编码器值很简单:
int16_t encoder_value = TIM_GetCounter(TIM3);
方向呢?看 TIM_SMCR 寄存器的 DIR 位。但我不建议频繁读寄存器,效率低。我一般这样处理:
// 用上一次值和当前值比较,判断方向
static int16_t last_enc = 0;
int16_t curr_enc = TIM_GetCounter(TIM3);
int16_t delta = curr_enc - last_enc;
if (delta > 0) {
// 正转
} else if (delta < 0) {
// 反转
}
last_enc = curr_enc;
这样做的好处是:一次读取,既得位置,又得方向。
3.3 速度采集与滤波
位置有了,速度怎么算?
最简单的办法:单位时间内的脉冲增量。但实际做起来,坑不少。
注意:直接差分算速度,噪声会被放大。尤其是低速时,脉冲间隔长,算出来的速度一跳一跳的。我曾经因为这个,让机器人走直线走出了「醉汉步」。
3.3.1 M法、T法、M/T法
测速有三种经典方法:
| 方法 | 原理 | 适用场景 | 缺点 |
|---|---|---|---|
| M法 | 固定时间,数脉冲数 | 中高速 | 低速时精度差 |
| T法 | 固定脉冲数,测时间 | 低速 | 高速时更新慢 |
| M/T法 | 结合两者 | 宽速域 | 实现稍复杂 |
我个人的习惯是:机器人关节电机用M/T法,因为要兼顾低速启停和高速运行。如果是轮式机器人,M法就够了。
3.3.2 滤波算法实战
算出来的速度,必须滤波。不然控制器的输出会抖得像筛子。
我常用的三种滤波:
- 限幅滤波:如果当前速度与上次速度的差值超过阈值,就丢弃。适合剔除突发噪声。
- 滑动平均:取最近N个速度值的平均。N一般取4~8,太大延迟高。
- 一阶低通滤波:公式简单,计算量小。
一阶低通的代码:
#define ALPHA 0.2f // 滤波系数,越小越平滑
float lowpass_filter(float input, float last_output) {
return ALPHA * input + (1.0f - ALPHA) * last_output;
}
这个 ALPHA 怎么调?我一般先设0.2,看波形。如果噪声大,降到0.1;如果响应慢,升到0.3。没有固定值,得试。
调试技巧:用DAC输出滤波前后的速度值,接示波器看。比串口打印直观一百倍。我每次调滤波参数,都是这么干的。
3.4 本章知识结构
下面这张图,把编码器测速的完整链路画出来了。从硬件信号到软件滤波,每一步都环环相扣。
从这张图你能看到,整个测速链路其实就四个环节。每个环节都有坑,但也都对应着成熟的解决方案。