说明¶
FIR — 有限脉冲响应滤波器
无反馈、始终稳定,可实现线性相位。通过窗函数法设计,卷积方式滤波。
算法原理¶
设计流程(窗函数法)¶
\[ \text{理想频率响应} \xrightarrow{\text{IFFT}} \text{理想脉冲响应(无限)} \xrightarrow{\text{加窗截断}} \text{FIR 系数} \;(h[0], \dots, h[M-1]) \]
Step 1 — 确定指标:截止频率 \(f_c\)、过渡带宽 \(\Delta f\)、阻带衰减
Step 2 — 生成理想脉冲响应(以低通为例):
\[ hd[n] = \frac{\sin(\omegac (n - M/2))}{\pi (n - M/2)}, \quad \omegac = 2\pi fc / f_s \]
高通/带通/带阻通过对低通原型做频谱搬移获得。
Step 3 — 加窗截断:\(h[n] = h_d[n] \cdot w[n]\)
-
矩形窗
主瓣最窄,旁瓣 -13 dB
过渡带最窄 -
Hanning
主瓣中等,旁瓣 -31 dB
通用场景 -
Hamming
主瓣中等,旁瓣 -41 dB
语音处理 -
Blackman
主瓣最宽,旁瓣 -57 dB
高衰减需求
滤波算法¶
批量处理(直接型):
\[ y[n] = \sum_{k=0}^{M-1} h[k] \cdot x[n - k], \quad n = 0, \dots, L-1 \]
本质是卷积运算,在 ESP32 上由 ESP-DSP 向量化加速。
实时流式处理
初始化时分配延迟线,每输入一个采样调用 tiny_fir_process_sample 即可得到滤波结果。适合逐采样点处理的嵌入式实时场景。
为什么 FIR 始终稳定?
只有零点,没有极点。传递函数 \(H(z) = \sum h[k] z^{-k}\) 所有极点都在原点。
线性相位条件
系数对称(\(h[k] = h[M-1-k]\))时 FIR 具有线性相位,适合需要保持波形相位的应用。
API 参考¶
滤波器设计¶
// 设计低通 FIR,返回设计的系数个数
int tiny_fir_design_lowpass(float *coeffs, int max_taps,
float cutoff_norm, tiny_fir_window_t window);
// 高通 / 带通 / 带阻类似
归一化频率:\(f{\text{norm}} = f / (f_s / 2)\),范围 [0, 1]}
批量滤波¶
// 一次性滤波整个信号(本质是卷积)
tiny_error_t tiny_fir_filter_f32(const float *input, int len,
const float *coeffs, int num_taps,
float *output);
实时滤波¶
typedef struct {
float *coefficients; // 滤波器系数
int num_taps; // 抽头数
float *delay_line; // 延迟线(内部状态)
// ...
} tiny_fir_state_t;
tiny_error_t tiny_fir_init(tiny_fir_state_t *state,
const float *coeffs, int num_taps);
tiny_error_t tiny_fir_deinit(tiny_fir_state_t *state);
float tiny_fir_process_sample(tiny_fir_state_t *state, float input);
void tiny_fir_reset(tiny_fir_state_t *state);
注意事项¶
抽头数越多,计算量越大
每个输出采样需 \(M\) 次乘加。ESP32-S3 @ 240 MHz 上 \(M=128\) 时约 0.5 ms。
过渡带 ≈ 窗函数主瓣宽度
窗函数决定过渡带宽和阻带衰减的 trade-off。要求陡峭过渡带 → 矩形窗(但旁瓣高);要求高衰减 → Blackman。
归一化频率
所有设计函数使用归一化频率 \([0, 1]\),其中 1 = Nyquist (\(f_s/2\))。避免混淆采样率和截止频率。