4、数字滤波算法(一):移动平均滤波的原理、窗口长度选择、阶跃响应分析

说到电流采样,噪声是个绕不开的话题。硬件滤波能干掉一部分高频干扰,但低频噪声、工频干扰这些,光靠RC滤波有时候真搞不定。这时候,数字滤波就该上场了。

今天咱们聊的是数字滤波里最基础、也最常用的一种——移动平均滤波。别小看它,我做了这么多年嵌入式,很多工业现场的电流采样,最后用的就是它。简单,但管用。

4.1 移动平均滤波的原理

说白了,移动平均滤波就是「取最近N个采样值的平均值」。每来一个新数据,就把最老的那个丢掉,重新算一次平均。

数学表达式长这样:

y[n] = (x[n] + x[n-1] + ... + x[n-N+1]) / N

其中:

  • y[n] 是当前滤波输出
  • x[n] 是当前采样值
  • N 是窗口长度

嗯,就这么简单。但简单归简单,用起来有讲究。

核心思想:移动平均滤波本质上是一个低通滤波器。它把高频噪声平均掉了,保留的是信号的直流分量和低频变化。

我在项目中遇到过一种情况:采样电流里混着50Hz的工频干扰。硬件上加了滤波器,但效果一般。后来我用移动平均,窗口长度选了个20ms对应的采样点数,工频干扰直接被干掉了。你想想看,一个周期内的噪声被平均掉,剩下的就是干净的直流信号。

4.2 窗口长度怎么选?

窗口长度N,是移动平均滤波最关键的参数。选大了,滤波效果好,但响应慢;选小了,响应快,但滤波效果差。这是个trade-off。

我个人习惯,先看噪声频率,再看信号变化速度。具体来说:

窗口长度N 滤波效果 响应速度 适用场景
小(2~4) 噪声小、信号变化快的场景
中(8~16) 中等 中等 一般工业电流采样
大(32~64) 噪声大、信号变化慢的场景

举个例子。假设你的采样率是1kHz,噪声是50Hz工频干扰。一个周期是20ms,对应20个采样点。如果你把窗口长度设为20,正好覆盖一个完整周期,工频干扰就被平均掉了。这就是我前面说的那个案例。

经验之谈:窗口长度最好选为噪声周期的整数倍。比如50Hz干扰,采样率1kHz,N=20或40效果最好。我曾经试过N=19,结果滤波后还有残留,后来改成20就干净了。

但要注意,窗口长度不能太大。为什么呢?因为移动平均滤波会引入延迟。延迟时间 = N × 采样周期。如果N=100,采样率1kHz,延迟就是100ms。对于需要快速响应的电流保护,这个延迟可能就太大了。

4.3 阶跃响应分析

阶跃响应,说白了就是看滤波器对突变信号的跟踪能力。你想想看,电流突然从1A跳到5A,滤波器需要多久才能跟上?

移动平均滤波的阶跃响应,是一个线性上升的过程。具体来说:

  • 当阶跃发生时,第一个输出点只包含1个新数据,N-1个旧数据
  • 第二个输出点包含2个新数据,N-2个旧数据
  • 以此类推,直到第N个输出点全部是新数据

所以,阶跃响应的上升时间 = N个采样周期。也就是说,窗口长度N越大,响应越慢。

我画了一张图,帮你理解这个过程:

移动平均滤波阶跃响应示意图(N=5) 0 1.0 n n+1 n+2 n+3 n+4 输入信号(阶跃) 滤波输出 上升时间 = N个采样周期

从图上你能看到,阶跃发生后,输出不是立刻跳变,而是分N步慢慢爬上去。每一步增加的量是 (新值 - 旧值) / N。

注意:如果你需要快速检测电流突变(比如过流保护),移动平均滤波的延迟可能会让你错过保护时机。我曾经在一个电机驱动项目里吃过这个亏——过流信号被平均滤波平滑掉了,结果MOS管烧了。后来我改用「先判断再滤波」的策略,才解决了问题。

4.4 代码实现

最后,给一个C语言的实现。我习惯用环形缓冲区,效率高,不用频繁搬移数据。

#define WINDOW_SIZE 16

typedef struct {
    float buffer[WINDOW_SIZE];
    int index;
    float sum;
    int count;
} MovingAverage;

void MovingAverage_Init(MovingAverage *ma) {
    ma->index = 0;
    ma->sum = 0.0f;
    ma->count = 0;
    for (int i = 0; i < WINDOW_SIZE; i++) {
        ma->buffer[i] = 0.0f;
    }
}

float MovingAverage_Update(MovingAverage *ma, float new_sample) {
    // 减去最旧的值
    ma->sum -= ma->buffer[ma->index];
    
    // 存入新值
    ma->buffer[ma->index] = new_sample;
    ma->sum += new_sample;
    
    // 更新索引
    ma->index = (ma->index + 1) % WINDOW_SIZE;
    
    // 计数不超过窗口大小
    if (ma->count < WINDOW_SIZE) {
        ma->count++;
    }
    
    return ma->sum / ma->count;
}

这段代码里,我用了sum变量来累加,每次更新只做一次加法和一次减法,计算量很小。在STM32这类MCU上跑,一个采样周期内就能算完,不耽误事儿。

小技巧:如果窗口长度是2的幂(比如8、16、32),可以用位运算代替取模运算,速度更快。比如 index = (index + 1) & (WINDOW_SIZE - 1)。我习惯这么写,省几个时钟周期也是好的。

好了,移动平均滤波就聊到这儿。它简单、高效、实用,是电流采样噪声滤波的入门首选。但要注意,它也有局限性——对高频噪声效果一般,阶跃响应有延迟。下一节我们会聊另一种数字滤波算法,专门解决这些问题。


专注资料整理