1. 从一段真实经历说起:为什么 FIR 设计没那么简单
我接过不少数字信号处理相关的任务,设计 FIR 滤波器算是其中出现频率最高的需求之一。很多人一开始觉得这事简单,MATLAB 里一行fir1或者 Python 里一句signal.firwin就完事了。但实际做起来,尤其是把滤波器真正落到工程里、和系统其他模块对接的时候,各种问题就全冒出来了:通带纹波超标、阻带衰减不够、群延迟导致时序对不上、定点化之后性能劣化……每个坑都够你折腾半天的。
这篇文章想把“设计 FIR 滤波器”这件事完整地拆开讲一遍。目标不是教你怎么抄代码,而是让你理解设计思路、参数选择背后的原理,以及在工程中真正落地时需要盯住的细节。适合刚接触 FIR 设计、被各种概念绕晕的同学,也适合已经会用工具但总感觉“差点意思”的工程师。
先交代一下 FIR 滤波器能做什么。简单说,它是一种数字滤波器,输入输出关系只取决于当前和过去的输入样本,因为它的单位脉冲响应是有限长的,所以叫“有限长单位脉冲响应”,英文 FIR,Finite Impulse Response。它能完成的工作包括:从信号里挑出你想要的频率成分、滤掉噪声干扰、对信号做整形和预加重,再复杂一点还能做匹配滤波、微分器、希尔伯特变换这类特殊用途。它最大的优点,是可以在整个频带范围内做到严格线性相位——这意味着信号经过滤波器后,不同频率成分的延迟保持一致,波形除了幅度变化之外不会发生相位畸变。这一点在通信系统、音频处理、生物医学信号分析等对相位敏感的场合是致命的。
这篇文章会从 FIR 和 IIR 的选型逻辑讲起,逐步拆解设计指标怎么定、窗函数怎么选、阶数怎么估算,给出可以直接参考的 MATLAB 和 Python 实现代码。最后用一套完整的低通滤波器设计案例,把从指标到实现、再到验证排查的全流程串起来。你在实操中遇到的大多数问题,应该都能在文章里找到对应的解决方案。
2. 设计之前的核心抉择:FIR 还是 IIR,线性相位到底有多重要
2.1 FIR 与 IIR 的取舍逻辑
很多人一上来就问“哪个滤波器好”,这个问题本身就不成立。FIR 和 IIR 各有完全不同的性格,选谁取决于你的应用场景。IIR 滤波器的典型特征是传递函数有极点,因此可以用很低的阶数实现非常陡峭的过渡带,计算量也小。但它的相位响应是非线性的,频率越高,信号各成分之间的相位关系会被扭曲。FIR 滤波器则恰好相反:没有极点,全部是零点,同样的指标往往需要高很多的阶数,但换来的是可以设计成严格线性相位。
我在实际项目里的大致选择标准是这样的:如果系统对相位不敏感,比如只是做能量检测、电平测量,或者信号本身是宽带随机噪声,IIR 往往更划算,省 FPGA 资源、省运算时间。但如果信号是语音、心电、振动这类需要保真的波形,或者后级要做相干解调、相关检测,那就老老实实选 FIR,线性相位在这一类系统里几乎等于生命线。
举一个我实际处理过的例子:一套心电信号预处理系统,目标是从工频干扰和肌电噪声中恢复出干净的 PQRST 波形。一开始图省事用了四阶的巴特沃斯带通滤波器,波形放大之后能看到明显的相位畸变——QRS 波的峰值位置产生了偏移。心电诊断里,ST 段的抬升幅度、QT 间期的长度都是直接关系到临床判断的指标,波形相位一旦变了,这些测量结果全都不准。后来换成等阶数的 FIR 滤波器之后,波形形状终于和原始信号吻合,各种间期测量也变得稳定了。从那以后,凡是涉及医学信号的项目,我基本无脑上线性相位 FIR。
2.2 线性相位的本质和四种 FIR 类型
FIR 实现线性相位的原理并不复杂,核心是让滤波器系数满足对称性。一个长度为 N 的 FIR 滤波器,如果系数满足:
h[n] = h[N-1-n](偶对称),或者 h[n] = -h[N-1-n](奇对称)
那么它的相位响应就是频率的线性函数,群延迟恒定等于 (N-1)/2 个采样周期。这个恒定群延迟就是 FIR 最宝贵的财产:不管你输入的信号是什么样,滤波后各个频率成分抵达的时间完全一致。
线性相位 FIR 按照长度奇偶和对称类型,可以分成四种类型:
- I 型:N 为奇数,系数偶对称,最通用,可以设计任意形式的滤波器,包括低通、高通、带通、带阻。
- II 型:N 为偶数,系数偶对称,频率响应在 π(即奈奎斯特频率)处必然为零,所以不能用来设计高通或带阻,只能做低通和带通。
- III 型:N 为奇数,系数奇对称,频率响应在 0 和 π 处都为零,最适合做希尔伯特变换器和微分器。
- IV 型:N 为偶数,系数奇对称,频率响应在 0 处为零,适合做希尔伯特变换器和微分器,但不能做普通低通。
这是新手最常忽略的约束。我见过有人用fir1设计了一个偶数阶高通滤波器,仿真结果怎么调都不对,后来发现是忘了 II 型 FIR 在高频端有天然的系统零点。所以在定指标之前先想清楚:你是要低通还是高通,滤波器长度打算取奇数还是偶数,这两点直接决定了可行性边界。
2.3 工程场景下的选型心法
我的建议是,不必迷信“FIR 一定比 IIR 好”这种论调。音频混响器、均衡器这类创意类处理,很多时候非线性相位的“染色”反而成了特色,IIR 是标配。通信基带的成形滤波器、匹配滤波器,对相位一致性和码间串扰有苛刻要求,必须用 FIR。低功耗的嵌入式传感器节点,计算资源和内存都紧张,IIR 往往是唯一现实的选择。
一句话总结:先看信号的用途,再谈滤波器的结构。相位敏感选 FIR,资源紧张选 IIR,中间地带根据你的实现平台和指标要求权衡。
3. 设计指标的制定和参数估算:这一步错了,后面全白干
3.1 频域指标到底在描述什么
设计 FIR 滤波器之前,必须先把一组明确的技术指标写下来。这些指标不是随便拍的,它们直接决定滤波器的阶数和性能边界。最重要的四个参数是:
- 通带截止频率 ωp:在这个频率以下,信号被保留,幅度误差控制在通带纹波范围内。
- 阻带截止频率 ωs:在这个频率以上,信号被衰减,衰减量至少达到阻带衰减指标。
- 通带纹波 δ1:通带内允许的幅度波动。用 dB 表示时通常很小,0.1dB 到 1dB 是常见取值。
- 阻带最小衰减 As:阻带内允许的最大幅度,典型指标是 40dB、60dB,甚至更高。
这几个参数的关系是:通带到阻带的过渡带越窄,滤波器阶数就需要越高;纹波要求越严格,阶数也会相应上升。工程上有一句经验之谈——每减少过渡带宽度一半,需要的滤波器阶数大约翻倍。所以指标定得越“苛刻”,你在硬件上付出的资源代价就越大。设计好的指标,是在性能需求和成本之间找到平衡点。
3.2 阶数估算的经验公式与查表法
在选窗函数设计法时,滤波器阶数 N 有一个非常实用的经验估算公式。经典教材里常引用的公式是:
N ≈ (As - 7.95) / (2.285 × Δω)
其中 Δω 是过渡带宽度(归一化频率),As 是阻带衰减的 dB 值。这个公式来源于凯泽窗的逼近特性,对大多数窗函数设计都能给出合理的起点。举个例子,要求阻带衰减 60dB,采样率 16kHz,通带截止 3.4kHz,阻带截止 4.6kHz,那过渡带宽度就是 (4.6-3.4)/16 = 0.075(归一化到采样频率)。代入公式:
N ≈ (60 - 7.95) / (2.285 × 0.075) ≈ 303.5,取偶数为 304。
如果要求更高的阻带衰减 80dB,同样过渡带下:
N ≈ (80 - 7.95) / (2.285 × 0.075) ≈ 420.2,取 422。
能看到规律吗?阻带衰减要求提高 20dB,阶数增加了 120 多阶。所以我在项目前期最常做的事情之一,就是先拿这个公式估算一下,看看阶数是否超出了系统的计算预算。如果在 FPGA 上跑,这会直接决定你要用多少 DSP 乘法器资源;如果在 DSP 上跑,它决定你要分配多少程序周期给滤波函数。
3.3 窗函数选择的逻辑:没有免费的午餐
窗函数设计法的本质,是将理想滤波器的无限长脉冲响应截断并加权。理想低通的脉冲响应是 sinc 函数,它是无限长的,直接截断会产生吉布斯效应——阻带纹波和通带振铃。加窗就是为了抑制这种截断带来的泄漏。
选窗函数的核心权衡是:主瓣越宽,过渡带越宽;旁瓣越低,阻带衰减越大,但没办法两者兼得。实际工程里常用的窗函数和它们的关键特性如下:
- 矩形窗:旁瓣衰减仅 -21dB,过渡带最窄,但效果太差,几乎不用于实际滤波器设计。
- 汉宁窗:旁瓣衰减 -44dB,过渡带宽度约为 3.1Δf(Δf 为频率分辨率),适合过渡带要求不高、衰减 40~50dB 的场景。
- 海明窗:旁瓣衰减 -53dB,过渡带宽度约为 3.3Δf,和汉宁窗属于同一梯队,但阻带衰减略好。
- 布莱克曼窗:旁瓣衰减 -74dB,过渡带宽度约为 5.5Δf,适合要求高衰减、过渡带宽裕量较大的场景。
- 凯泽窗:通过参数 β 连续调节旁瓣衰减,β 越大衰减越大但过渡带越宽,是工程上最灵活的选择。
凯泽窗的 β 参数有近似经验公式:β = 0.1102(As - 8.7),适用于 As > 50dB 的情况。如果 As 在 30 到 50dB 之间,β 的公式略有不同,会在下文实际案例中给出。
值得注意的是,窗函数的阻带衰减是以牺牲过渡带为代价换来的。很多人指标里同时要求过渡带极窄、阻带衰减极高,这种情况下窗函数法会非常吃力,你可能要转向最优设计法(等纹波设计,Parks-McClellan 算法),后面会有专门的讨论。
4. 三种主流设计方法拆解:窗函数法、频率采样法与最优设计
4.1 窗函数法:最直观,也最容易被坑
窗函数法的流程可以概括为五步:
- 根据指标确定理想滤波器的幅度响应和截止频率,通常是理想低通的边界频率取通带和阻带截止频率的中点。
- 对理想频率响应做逆离散时间傅里叶变换,得到无限长脉冲响应,对低通就是一个 sinc 序列。
- 根据阻带衰减要求选择合适的窗函数。
- 根据过渡带宽度要求估算滤波器长度 N。
- 将理想脉冲响应与窗函数相乘,得到最终 FIR 系数。
用 MATLAB 实现时,代码非常简洁:
fc = 3800; % 截止频率 3.8kHz fs = 16000; % 采样率 16kHz N = 304; % 阶数,由估算公式确定 win = kaiser(N+1, 4.82); % 凯泽窗,beta 值根据阻带衰减确定 b = fir1(N, fc/(fs/2), 'low', win); freqz(b, 1, 1024, fs); % 绘制频率响应验证Python 版本用scipy.signal也一样方便:
import numpy as np from scipy.signal import firwin, freqz fs = 16000 fc = 3800 N = 304 beta = 4.82 taps = firwin(N+1, fc, fs=fs, window=('kaiser', beta)) freqz(taps, fs=fs)这段代码里需要重点理解的是fc/(fs/2)这个归一化操作。MATLAB 和 SciPy 里要求截止频率是相对于奈奎斯特频率的归一化值,奈奎斯特频率是采样率的一半。无数人忘掉这个归一化,直接把频率塞进去,结果滤波器行为完全不对。我见过有人在 Python 里用firwin(65, 2000, fs=10000)设计通带 2kHz 的滤波器,看起来正常,但切换到 MATLAB 时直接把2000代入归一化参数,滤波器通带变成了 10kHz,完全错乱。设计前务必想清楚你用的 API 需要什么单位。
4.2 频率采样法:灵活但有代价
频率采样法的思路是另一种方向:直接在频域指定你要的幅度响应,在 0 到 π 之间均匀取 M 个点,把这 M 个点的频域值做逆 DFT,得到时域系数。它的实现简单,MATLAB 的fir2就是干这个的。在我处理的某些特殊需求场景里,比如设计一个斜率不是标准低通曲线的滤波器,频率采样法就非常灵活。
但频率采样法有一个明显的问题:你只在 M 个离散点上约束了响应,这些采样点之间的响应是完全不受控的。如果过渡带的采样点选择不合适,频点之间的纹波可能远超预期。要改善就需要增大采样点数,但又会导致滤波器长度变长、实现成本上升。
我很少把频率采样法作为首选,除非我需要的是某种特殊形状的响应,而且愿意在事后花时间反复调整频点来压纹波。对标准低通、高通、带通、带阻这几种常规需求,窗函数法和最优设计已经足够好,没必要用频率采样法给自己找麻烦。
4.3 最优设计:等纹波逼近的工程级解法
最优设计法在工程中通常指 Parks-McClellan 算法,它的核心思想是:在通带和阻带内让误差的峰值均匀分布(等纹波),并在给定阶数的条件下让峰值误差最小化。它是真正意义上的“最优”,因为它在数学上被证明是在最大误差最小准则下的最优解。
在相同阶数下,等纹波设计能提供比窗函数法更低的通带纹波和更高的阻带衰减;反过来,在相同性能要求下,等纹波设计需要的阶数更少。这是它的巨大优势。但它也有自身的考量项:等纹波设计把误差均匀分布在通带和阻带,使得滤波器的群延迟响应在某些极端设计下可能出现异常,尤其当阶数估算不准确时会明显看到通带内的吉布斯效应。
MATLAB 的firpm和 Python 的remez都实现了这一算法。以刚才的指标为例,在 Python 中这样实现:
from scipy.signal import remez fs = 16000 pass_zero = 3400 stop_begin = 4600 N = 303 # 等纹波设计通常可以比窗函数法略低 taps = remez(N, [0, pass_zero, stop_begin, fs/2], [1, 0], fs=fs)注意这里传的边缘频率和期望增益的配置方式:第一组边界 [0, pass_zero] 对应增益 1,第二组边界 [stop_begin, fs/2] 对应增益 0。参数顺序弄错了,滤波器的通带阻带就完全反了。我在给团队的同事 review 代码时看到过太多次这种错误。
5. 实操落地全流程:一套低通滤波器从指标到验证
5.1 完整的案例指标设定
下面我把刚才提到的各个碎片串成一个完整案例。假设需要设计一个音频前置去噪低通滤波器:
- 采样率 fs = 16kHz
- 通带截止频率 fp = 3.4kHz
- 阻带起始频率 fst = 4.6kHz
- 通带最大纹波 Rp ≤ 1dB
- 阻带最小衰减 As ≥ 60dB
通带 1dB 纹波换算成线性幅度大约是 1.12 倍左右,阻带 60dB 对应幅度 0.001。这个指标组合在语音预处理里非常典型——既要保留语音的主要频段(3.4kHz 内),又要滤掉带外噪声和可能的超声干扰。
先按凯泽窗估算阶数。过渡带宽度 Δf = 4.6 - 3.4 = 1.2kHz,归一化到采样率是 1.2/16 = 0.075。凯泽 β 参数:
β = 0.1102 × (60 - 8.7) = 5.65
阶数估算:
N ≈ (60 - 7.95) / (2.285 × 0.075) ≈ 303.8
取偶数,N = 304,系数长度为 305。
5.2 两种方法的实现对照
窗函数法的关键代码上面已经给出。我下面把等纹波设计也完整写出来,方便直接对照。
Python 等纹波设计实现:
from scipy.signal import remez, freqz import numpy as np fs = 16000 N = 303 # 等纹波设计可以尝试更低的阶数 bands = [0, 3400, 4600, 8000] desired = [1, 0] taps = remez(N, bands, desired, fs=fs) w, h = freqz(taps, 1, worN=2048, fs=fs) mag_db = 20 * np.log10(np.abs(h) + 1e-12)跑出来看一下通带纹波和阻带衰减:如果阻带衰减没到 60dB,可以适当增加阶数;如果超过指标很多,说明阶数有余量,可以降下来节省资源。这是设计闭环里最常规的迭代操作。
MATLAB 等纹波设计对照:
fs = 16000; fpass = 3400; fstop = 4600; dev = [0.028 0.001]; % 通带 1dB 纹波约等于线性幅度 0.028,阻带统一 0.001 [n, fo, ao, w] = firpmord([fpass fstop], [1 0], dev, fs); b = firpm(n, fo, ao, w); freqz(b, 1, 1024, fs);firpmord是 MATLAB 里做阶数估计的函数,它根据你给出的边缘频率和允许偏差自动计算最小阶数。它能省掉你手动估算阶数的步骤,但底层使用的不是之前那个解析公式,而是工程数值优化的结果。
5.3 时域验证:不要只看频谱图
很多人在滤波器设计阶段只看频率响应就下结论,这在纯理论验证里没问题,但放到工程系统里往往不够充分。我习惯至少做三件事的验证:
第一,给滤波器输入一个单位脉冲,观察输出是否为有限长度序列,确认 FIR 结构正确。第二,给滤波器输入一个包含多个频率分量的合成信号,例如 1kHz、4kHz、7kHz 的正弦叠加,在时域观察 1kHz 分量是否完整保留、4kHz 以上的分量是否被有效衰减。第三,检查群延迟补偿。
群延迟对 FIR 是恒定值 (N-1)/2 个采样点,对于 304 阶滤波器来说,延迟是 152 个采样周期。在 16kHz 采样率下,这相当于 9.5ms 延迟。如果你的系统里还有其他通路(比如高通旁路、原始信号直通),一定要把滤波器的延迟考虑进去,否则两个通路叠加时会看到明显的相位对不齐。
下面这段代码演示了如何用 Python 构造多频合成信号并验证滤波效果:
import numpy as np from scipy.signal import lfilter fs = 16000 t = np.arange(0, 0.1, 1/fs) x = np.sin(2*np.pi*1000*t) + 0.5*np.sin(2*np.pi*4000*t) + 0.3*np.sin(2*np.pi*7000*t) y = lfilter(taps, 1, x) # 计算滤波器延迟并对齐比较 delay = (len(taps) - 1) // 2 aligned_x = x[:len(y)-delay] aligned_y = y[delay:]对比aligned_x和aligned_y就能直观看到:1kHz 的正弦波几乎不变,4kHz 和 7kHz 分量被大幅削弱。如果这个系统后级还有特征提取的算法,请务必用对齐后的数据,不要用原始滤波输出直接计算,否则延迟引入的时间偏移可能直接毁掉你的特征时序。
5.4 定点化实现和资源估算
在硬件上实现 FIR 滤波器时,浮点运算通常是不现实的,至少是不划算的。FPGA 和 DSP 上更多采用定点实现。定点化有两个核心问题需要处理:一是系数量化,二是运算过程的数据位宽。
系数量化的影响可以用一个简单的实验来体会。比如系数本来是浮点小数,当你用 16 位定点数表示时,每个系数会产生约 1.5e-5 级别的量化误差。这种误差会直接抬高阻带的地板,让本来能到 -80dB 的阻带衰减退化到 -70dB 左右。如果系数位宽只有 12 位,阻带衰减可能退化到 -60dB 以下。所以要求高阻带衰减的系统,系数量化位宽必须留够,这是我在 FPGA 设计中的一条硬经验。
下面是一段定点化前移操作的示例。滤波器系数量化到 16 位整数时,先归一化到 [-1, 1),乘 2^15 后做四舍五入取整:
import numpy as np h = taps / np.sum(taps) # 归一化,让直流增益为 1 q16 = np.round(h * (2**15))运算过程也需要考虑中间结果的溢出。对 N 阶 FIR,输入信号 x[n] 乘以系数并累加 N+1 次,中间累加器需要预留额外的位宽来避免溢出。比如输入是 16 位,系数是 16 位,乘积是 32 位,累加 N 项时需要额外增加 log2(N) 位来容纳进位,用 32+ceil(log2(N)) 位做累加器是比较稳妥的做法。对 304 阶滤波器,log2(304) ≈ 8.25,累加器至少 40 位。
6. 常见问题与调试技巧:踩过的坑集中复盘
6.1 截止频率设对了但行为不对:归一化单位问题
这是最高频的入门问题。同一个滤波器,你可以在 MATLAB 看到好的结果,但把系数搬到 C 语言的定点实现里,行为却完全不对。原因十有八九出在归一化单位换算上。设计工具的截止频率往往需要归一化到奈奎斯特频率或者采样频率,而 C 语言的滤波实现常常用弧度频率或者直接使用数字频率。我的建议是,在项目开始时就在文档里明确写出所有频率参数的参考系,并且每个实现的代码里用常量统一转换,比如:
#define FS 16000 #define FC_NORM 3800.0 / (FS / 2) // 归一化到奈奎斯特频率另外千万记住,数字角频率是模拟频率除以采样率再乘以 2π,不是直接除以采样频率。搞反这个换算关系,滤波器设计和实现的效果必然天差地别。
6.2 纹波超标和阻带衰减不够:阶数与窗函数的重新权衡
遇到阻带衰减不够的情况,第一反应不要是盲目增加阶数。先确认衰减指标和窗函数能力是否匹配。矩形窗只能做到 -21dB 的旁瓣,汉宁窗 -44dB,海明窗 -53dB,布莱克曼窗 -74dB。如果你选的窗函数本身只能做到 -50dB,那你再怎么增加阶数也没办法把阻带压到 -70dB。换窗函数比加阶数更快。如果换了凯泽窗还是不行,那才考虑增加阶数来缩小过渡带、间接改善阻带性能。
等纹波设计里阻带不足,则大概率是阶数估计偏低了。用firpmord或自己手算的公式重新核算一下,给阶数留一点裕量,通常在估算值的 1.1 到 1.2 倍之间比较稳妥。
6.3 定点化后性能劣化严重:系数精度和累加器位宽
FPGA 项目里经典的疑难杂症:浮点仿真一切正常,一到板级调试,频谱图就出现明显的噪声地板上浮。这个现象基本可以定位到两点:要么系数位宽不够,要么累加器截断太早。系数位宽每减少一位,阻带衰减大约劣化 6dB,这是一个非常直观的经验规律。累加器如果在每次乘法后立即截断到与输入同宽,截断噪声就会被累积放大,正确的做法是只在最后一级输出时做一次舍入,中间全程保持扩展精度。
6.4 群延迟导致系统时序混乱:延迟补偿的三种方案
群延迟导致的时序问题在实时系统里最常见。典型的场景是:滤波器输出和另一路直通信号做叠加或比较,结果出现时间上的错位。这个问题有三个常规解法:
- 给另一路信号也加相同长度的延迟,让两路信号的延迟对齐。
- 调整整个系统的时间基准,把滤波器的延迟计入总的链路预算。
- 如果允许相位非线性,改用最小相位 FIR,但这样会丧失线性相位特性,对某些应用不可接受。
第一种方案在绝大多数工程里最实用,实现成本也最低。
6.5 一个快速问题速查表
整理一份我在项目里经常对照的排查表,遇到问题可以直接按表格定位:
| 现象 | 可能原因 | 解决对策 |
|---|---|---|
| 阻带衰减明显低于设计值 | 窗函数旁瓣能力不足 | 换用旁瓣更低的窗,如布莱克曼或凯泽 |
| 通带边缘出现剧烈振铃 | 过渡带设置过窄,阶数不够 | 增加阶数或放宽过渡带 |
| 定点输出噪声明显增大 | 系数位宽不足、累加器截断过早 | 增加系数位宽,保留中间累加精度 |
| 截止频率和预期偏差大 | 归一化单位换算错误 | 统一频率参考系,写注释说明 |
| 高频完全消失但低频也有畸变 | IIR 型 FIR 类型选错 | 检查滤波器类型,偶数阶不能做高通 |
| 时域滤波后信号与参考时序歪斜 | 未补偿群延迟 | 对参考通路补等量延迟 |
7. 一点个人经验和最终建议
做完这么多 FIR 设计的项目,我最大的体会是:设计工具只是帮你算出系数的“计算器”,真正决定滤波器质量的,是你在设计之前对指标的理解、对实现平台的考虑,以及设计之后对系统级问题的验证。
几个我觉得特别值得养成的习惯:第一,每一个滤波器设计任务开始前,先花 15 分钟把指标的物理意义想清楚,确认通带、阻带、过渡带的边界值都能对应到你系统里的实际信号特性。第二,验证时不要只用一个正弦波,一定要用接近真实信号的场景来测试。第三,定点化从一开始就考虑,不要等浮点原型做完再回头补,那时候返工成本最高。
如果后续想继续深入,你可以接着研究多速率信号处理中的滤波器组设计,以及自适应 FIR 滤波器的 LMS 算法实现。掌握了这些,FIR 滤波器就会从一个个孤立的系数集合,变成你系统设计中随手可用的基本功。