4、数字滤波算法:移动平均滤波、中值滤波、卡尔曼滤波入门
模拟滤波搞完了,咱们聊聊数字滤波。说实话,我入行头两年总觉得数字滤波是“软件的事”,硬件工程师把前端调理电路做好就行了。直到有一次做电机电流采样,模拟滤波器把相位搞得一塌糊涂,我才老老实实回来研究数字滤波。
数字滤波的好处很明显:不改硬件、参数可调、没有温漂。坏处也直接——占CPU、有延迟。怎么选?看场景。
4.1 移动平均滤波:最朴素的平滑器
移动平均滤波,说白了就是取最近N个采样值的平均数。你想想看,电流信号上叠加了白噪声,每个点抖一下,平均一下不就稳了?
公式很简单:
y[n] = (x[n] + x[n-1] + ... + x[n-N+1]) / N
我在项目里常用的是滑动窗口实现,不存全部数据,只维护一个累加和:
// 移动平均滤波 - 滑动窗口实现
#define WINDOW_SIZE 16
static uint16_t buffer[WINDOW_SIZE] = {0};
static uint8_t index = 0;
static uint32_t sum = 0;
uint16_t moving_average(uint16_t sample) {
sum -= buffer[index]; // 去掉最旧的值
sum += sample; // 加上最新的值
buffer[index] = sample; // 更新缓冲区
index = (index + 1) % WINDOW_SIZE;
return sum / WINDOW_SIZE; // 返回平均值
}
优缺点:
| 优点 | 缺点 |
|---|---|
| 实现简单,计算量小 | 对脉冲干扰敏感(一个坏点会污染N个输出) |
| 平滑效果好,噪声抑制与√N成正比 | 有固定延迟 = N/2个采样周期 |
| 适合白噪声环境 | 不能滤除与信号频率相近的噪声 |
什么时候用?电流采样做平均值计算、ADC前级有白噪声、对实时性要求不苛刻的场景。我做过一个电池管理系统,电流采样就用32点移动平均,效果很稳。
4.2 中值滤波:脉冲干扰的克星
移动平均最怕什么?脉冲干扰。比如电机启动瞬间的尖峰、继电器动作时的毛刺。一个异常大值,能把平均值拉偏好一阵子。
中值滤波的思路完全不同——取窗口内所有值排序后的中间值。你想想,不管脉冲多大,只要它只出现一次,排序后肯定被挤到边上,中间值不受影响。
// 中值滤波 - 窗口大小3(最常用)
uint16_t median_filter_3(uint16_t a, uint16_t b, uint16_t c) {
// 排序三个值,取中间
if (a > b) { uint16_t t = a; a = b; b = t; }
if (b > c) { uint16_t t = b; b = c; c = t; }
if (a > b) { uint16_t t = a; a = b; b = t; }
return b;
}
中值滤波 vs 移动平均:
| 特性 | 中值滤波 | 移动平均 |
|---|---|---|
| 脉冲抑制 | 优秀(完全消除单点脉冲) | 差(脉冲会扩散) |
| 平滑效果 | 一般(信号会有阶梯感) | 优秀(连续平滑) |
| 计算量 | 大(需要排序) | 小(只需加减法) |
| 延迟 | 窗口/2 | 窗口/2 |
我曾经在一个变频器项目里,电流采样总是莫名其妙跳变。查了半天,发现是IGBT开关带来的共模干扰。加上3点中值滤波后,问题直接消失。嗯,有时候解决问题就这么简单。
4.3 卡尔曼滤波入门:状态估计的瑞士军刀
卡尔曼滤波,听起来高大上,其实核心思想就一句话:用模型预测 + 用测量修正。
你想想看,电流信号不是随机乱跳的——它受电路方程约束。卡尔曼滤波就是利用这个约束,把“预测值”和“测量值”按置信度加权融合,得到最优估计。
对于电流采样这种一维系统,卡尔曼滤波可以简化成几个公式:
// 一维卡尔曼滤波(简化版)
typedef struct {
float x; // 状态估计值
float p; // 估计误差协方差
float q; // 过程噪声协方差
float r; // 测量噪声协方差
float k; // 卡尔曼增益
} kalman_1d_t;
void kalman_init(kalman_1d_t *kf, float init_x, float init_p, float q, float r) {
kf->x = init_x;
kf->p = init_p;
kf->q = q;
kf->r = r;
}
float kalman_update(kalman_1d_t *kf, float measurement) {
// 预测
kf->p = kf->p + kf->q;
// 更新
kf->k = kf->p / (kf->p + kf->r);
kf->x = kf->x + kf->k * (measurement - kf->x);
kf->p = (1 - kf->k) * kf->p;
return kf->x;
}
三种滤波器的选择指南:
| 场景 | 推荐算法 | 理由 |
|---|---|---|
| 白噪声环境,实时性要求高 | 移动平均 | 计算量最小,延迟可控 |
| 有脉冲干扰,信号变化慢 | 中值滤波 | 彻底消除脉冲,实现简单 |
| 需要高精度估计,有模型可用 | 卡尔曼滤波 | 最优估计,自适应能力强 |
| 资源极度受限(8位MCU) | 移动平均或中值滤波 | 卡尔曼的浮点运算扛不住 |
下面这张图帮你理清三种滤波器的核心逻辑:
4.4 实战建议:怎么搭滤波链路
实际项目中,我很少只用一种滤波器。更常见的做法是组合使用:
- 第一级:中值滤波(窗口3) — 干掉脉冲干扰,保护后续算法不被异常值污染
- 第二级:移动平均(窗口8-16) — 平滑白噪声,输出稳定的电流值
- 第三级(可选):卡尔曼滤波 — 如果需要对电流做动态估计(比如预测下一时刻的电流),加上卡尔曼做状态跟踪
最后说一句:别迷信卡尔曼滤波。如果你的MCU是8位、主频只有几MHz,老老实实用移动平均或中值滤波。卡尔曼的浮点运算和矩阵乘法,在这种平台上跑起来,CPU占用率直接拉满,别的活就别干了。