1. 项目背景与问题定位
最近在做一个工业传感器数据采集项目时,发现原始信号中总是混入50Hz工频干扰和低频环境噪声。这些干扰信号幅度虽然不大,但严重影响了我们对有效信号的识别。用示波器观察原始信号,能看到明显的基线漂移和周期性干扰。
这种情况在模拟信号处理中很常见——传感器信号经过长线传输后,很容易耦合进各种噪声。特别是当信号频率较低时(比如我们监测的振动信号在10-200Hz范围内),传统的RC无源滤波器很难在保证通带特性的同时有效抑制低频干扰。
经过多次尝试,最终决定在STM32上实现数字FIR高通滤波器。相比模拟滤波器,数字方案有以下优势:
- 参数可编程:截止频率、阶数等参数可随时调整
- 稳定性高:不受温度、器件老化等因素影响
- 灵活性强:同一硬件平台可实现多种滤波特性
2. 滤波器设计与系数生成
2.1 FIR滤波器基本原理
FIR(有限脉冲响应)滤波器的输出仅取决于有限个过去的输入值,其数学表达式为:
y[n] = Σ h[k]·x[n-k]
其中h[k]是滤波器系数,k=0,1,...,N-1
FIR滤波器的主要特点:
- 绝对稳定(因为无反馈)
- 可以实现严格的线性相位
- 设计方法成熟(窗函数法、频率采样法等)
2.2 窗函数法设计步骤
我们采用最常用的窗函数法设计高通FIR滤波器,具体步骤:
-
确定理想高通滤波器的频率响应:
Hd(e^jω) = 0, |ω|≤ωc
Hd(e^jω) = 1, ωc<|ω|≤π -
通过逆傅里叶变换得到理想脉冲响应:
hd[n] = (1/2π) ∫ Hd(e^jω)e^jωn dω -
加窗截断无限长脉冲响应:
h[n] = hd[n]·w[n], n=-M,...,M
其中w[n]是窗函数
2.3 MATLAB系数生成实战
在实际工程中,我们直接使用MATLAB的fir1函数生成滤波器系数。以下是详细参数说明:
matlab复制order = 20; % 滤波器阶数
cutoff = 0.4; % 归一化截止频率(0-1对应0-fs/2)
h = fir1(order, cutoff, 'high', hamming(order+1));
关键参数选择经验:
- 阶数选择:20阶在STM32F103上实测处理时间约50μs(72MHz主频),能满足1kHz采样率需求
- 截止频率:0.4对应实际200Hz(当采样率1kHz时),能有效抑制50Hz工频干扰
- 窗函数:汉明窗在阻带衰减和过渡带宽度间取得较好平衡
注意:MATLAB的fir1函数生成的系数已经包含了窗函数处理,不需要额外操作
3. STM32实现方案
3.1 环形缓冲区设计
FIR滤波需要维护一个包含历史数据的滑动窗口,我们采用环形缓冲区实现:
c复制#define FILTER_ORDER 20
float buffer[FILTER_ORDER+1]; // 缓冲区大小=阶数+1
uint8_t buf_index = 0; // 当前写入位置
void push_sample(float new_sample) {
buffer[buf_index] = new_sample;
buf_index = (buf_index + 1) % (FILTER_ORDER+1);
}
设计要点:
- 缓冲区长度必须为FILTER_ORDER+1,因为需要保存当前时刻及之前N个样本
- 使用无符号整型作为索引,利用自动回零特性简化边界判断
- 新数据直接覆盖最老数据,避免数据搬移开销
3.2 卷积运算实现
FIR滤波的核心是卷积运算,下面是优化后的实现:
c复制float fir_filter(float *coeffs) {
float output = 0.0f;
uint8_t index = buf_index; // 从最新样本开始
for(int i=0; i<=FILTER_ORDER; i++){
if(index == 0) index = FILTER_ORDER; // 替代取模运算
output += coeffs[i] * buffer[index];
index--;
}
return output;
}
性能优化技巧:
- 倒序访问缓冲区,避免每次计算模运算
- 使用if判断代替%,实测速度提升20%
- 系数和样本使用float类型,保证计算精度
3.3 CMSIS-DSP库加速
对于性能要求高的场景,可以使用ARM的DSP库:
c复制#include "arm_math.h"
#define BLOCK_SIZE 32
float fir_state[FILTER_ORDER + BLOCK_SIZE -1];
arm_fir_instance_f32 fir;
void filter_init() {
arm_fir_init_f32(&fir, FILTER_ORDER+1, hamming_coeffs, fir_state, BLOCK_SIZE);
}
void process_block(float *input, float *output) {
arm_fir_f32(&fir, input, output, BLOCK_SIZE);
}
使用注意事项:
- state数组大小必须≥FILTER_ORDER + BLOCK_SIZE -1
- 批处理模式效率远高于单点处理,建议BLOCK_SIZE设为4的倍数
- 初始化只需一次,后续反复调用process_block即可
4. 定点数优化技巧
在资源受限的MCU(如STM32F103)上,浮点运算效率较低。可以采用Q15定点数格式优化:
4.1 系数转换
matlab复制q15_coeffs = int16(h * 32767); % MATLAB系数转换
对应的C代码:
c复制#include "arm_math.h"
q15_t coeffs_q15[FILTER_ORDER+1];
arm_fir_instance_q15 fir_q15;
q15_t state_q15[FILTER_ORDER + BLOCK_SIZE -1];
void filter_init_q15() {
arm_fir_init_q15(&fir_q15, FILTER_ORDER+1, coeffs_q15, state_q15, BLOCK_SIZE);
}
4.2 使用注意事项
- 输入数据也需要转换为Q15格式(乘以32767)
- 输出结果需要除以32767得到实际值
- 中间结果可能溢出,需确保信号幅度不超过1.0
- 建议先用MATLAB验证定点化后的频率响应
5. 实际应用与调试
5.1 主程序流程
c复制int main() {
// 初始化
filter_init();
adc_init();
while(1) {
float raw = read_adc();
push_sample(raw);
float filtered = fir_filter(coeffs);
// 输出原始信号和滤波后信号对比
printf("%.3f\t%.3f\n", raw, filtered);
delay_ms(1); // 控制采样率1kHz
}
}
5.2 性能实测数据
| MCU型号 | 滤波阶数 | 处理时间(浮点) | 处理时间(Q15) |
|---|---|---|---|
| STM32F103 | 20阶 | 52μs | 18μs |
| STM32F407 | 50阶 | 28μs | 9μs |
| STM32H743 | 100阶 | 15μs | 5μs |
5.3 常见问题排查
-
滤波器无效果:
- 检查截止频率是否设置合理
- 确认系数加载正确
- 验证缓冲区管理逻辑
-
输出信号失真:
- 检查输入信号幅度是否超出范围
- 验证定点数转换过程
- 降低阶数测试
-
处理速度不达标:
- 启用编译器优化(-O2)
- 使用DSP库批处理模式
- 考虑降低阶数或切换MCU
6. 进阶优化方向
- 自适应滤波:根据信号特性动态调整滤波器参数
- 多级滤波:级联多个低阶滤波器替代单个高阶滤波器
- 频率分析:结合FFT实现动态参数调整
- 硬件加速:利用STM32的硬件FPU和DSP扩展指令
我在实际项目中发现,对于振动信号分析,20-30阶的FIR高通滤波器配合200Hz左右的截止频率,能在处理速度和滤波效果间取得很好平衡。当需要更高性能时,切换到Q15定点数运算可以带来3倍左右的性能提升,但要注意信号幅度的控制。
