4. 编码器信号采集与处理:从脉冲到位置速度的完整链路
正交信号解码、倍频、速度计算、位置累加——这四个环节串起来,就是编码器数据处理的完整流水线。很多初学者只关注硬件接线,忽略了软件层面的信号处理,结果就是:电机转得挺欢,读回来的数据却乱七八糟。
我个人习惯把这四个步骤看作一个“信号消化系统”。编码器是嘴巴,吃进来的是ABZ脉冲;STM32是胃,负责消化;而我们写的算法,就是消化酶。下面咱们一个一个拆开讲。
4.1 正交信号解码算法:从AB相到方向+计数
正交信号,说白了就是A、B两路相位差90°的方波。解码的核心任务只有两个:判断旋转方向和产生计数脉冲。
解码原理一句话:当A相上升沿到来时,检查B相的电平。B为低→正转;B为高→反转。反过来,B相上升沿查A相电平也行。
我在项目中遇到过一种情况:电机正转时计数增加,反转时计数却不变。查了半天,发现是解码逻辑里只处理了A相中断,没处理B相。你想想看,只靠一路信号,怎么可能知道方向?
STM32的定时器自带编码器模式,硬件直接帮你干了解码的活。但如果你想自己写软件解码,一般有两种方式:
| 解码方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 单边沿解码 | 只检测A相或B相的上升沿 | 代码简单,中断少 | 分辨率低,只有1倍频 |
| 双边沿解码 | 同时检测A相和B相的上升沿 | 分辨率翻倍,2倍频 | 中断频率翻倍 |
| 四边沿解码 | 检测A、B两相的全部上升沿和下降沿 | 分辨率最高,4倍频 | 中断频率最高,CPU开销大 |
嗯,这里要注意:硬件编码器模式默认就是4倍频解码。你配置好定时器的编码器模式后,计数器会自动在AB相的每个边沿都更新一次。省心,但也要知道它背后干了什么。
4.2 倍频技术:不换编码器也能提高分辨率
编码器本身有物理分辨率,比如一圈1000线。但通过倍频,你可以让分辨率变成2000、4000。说白了,就是“无中生有”地插值。
我曾经在一个项目里,编码器只有500线,但控制精度要求0.1°。算下来需要3600线/圈,差得远。后来用了4倍频,500×4=2000,还是不够。最后只能换编码器。所以倍频不是万能的,它只能锦上添花,不能雪中送炭。
我的建议:倍频倍数不要超过4倍。再高的话,信号抖动和噪声会被放大,反而得不偿失。如果你需要更高分辨率,直接换高线数编码器更靠谱。
倍频的实现方式,在STM32上其实很简单——配置定时器的编码器模式时,选择TI1FP1和TI2FP2的边沿检测方式即可。代码示例如下:
// STM32定时器编码器模式配置(4倍频)
TIM_EncoderInterfaceConfig(TIM3, TIM_ENCODERMODE_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
// TIM_ENCODERMODE_TI12 表示在TI1和TI2的每个边沿都计数
// 这就是4倍频模式
4.3 速度计算:M法和T法,各有利弊
速度计算是编码器应用里的重头戏。常用的方法就两种:M法(测频法)和T法(测周法)。我刚开始做的时候,傻傻分不清,后来踩了坑才明白它们的适用场景。
| 方法 | 原理 | 适用场景 | 缺点 |
|---|---|---|---|
| M法 | 固定时间内统计脉冲数 | 高速场合(脉冲多) | 低速时误差大 |
| T法 | 测量相邻脉冲的时间间隔 | 低速场合(周期长) | 高速时精度下降 |
| M/T法 | 结合两者,动态切换 | 宽速域场合 | 实现复杂 |
为什么会这样?M法在低速时,固定时间内可能只抓到几个脉冲,误差一个脉冲就是百分之几十。T法在高速时,脉冲间隔太短,定时器分辨率不够,误差也大。所以,没有万能的方法,只有合适的场景。
避坑指南:我曾经在一个伺服项目里,全程用M法测速。电机高速运行时数据很漂亮,但一降到100rpm以下,速度曲线就像心电图。后来换成T法,低速数据才稳定下来。记住:低速用T法,高速用M法,宽速域用M/T法。
M法的代码实现很简单:
// M法速度计算(定时器每10ms中断一次)
void Speed_Calculate_M_Method(void) {
static uint16_t last_count = 0;
uint16_t current_count = TIM_GetCounter(TIM3);
int16_t pulse_diff = current_count - last_count;
// 速度 = 脉冲差 / (时间 * 编码器分辨率 * 倍频数)
// 假设10ms采样,编码器1000线,4倍频
float speed_rpm = (float)pulse_diff * 60.0f / (0.01f * 1000.0f * 4.0f);
last_count = current_count;
}
4.4 位置累加与清零:别让计数器溢出坑了你
位置累加,说白了就是把每次的脉冲变化量加起来。但这里有个大坑——计数器溢出。
STM32的定时器计数器是16位的,最大65535。如果编码器转得快,几分钟就溢出了。你想想看,计数器从65535变到0,你以为是正转了一点点,其实是反转了65535步。位置数据直接崩掉。
解决方案:使用32位变量扩展计数范围。每次读取定时器计数器后,判断是否发生了溢出(向上溢出或向下溢出),然后手动调整32位累加值。
我个人的习惯是,在定时器更新中断(溢出中断)里处理这个逻辑:
// 32位位置累加(处理16位溢出)
int32_t position_32bit = 0;
uint16_t last_encoder_value = 0;
void Encoder_Position_Update(void) {
uint16_t current_value = TIM_GetCounter(TIM3);
int16_t delta = (int16_t)(current_value - last_encoder_value);
// 关键:用int16_t强制转换,自动处理溢出回绕
position_32bit += delta;
last_encoder_value = current_value;
}
清零操作就简单了。但要注意:清零不是把计数器写0就完事了。你得同时清零32位累加值和上一次的采样值,否则下次计算时会跳变。
我的建议:清零操作最好放在临界区里执行,关中断、清零、开中断。防止清零过程中被中断打断,读到半残废的数据。
4.5 本章知识体系总览
下面这张图,把编码器信号处理的完整链路画了出来。从ABZ脉冲输入,到最终的位置和速度输出,每一步都有对应的处理模块。
这张图把整个流程串起来了。你可以把它当作调试时的检查清单:信号进来,解码对不对?倍频配了没?速度用哪种方法?位置累加有没有溢出保护?每一步都确认无误,数据才能可靠。