4、信号预处理——滤波去噪:硬件RC滤波设计、软件滑动平均滤波、中值滤波实现

各位同学,咱们接着聊。上一节我们把轮速传感器的原始信号调理成了方波,但说实话,那个方波还远不够干净。你想想看,车轮在路面上跑,颠簸、电磁干扰、传感器本身的抖动,都会在信号上叠加一堆噪声。

这节我们就来解决这个问题——滤波去噪。我个人的经验是,滤波做不好,后面ABS算法写得再漂亮也是白搭。曾经有个项目,轮速信号在低速时频繁跳变,查了三天,最后发现是RC滤波的截止频率选高了,高频噪声全漏进来了。

滤波这件事,通常分两步走:硬件先粗滤,软件再精滤。咱们一个一个说。

4.1 硬件RC低通滤波——第一道防线

硬件滤波,说白了就是在传感器输出和MCU引脚之间,串一个电阻、并一个电容。结构简单,但效果立竿见影。

为什么用RC低通?因为轮速信号的频率本身不高——乘用车车轮转速最高也就2000rpm左右,对应的齿圈信号频率大概在几百赫兹到几千赫兹。而噪声往往是高频的,比如发动机点火干扰、车载逆变器开关噪声。RC低通正好把高频干掉。

电路长这样:

传感器输出 —— R —— MCU引脚
                  |
                  C
                  |
                 GND

截止频率的计算公式:

f_c = 1 / (2π × R × C)

举个例子。假设齿圈有48个齿,最高车速对应频率2kHz。我一般会把截止频率设在信号最高频率的3~5倍,也就是6kHz~10kHz。选R=1kΩ,C=22nF,算一下:

f_c = 1 / (2 × 3.14 × 1000 × 22×10^-9) ≈ 7.2kHz

嗯,这个值就挺合适。

我的小习惯: 电阻选1kΩ~10kΩ之间,电容选nF级别。电阻太小,前级驱动电流大;电阻太大,信号衰减严重。电容别用铝电解,用C0G或X7R贴片电容,温漂小。
注意: RC滤波会引入相位延迟。对于ABS这种实时性要求高的系统,延迟太大会影响控制精度。我一般控制延迟在信号周期的1/10以内。比如2kHz信号,周期500μs,延迟不要超过50μs。

4.2 软件滑动平均滤波——简单粗暴但有效

硬件滤波之后,信号已经干净了不少。但MCU引脚上可能还有残留的毛刺,或者因为传感器安装间隙变化导致的微小抖动。这时候就该软件上场了。

滑动平均滤波,是我用得最多的方法。原理很简单:维护一个长度为N的窗口,每次采样一个新值,就丢掉最旧的那个,然后对窗口内所有值求平均。

为什么好用?因为它对周期性噪声有很好的抑制效果,而且实现起来几乎不占CPU。你想想看,ABS系统里轮速信号每秒要采样几千次,如果每个点都做复杂运算,MCU扛不住的。

代码实现:

#define WINDOW_SIZE 8

static uint16_t buffer[WINDOW_SIZE] = {0};
static uint8_t index = 0;
static uint32_t sum = 0;

uint16_t moving_average(uint16_t new_sample)
{
    // 减去最旧的值
    sum -= buffer[index];
    // 存入新值
    buffer[index] = new_sample;
    // 加上新值
    sum += new_sample;
    // 更新索引
    index = (index + 1) % WINDOW_SIZE;
    // 返回平均值
    return sum / WINDOW_SIZE;
}

这里有个细节:窗口大小N怎么选?

窗口大小 平滑效果 响应速度 适用场景
4 一般 高速行驶,需要快速响应
8 较好 中等 城市道路,常规工况
16 很好 低速或静止检测

我个人习惯在低速时用大窗口,高速时用小窗口。曾经有个项目,我固定用8点平均,结果低速时信号抖动被平滑掉了,但高速时响应慢了半拍,导致ABS介入延迟。后来改成动态调整窗口大小,问题就解决了。

核心要点: 滑动平均滤波的本质是低通滤波。窗口越大,截止频率越低,平滑效果越好,但延迟也越大。这是一个典型的「平滑 vs 响应」的权衡。

4.3 中值滤波——专治脉冲噪声

滑动平均滤波对高斯噪声效果好,但遇到脉冲噪声——比如一个突然的尖峰——就有点力不从心了。因为平均会把尖峰的影响「摊」到整个窗口里。

这时候中值滤波就派上用场了。它的原理:对窗口内的数据排序,取中间那个值作为输出。

举个例子。原始数据:[100, 102, 98, 500, 101]。500明显是个异常尖峰。滑动平均会输出(100+102+98+500+101)/5=180.2,被拉高了很多。而中值滤波排序后:[98, 100, 101, 102, 500],取中间值101,完美避开干扰。

代码实现:

#define MEDIAN_WINDOW 5

uint16_t median_filter(uint16_t *buffer, uint8_t len)
{
    uint16_t temp[MEDIAN_WINDOW];
    uint8_t i, j;
    uint16_t tmp;

    // 复制数据
    for(i = 0; i < len; i++)
        temp[i] = buffer[i];

    // 冒泡排序(窗口小,够用)
    for(i = 0; i < len - 1; i++)
    {
        for(j = 0; j < len - 1 - i; j++)
        {
            if(temp[j] > temp[j+1])
            {
                tmp = temp[j];
                temp[j] = temp[j+1];
                temp[j+1] = tmp;
            }
        }
    }

    // 返回中值
    return temp[len / 2];
}
注意: 中值滤波需要排序,计算量比滑动平均大。窗口大小一般取3、5、7,奇数。窗口太大,排序耗时成倍增加,而且会丢失信号的细节变化。我一般只用3点或5点中值。

4.4 实际项目中的组合策略

讲到这里,你可能会问:到底用哪种?

我的答案是:组合使用

在实际的ABS控制器里,信号处理链路通常是这样的:

  1. 硬件RC滤波:在PCB上直接做,滤掉大部分高频噪声。这是必须的,不能省。
  2. 软件中值滤波:在MCU中断服务程序里,对ADC采样值做3点中值,干掉脉冲干扰。
  3. 软件滑动平均:在主循环或定时器里,对中值滤波后的数据做8点滑动平均,进一步平滑。

我曾经在一个项目中,只做了硬件滤波和滑动平均,结果在电磁兼容测试时,辐射干扰导致轮速信号偶尔跳变。后来在中断里加了一级3点中值滤波,问题就再没出现过。

避坑指南: 滤波器的参数不是一成不变的。不同车型、不同传感器、不同线束长度,噪声特性都不一样。我建议你在台架上用示波器抓一下原始信号,看看噪声的频率和幅度,再决定RC的截止频率和软件窗口大小。别偷懒,这一步省不了。

好了,这一节的内容就到这里。滤波去噪是信号处理的基础,做好了,后面的轮速计算和ABS控制才能稳。下一节我们讲轮速的周期测量和速度计算,到时候你会感谢现在把信号滤干净的自己。