滤波器设计原理:FIR与IIR滤波器对比、极点零点分析、稳定性判断
好,咱们进入滤波器设计的核心环节。说实话,很多做嵌入式音频的朋友,一听到「滤波器设计」就头大。我当年也一样,觉得不就是个低通嘛,直接调库函数不就行了?直到有一次做车载音响项目,客户要求48dB/oct的陡降,我才发现——嗯,不懂原理真的会翻车。
今天咱们就掰开揉碎,把FIR和IIR这对「冤家」讲清楚。顺便聊聊极点零点,还有稳定性这个坑。
一、FIR与IIR:两种思路,两种命运
先问个问题:给你一串数字,你想把高频部分去掉,怎么办?
最简单的办法——取平均值。比如当前采样点和前面几个点求个平均,高频抖动就被抹平了。这就是FIR滤波器的雏形。它只依赖输入信号,不依赖过去的输出。
IIR呢?它会把过去的输出也拉进来一起算。说白了,就是有「反馈」的滤波器。
| 对比项 | FIR | IIR |
|---|---|---|
| 结构 | 无反馈(非递归) | 有反馈(递归) |
| 相位特性 | 可做到严格线性相位 | 非线性相位(有群延迟失真) |
| 计算量 | 较大(阶数高) | 较小(阶数低) |
| 稳定性 | 天生稳定 | 可能不稳定(需小心设计) |
| 典型应用 | 均衡器、抗混叠滤波 | 音调控制、低功耗场景 |
我的经验之谈:做图形均衡器(GEQ)时,我几乎只用FIR。为什么?因为人耳对相位失真非常敏感。你用IIR做10段均衡,每个频段都引入不同的相位偏移,声音听起来会「糊」。但如果你做的是低音增强或者简单的音调控制,IIR完全够用,而且省CPU。
二、极点零点分析:滤波器设计的「心电图」
滤波器本质上是个差分方程。把它做Z变换,就得到了传递函数H(z)。
H(z)的分子根叫「零点」,分母根叫「极点」。这两个东西,决定了滤波器的全部性格。
- 零点:让信号衰减的位置。零点在单位圆上,意味着那个频率被完全干掉。
- 极点:让信号增强的位置。极点越靠近单位圆,增益越大,但也越危险。
举个例子。你想做一个50Hz的陷波滤波器,滤掉电源噪声。那就在50Hz对应的角度上放一对零点。但光放零点不够,旁边还得放一对极点,稍微往原点方向缩一点,用来「拉平」其他频段。
// 一个简单的二阶IIR陷波器系数示例
// 采样率 44100Hz,陷波频率 50Hz,Q=30
float b0 = 0.9995f;
float b1 = -1.9978f;
float b2 = 0.9995f;
float a1 = -1.9978f;
float a2 = 0.9990f;
// 注意:a1和a2是反馈系数,决定了极点位置
一个小技巧:看极点零点的分布图,就能快速判断滤波器类型。所有零极点都在实轴上?那是经典的低通或高通。零极点成对出现在单位圆附近?那是带通或带阻。我曾经调试一个均衡器,发现高频段增益异常,一画极点图——好家伙,极点都快跑到单位圆外面去了。
三、稳定性判断:别让你的音响「自激」
稳定性这东西,说穿了就一句话:所有极点必须在单位圆内。
为什么?因为极点代表系统的「记忆」。如果极点在单位圆外,系统会不断放大过去的输出,最终输出爆炸——在音响里就是刺耳的啸叫,或者直接烧喇叭。
我遇到过最惨的一次:给客户做车载低音炮的DSP程序,IIR滤波器系数算错了,一个极点跑到了1.02的位置。上车一通电,低音炮直接「嗡——」地一声,然后冒烟了。嗯,从那以后,我每次写完IIR系数,第一件事就是检查极点位置。
避坑指南:千万别以为系数算出来就完事了。定点数实现时,量化误差会把极点「推」到单位圆外。我曾经在16位定点DSP上实现IIR,仿真时好好的,上板就自激。查了两天,发现是系数截断误差导致极点偏移。解决方案:用双精度算系数,再转成Q格式,并且留出安全余量(极点离单位圆至少0.01的距离)。
四、实际选型建议
说了这么多,到底什么时候用FIR,什么时候用IIR?我个人的习惯是这样的:
- CPU资源充裕,追求音质 → 用FIR。比如高端音响、录音棚设备。
- 资源紧张,实时性要求高 → 用IIR。比如蓝牙耳机、助听器。
- 需要陡峭的过渡带 → 用IIR(椭圆滤波器),或者用高阶FIR(但延迟大)。
- 需要线性相位 → 只能用FIR。IIR做不到。
你想想看,一个48阶FIR和一个6阶IIR,能达到差不多的幅频响应。但FIR的延迟是48个采样点,IIR只有几个采样点。在实时通话场景里,延迟就是生命。
反过来,如果你做的是离线处理或者播放类应用,延迟无所谓,那FIR的线性相位优势就体现出来了。
最后说一句:滤波器设计没有银弹。我见过有人用IIR硬做线性相位,结果搞出个100阶的IIR,又慢又不稳定。也见过有人用FIR做低功耗,结果电池撑不过两小时。选型之前,先想清楚你的约束条件——算力、延迟、功耗、音质,排个优先级,答案自然就有了。
好,这一章就到这里。下一章咱们聊聊具体的滤波器实现——怎么用C语言在嵌入式平台上把滤波器跑起来,包括定点数优化和循环缓冲区那些事儿。