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步慢慢爬上去。每一步增加的量是 (新值 - 旧值) / 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)。我习惯这么写,省几个时钟周期也是好的。
好了,移动平均滤波就聊到这儿。它简单、高效、实用,是电流采样噪声滤波的入门首选。但要注意,它也有局限性——对高频噪声效果一般,阶跃响应有延迟。下一节我们会聊另一种数字滤波算法,专门解决这些问题。