4、正交解码与抗抖动设计:施密特触发器应用、数字滤波(窗口滤波法)、毛刺消除实战经验

编码器信号最让人头疼的问题是什么?

不是分辨率不够,也不是速度太快。而是——抖动。

电机低速运行时,编码器输出信号在边沿附近反复跳变,一个本该只计一次的脉冲,硬生生被抖成了几十次。位置精度?直接崩了。

这一章,我们就来彻底解决这个问题。

4.1 信号抖动的根源

先搞清楚抖动从哪来。

  • 机械振动:电机轴在低速时的不稳定旋转,导致编码器码盘在边沿附近来回摆动。
  • 电气噪声:长线传输、电机驱动PWM干扰、电源纹波,都会在信号边沿叠加毛刺。
  • 信号反射:编码器线缆阻抗不匹配时,边沿处产生振铃,形成多次过零。

我遇到过最夸张的一次,一个1024线的编码器,在电机转速只有10rpm时,计数器每秒跳了3000多次。实际位置根本没动,全是抖动。

核心认知:抖动不是随机噪声,而是信号在逻辑电平阈值附近反复穿越造成的伪边沿。消除抖动,本质上是让信号在阈值附近“一次穿越,不再回头”。

4.2 第一道防线:施密特触发器

施密特触发器是什么?说白了,就是一个带迟滞的比较器。

普通比较器只有一个阈值电压。信号在阈值附近波动,输出就会反复跳变。施密特触发器有两个阈值——正向阈值和负向阈值,中间有一个回差区间。

特性 普通比较器 施密特触发器
阈值数量 1个 2个(正向/负向)
抗抖动能力 弱 强(回差区间抑制噪声)
输出边沿 易产生毛刺 干净、陡峭
典型芯片 LM393 74HC14、MAX9926

在STM32内部,GPIO输入本身就带有施密特触发器。但问题是——内部施密特的回差电压很小,通常只有几百毫伏。对于工业现场的长线编码器信号,这点回差根本不够用。

我的习惯:在编码器信号进入STM32之前,先经过一片74HC14做外部施密特整形。74HC14的回差电压约0.8V@5V,对付工业噪声绰绰有余。成本?几毛钱。效果?立竿见影。

4.3 第二道防线:数字窗口滤波

施密特触发器能干掉大部分电气噪声,但机械抖动它管不了。机械抖动的特点是:信号确实发生了电平变化,但在边沿附近来回弹跳多次才稳定下来。

这时候,需要数字滤波上场。

窗口滤波法的原理很简单:

  1. 检测到信号边沿时,不立即响应,而是启动一个定时窗口。
  2. 在窗口时间内,连续采样信号电平N次。
  3. 如果N次采样结果一致,才确认边沿有效。
  4. 如果采样结果不一致,丢弃本次边沿,等待下一个边沿。

为什么叫“窗口”?因为你在时间轴上开了一个观察窗口,窗口内信号必须保持稳定,才算有效。

关键参数:窗口宽度 = 采样次数 × 采样间隔。窗口太短,滤不掉抖动;窗口太长,会丢失真实边沿,影响高速响应。我一般取5~10μs,具体看编码器最高频率。

4.4 窗口滤波的STM32实现

用STM32的定时器输入捕获配合软件滤波,是最灵活的做法。下面是我在实际项目中用过的代码框架:

// 窗口滤波参数
#define FILTER_WINDOW_US    10      // 窗口宽度10μs
#define FILTER_SAMPLES      5       // 采样5次

// 编码器信号滤波状态机
typedef enum {
    FILTER_IDLE,        // 空闲,等待边沿
    FILTER_SAMPLING,    // 采样窗口内
    FILTER_CONFIRMED    // 边沿已确认
} FilterState;

