4、数字滤波器设计:从原理到实战
好,咱们进入第四章。这一章内容比较多,但都是硬核干货。数字滤波器,说白了就是给电流环信号“洗澡”——把噪声洗掉,把有用的信号留下来。我在FOC项目里踩过最多的坑,就是滤波器没选对,导致系统要么响应慢得像蜗牛,要么高频振荡停不下来。
今天我把常用的几种滤波器都捋一遍。你不需要全部精通,但得知道它们各自适合什么场景。
4.1 FIR滤波器:稳定但“费资源”
FIR(有限脉冲响应)滤波器,最大的优点就是线性相位。什么意思?就是信号经过它之后,波形形状不变,只是整体延迟了一下。这对电流环很重要——你不想让电流波形畸变吧?
FIR的设计思路很简单:用过去N个采样值的加权平均来得到当前输出。
// 一个简单的16阶FIR低通滤波器
#define FIR_TAPS 16
float fir_coeffs[FIR_TAPS] = {
0.0156, 0.0312, 0.0469, 0.0625,
0.0781, 0.0938, 0.1094, 0.1250,
0.1250, 0.1094, 0.0938, 0.0781,
0.0625, 0.0469, 0.0312, 0.0156
};
float fir_buffer[FIR_TAPS] = {0};
int fir_index = 0;
float fir_filter(float input) {
fir_buffer[fir_index] = input;
fir_index = (fir_index + 1) % FIR_TAPS;
float output = 0;
int buf_idx = fir_index;
for(int i = 0; i < FIR_TAPS; i++) {
buf_idx = (buf_idx + 1) % FIR_TAPS;
output += fir_coeffs[i] * fir_buffer[buf_idx];
}
return output;
}
你看,代码不复杂。但有个问题——阶数越高,延迟越大。我做过一个项目,用64阶FIR滤电流采样,结果延迟了将近1ms,电流环带宽直接掉到200Hz以下。后来我换成了IIR,才把带宽拉回来。
4.2 IIR滤波器:高效但“脾气大”
IIR(无限脉冲响应)滤波器,说白了就是带反馈的滤波器。它用更少的阶数就能达到和FIR一样的滤波效果,但代价是可能不稳定。
最常用的IIR就是二阶巴特沃斯低通滤波器。我一般直接用双线性变换法从模拟滤波器推导数字系数。
// 二阶IIR低通滤波器,截止频率500Hz,采样率10kHz
typedef struct {
float b0, b1, b2; // 前馈系数
float a1, a2; // 反馈系数
float x1, x2; // 历史输入
float y1, y2; // 历史输出
} iir_biquad_t;
void iir_init(iir_biquad_t *f, float fc, float fs) {
float w0 = 2 * M_PI * fc / fs;
float alpha = sin(w0) / (2 * 0.707); // Q=0.707,巴特沃斯
float cos_w0 = cos(w0);
float b0 = (1 - cos_w0) / 2;
float b1 = 1 - cos_w0;
float b2 = (1 - cos_w0) / 2;
float a0 = 1 + alpha;
float a1 = -2 * cos_w0;
float a2 = 1 - alpha;
// 归一化
f->b0 = b0 / a0;
f->b1 = b1 / a0;
f->b2 = b2 / a0;
f->a1 = a1 / a0;
f->a2 = a2 / a0;
f->x1 = f->x2 = 0;
f->y1 = f->y2 = 0;
}
float iir_process(iir_biquad_t *f, float x) {
float y = f->b0 * x + f->b1 * f->x1 + f->b2 * f->x2
- f->a1 * f->y1 - f->a2 * f->y2;
f->x2 = f->x1;
f->x1 = x;
f->y2 = f->y1;
f->y1 = y;
return y;
}
4.3 移动平均滤波器:简单粗暴但有效
移动平均滤波器,就是取最近N个采样值的平均值。它其实是一种特殊的FIR滤波器——所有系数都相等。
为什么单独拿出来说?因为它实现简单、计算量小、没有溢出风险。我经常用它来做ADC采样的预处理。
// 16点移动平均滤波器
#define MA_WINDOW 16
float ma_buffer[MA_WINDOW] = {0};
int ma_index = 0;
float ma_sum = 0;
float moving_average(float input) {
ma_sum -= ma_buffer[ma_index];
ma_buffer[ma_index] = input;
ma_sum += input;
ma_index = (ma_index + 1) % MA_WINDOW;
return ma_sum / MA_WINDOW;
}
你看,代码就这几行。但要注意:窗口越大,延迟越大,对高频噪声的抑制效果也越好。窗口大小N对应的延迟是(N-1)/2个采样周期。
| 窗口大小 | 延迟(采样周期) | 适用场景 |
|---|---|---|
| 4 | 1.5 | 高速电流环,带宽要求高 |
| 8 | 3.5 | 一般电流采样滤波 |
| 16 | 7.5 | 速度环或位置环预处理 |
| 32 | 15.5 | 离线分析或慢速系统 |
4.4 中值滤波器:对付“野点”的利器
中值滤波器不取平均,取中间值。它对脉冲噪声(比如偶尔出现的尖峰)有奇效。
我遇到过一种情况:电机运行时,电流传感器偶尔会受电磁干扰,产生一个比正常值大10倍的尖峰。如果用平均滤波,这个尖峰要拖好几个周期才能消掉。但中值滤波——直接把它当异常值扔掉。
// 5点中值滤波器
#define MED_WINDOW 5
float med_buffer[MED_WINDOW] = {0};
int med_index = 0;
// 简单的冒泡排序找中值(窗口小,够用)
float median_filter(float input) {
med_buffer[med_index] = input;
med_index = (med_index + 1) % MED_WINDOW;
// 复制并排序
float temp[MED_WINDOW];
for(int i = 0; i < MED_WINDOW; i++) temp[i] = med_buffer[i];
for(int i = 0; i < MED_WINDOW - 1; i++) {
for(int j = 0; j < MED_WINDOW - 1 - i; j++) {
if(temp[j] > temp[j+1]) {
float t = temp[j];
temp[j] = temp[j+1];
temp[j+1] = t;
}
}
}
return temp[MED_WINDOW / 2];
}
4.5 卡尔曼滤波器基础:最优估计
卡尔曼滤波器,听起来高大上,其实核心思想就一句话:用模型预测 + 测量修正。它能在有噪声的情况下,给出最优的状态估计。
在FOC里,我常用卡尔曼滤波来估计转子位置和速度。但说实话,电流环里直接用卡尔曼的并不多——因为计算量太大,而且电流环的采样频率通常很高(10kHz以上),卡尔曼的矩阵运算容易把MCU跑死。
不过,如果你做的是无传感器FOC,卡尔曼滤波几乎是绕不开的。这里给一个简化的一维卡尔曼滤波示例(用于平滑电流信号):
// 一维卡尔曼滤波器
typedef struct {
float x; // 状态估计值
float P; // 估计误差协方差
float Q; // 过程噪声协方差
float R; // 测量噪声协方差
} kalman_1d_t;
void kalman_init(kalman_1d_t *k, float init_x, float init_P) {
k->x = init_x;
k->P = init_P;
k->Q = 0.01; // 根据系统调参
k->R = 0.1; // 根据传感器噪声调参
}
float kalman_update(kalman_1d_t *k, float z) {
// 预测
float x_pred = k->x; // 状态预测(一维,假设恒定)
float P_pred = k->P + k->Q; // 协方差预测
// 更新
float K = P_pred / (P_pred + k->R); // 卡尔曼增益
k->x = x_pred + K * (z - x_pred); // 状态更新
k->P = (1 - K) * P_pred; // 协方差更新
return k->x;
}
4.6 陷波滤波器:精准打击特定频率
陷波滤波器,就是专门干掉某个特定频率的噪声。比如电机在某个转速下会产生500Hz的机械共振,你就可以用陷波滤波器把它滤掉,同时不影响其他频率的信号。
二阶陷波滤波器的传递函数长这样:
// 二阶陷波滤波器,中心频率500Hz,带宽50Hz,采样率10kHz
void notch_init(iir_biquad_t *f, float f0, float bw, float fs) {
float w0 = 2 * M_PI * f0 / fs;
float alpha = sin(w0) * sinh(log(2.0) / 2 * bw * w0 / sin(w0));
float cos_w0 = cos(w0);
float b0 = 1;
float b1 = -2 * cos_w0;
float b2 = 1;
float a0 = 1 + alpha;
float a1 = -2 * cos_w0;
float a2 = 1 - alpha;
f->b0 = b0 / a0;
f->b1 = b1 / a0;
f->b2 = b2 / a0;
f->a1 = a1 / a0;
f->a2 = a2 / a0;
}
嗯,这里要注意:带宽bw不能设得太窄,否则滤波器对参数变化非常敏感。我见过有人把带宽设成1Hz,结果温度一变,中心频率漂了2Hz,滤波器完全失效。
4.7 低通滤波器截止频率选择
这是整个滤波器设计里最“艺术”的部分。截止频率选高了,噪声滤不干净;选低了,电流环响应变慢,带宽下降。
我的经验公式是这样的:
- 电流环采样频率:通常10kHz~20kHz
- 电流环带宽目标:1kHz~2kHz
- 低通滤波器截止频率:带宽的3~5倍,即3kHz~10kHz
为什么是3~5倍?因为滤波器会引入相位延迟。截止频率越低,延迟越大。如果截止频率和带宽太接近,相位裕度会被吃掉,系统容易振荡。
| 电流环带宽目标 | 推荐低通截止频率 | 滤波器阶数 |
|---|---|---|
| 500 Hz | 1.5 ~ 2.5 kHz | 二阶 |
| 1 kHz | 3 ~ 5 kHz | 二阶 |
| 2 kHz | 6 ~ 10 kHz | 一阶或二阶 |
| 3 kHz | 9 ~ 15 kHz | 一阶 |
4.8 本章知识体系总览
说了这么多,我画了一张图帮你理清思路。这张图展示了不同滤波器的定位和选择逻辑:
这张图的核心逻辑是:先判断噪声类型,再选滤波器,最后用FIR或IIR实现,并调优参数。别一上来就套卡尔曼,也别所有场景都用移动平均。对症下药,才是工程师该干的事。