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脉冲输入,到最终的位置和速度输出,每一步都有对应的处理模块。

编码器信号处理完整链路 ABZ脉冲输入 正交信号解码 倍频处理 数据分发 速度计算 M法(高速) T法(低速) 位置累加 32位扩展 + 溢出处理 位置清零(临界区保护) 位置 / 速度 输出 关键提醒 • 解码决定方向 • 倍频提高分辨率 • 速度计算分场景 • 位置累加防溢出

这张图把整个流程串起来了。你可以把它当作调试时的检查清单:信号进来,解码对不对?倍频配了没?速度用哪种方法?位置累加有没有溢出保护?每一步都确认无误,数据才能可靠。