// 滤波处理函数(在定时器中断中调用,间隔2μs)
void EncoderFilter_Process(GPIO_TypeDef* port, uint16_t pin) {
    static FilterState state = FILTER_IDLE;
    static uint8_t sample_count = 0;
    static uint8_t stable_level = 0;
    
    switch(state) {
        case FILTER_IDLE:
            // 检测到边沿,进入采样
            if(GPIO_ReadInputDataBit(port, pin) != last_level) {
                state = FILTER_SAMPLING;
                sample_count = 0;
                stable_level = GPIO_ReadInputDataBit(port, pin);
            }
            break;
            
        case FILTER_SAMPLING:
            // 窗口内连续采样
            if(GPIO_ReadInputDataBit(port, pin) == stable_level) {
                sample_count++;
                if(sample_count >= FILTER_SAMPLES) {
                    // 确认边沿有效
                    state = FILTER_CONFIRMED;
                    // 触发正交解码
                    Quadrature_Decode(stable_level);
                }
            } else {
                // 采样不一致,抖动,丢弃
                state = FILTER_IDLE;
                sample_count = 0;
            }
            break;
            
        case FILTER_CONFIRMED:
            // 等待信号稳定后复位
            state = FILTER_IDLE;
            sample_count = 0;
            break;
    }
}

注意:滤波窗口内不要阻塞主循环。采样间隔由定时器中断驱动,滤波状态机在中断中执行。窗口宽度和采样次数的选择,要结合编码器最高频率和系统实时性要求来权衡。

4.5 毛刺消除实战经验

施密特触发器和窗口滤波配合使用,能解决90%以上的抖动问题。但还有10%的顽固毛刺,需要一些“野路子”。

经验一:硬件RC低通

在编码器信号线上串联一个100Ω电阻,对地并联一个100pF电容,构成一阶RC低通。截止频率约16MHz,对编码器信号(通常几百kHz)无影响,但对高频毛刺有很好的抑制作用。我习惯在74HC14的输入端前面加这个RC网络。

经验二:软件去毛刺——连续N次相同才更新

对于位置计数器,不要每次边沿都更新。而是连续N次解码结果一致时,才更新位置值。这个N值我一般取3~5,对低速抖动特别有效。

经验三:死区时间设置

在正交解码中,A相和B相的边沿之间有一个最小时间间隔。如果检测到两个边沿间隔小于某个值(比如1μs),就认为是抖动,忽略本次边沿。这个值要根据编码器最高频率计算:死区时间 < 1/(4×最高频率)。

我曾经踩过的坑:有一次在振动台上测试运动控制卡,编码器信号抖动特别厉害。施密特用了,窗口滤波也加了,还是不行。最后发现是编码器线缆屏蔽层没有单点接地,共模噪声耦合进了信号线。把屏蔽层在控制器端单点接地后,问题立刻消失。嗯,硬件设计,细节决定成败。

4.6 抗抖动设计整体架构

把上面几层防护串起来,就是一套完整的抗抖动方案。我画了一张架构图,你看一眼就明白了。

编码器信号抗抖动设计架构 编码器原始信号 第一层:硬件抗抖动 RC低通滤波 + 74HC14施密特触发器 第二层:数字窗口滤波 定时器中断采样 + 状态机确认 第三层:软件去毛刺 连续N次一致更新 + 死区时间判断 干净的位置脉冲 三层防护逐级递进:硬件先粗滤,数字滤波去抖动,软件去毛刺兜底

三层防护,逐级递进。硬件先粗滤,干掉大部分电气噪声;数字窗口滤波精准打击机械抖动;软件去毛刺兜底,处理漏网之鱼。三层下来,编码器信号基本就干净了。

4.7 参数选择速查表

最后给一张参数速查表,方便你实际设计时参考:

参数 推荐值 说明
RC低通截止频率 10~20MHz R=100Ω, C=100pF,对编码器信号无影响
施密特回差电压 ≥0.5V 74HC14约0.8V@5V,STM32内部约0.3V
窗口滤波宽度 5~10μs 根据编码器最高频率调整
采样次数 3~5次 次数越多,抗抖动越强,但响应变慢
软件去毛刺N值 3~5 连续N次一致才更新位置
死区时间 < 1/(4×f_max) f_max为编码器最高频率

最后提醒一句:参数不是死的。不同编码器、不同线缆长度、不同电机类型,抖动特性都不一样。我每次做新项目,都会先用示波器抓一下编码器信号,看看抖动幅度和频率,再针对性调整滤波参数。别偷懒,这一步省不了。