4、正交解码与抗抖动设计:施密特触发器应用、数字滤波(窗口滤波法)、毛刺消除实战经验
编码器信号最让人头疼的问题是什么?
不是分辨率不够,也不是速度太快。而是——抖动。
电机低速运行时,编码器输出信号在边沿附近反复跳变,一个本该只计一次的脉冲,硬生生被抖成了几十次。位置精度?直接崩了。
这一章,我们就来彻底解决这个问题。
4.1 信号抖动的根源
先搞清楚抖动从哪来。
- 机械振动:电机轴在低速时的不稳定旋转,导致编码器码盘在边沿附近来回摆动。
- 电气噪声:长线传输、电机驱动PWM干扰、电源纹波,都会在信号边沿叠加毛刺。
- 信号反射:编码器线缆阻抗不匹配时,边沿处产生振铃,形成多次过零。
我遇到过最夸张的一次,一个1024线的编码器,在电机转速只有10rpm时,计数器每秒跳了3000多次。实际位置根本没动,全是抖动。
核心认知:抖动不是随机噪声,而是信号在逻辑电平阈值附近反复穿越造成的伪边沿。消除抖动,本质上是让信号在阈值附近“一次穿越,不再回头”。
4.2 第一道防线:施密特触发器
施密特触发器是什么?说白了,就是一个带迟滞的比较器。
普通比较器只有一个阈值电压。信号在阈值附近波动,输出就会反复跳变。施密特触发器有两个阈值——正向阈值和负向阈值,中间有一个回差区间。
| 特性 | 普通比较器 | 施密特触发器 |
|---|---|---|
| 阈值数量 | 1个 | 2个(正向/负向) |
| 抗抖动能力 | 弱 | 强(回差区间抑制噪声) |
| 输出边沿 | 易产生毛刺 | 干净、陡峭 |
| 典型芯片 | LM393 | 74HC14、MAX9926 |
在STM32内部,GPIO输入本身就带有施密特触发器。但问题是——内部施密特的回差电压很小,通常只有几百毫伏。对于工业现场的长线编码器信号,这点回差根本不够用。
我的习惯:在编码器信号进入STM32之前,先经过一片74HC14做外部施密特整形。74HC14的回差电压约0.8V@5V,对付工业噪声绰绰有余。成本?几毛钱。效果?立竿见影。
4.3 第二道防线:数字窗口滤波
施密特触发器能干掉大部分电气噪声,但机械抖动它管不了。机械抖动的特点是:信号确实发生了电平变化,但在边沿附近来回弹跳多次才稳定下来。
这时候,需要数字滤波上场。
窗口滤波法的原理很简单:
- 检测到信号边沿时,不立即响应,而是启动一个定时窗口。
- 在窗口时间内,连续采样信号电平N次。
- 如果N次采样结果一致,才确认边沿有效。
- 如果采样结果不一致,丢弃本次边沿,等待下一个边沿。
为什么叫“窗口”?因为你在时间轴上开了一个观察窗口,窗口内信号必须保持稳定,才算有效。
关键参数:窗口宽度 = 采样次数 × 采样间隔。窗口太短,滤不掉抖动;窗口太长,会丢失真实边沿,影响高速响应。我一般取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 抗抖动设计整体架构
把上面几层防护串起来,就是一套完整的抗抖动方案。我画了一张架构图,你看一眼就明白了。
三层防护,逐级递进。硬件先粗滤,干掉大部分电气噪声;数字窗口滤波精准打击机械抖动;软件去毛刺兜底,处理漏网之鱼。三层下来,编码器信号基本就干净了。
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为编码器最高频率 |
最后提醒一句:参数不是死的。不同编码器、不同线缆长度、不同电机类型,抖动特性都不一样。我每次做新项目,都会先用示波器抓一下编码器信号,看看抖动幅度和频率,再针对性调整滤波参数。别偷懒,这一步省不了。