07 IIR实验
Infinite Impulse Response 无限冲激响应
up的视频讲解: https://space.bilibili.com/1455872040
up视频里的实验代码: 👣IIR实验代码
1 🌼 IIR 简介
1 把上次的结果也用起来
FIR用过去的输入做加权求和。这一节我们再加一个思路:把上次已经算好的输出也用进来。
最简单的一阶平滑就是:
y[n] = α × x[n] + (1−α) × y[n−1],0 < α ≤ 1
α越小,当前输入所占的比例越小,曲线更平滑,但响应也更慢。输入一个1之后再持续输入0,过去的输出还会一次次影响下一次输出,理论上会无限延续,因此叫无限冲激响应。
static float last_y = 0.0f;
float Smooth_Process(float x)
{
last_y += 0.1f * (x - last_y);
return last_y;
}
这个例子适合传感器平滑。α本身不是赫兹,改变采样率之后,滤波器对应的实际频率特性也会改变。常见的 α=1−exp(−2πfc/Fs)是依据模拟时间常数做的映射,不能在任意截止频率下都当成精确的数字−3 dB设计。
2 ➡️从一阶走到二阶
更常用的二阶节写成:
y[n] = b0·x[n] + b1·x[n−1] + b2·x[n−2]
− a1·y[n−1] − a2·y[n−2]
这里已经把a0归一化为1。一个二阶节也叫Biquad,多个二阶节串联可以实现更高阶滤波器。不要把高阶多项式直接硬塞进一条很长的差分方程,系数量化和舍入误差会更难控制。
反馈同时带来了稳定性问题。对本节这样的因果有理滤波器,检查极点是否严格落在单位圆内;若反馈符号写反,即使系数原本是正确的,程序也可能迅速发散。
3 🔧设计二阶Butterworth低通
继续使用8 kHz采样、500 Hz与2000 Hz的混合信号,设计1000 Hz截止的二阶Butterworth低通。
from scipy import signal
sos = signal.butter(2, 1000, fs=8000, output="sos")
print(sos) # 每行 b0, b1, b2, a0, a1, a2
Butterworth的截止频率是幅度下降到 1/√2 的位置,约−3.01 dB。SOS形式方便逐节处理,也适合进一步增加阶数。SciPy Butterworth设计说明
这组参数的CMSIS系数约为:
/* b0, b1, b2, -a1, -a2 */
{0.0976310729f, 0.1952621459f, 0.0976310729f,
0.9428090416f, -0.3333333333f}
为什么末尾两个数要取反?因为CMSIS DF1采用加反馈项的约定,而上面SciPy的a系数对应减反馈项。必须先除以a0,再转换符号。Arm DF1符号约定
4 💻实验代码
使用FIR实验中的合成输入生成方式。下载系数头文件,并把下面代码放进已经接好CMSIS-DSP的工程。
#include "arm_math.h"
#include "filter_coeffs.h"
#define BLOCK_SIZE 256U
static arm_biquad_casd_df1_inst_f32 iir;
/* 每节保存 x[n-1], x[n-2], y[n-1], y[n-2] */
static float32_t iir_state[4U * IIR_STAGES];
void IIR_Experiment_Init(void)
{
arm_biquad_cascade_df1_init_f32(&iir, IIR_STAGES, iir_coeffs, iir_state);
}
void IIR_Experiment_Block(const float32_t *input, float32_t *output)
{
arm_biquad_cascade_df1_f32(&iir, input, output, BLOCK_SIZE);
}
初始化只执行一次,每来一个连续的256点数据块,调用一次处理函数。input/output各分配256个float,历史状态保存在iir_state中。不要在中断里做整帧滤波或串口打印。
为了理解库函数内部在做什么,可以先独立运行下面的单点实现,再与CMSIS输出逐点比较。它使用本节已经转换成加反馈形式的系数:
static float x1, x2, y1, y2;
float Biquad_Process(float x)
{
float y = iir_coeffs[0] * x + iir_coeffs[1] * x1
+ iir_coeffs[2] * x2 + iir_coeffs[3] * y1
+ iir_coeffs[4] * y2;
x2 = x1; x1 = x;
y2 = y1; y1 = y;
return y;
}
5 ✅验证结果与稳定性
先测试冲激和阶跃,再测试正弦,最后才接真实ADC。
| 测试 | 理论/数值参考 |
|---|---|
| 零状态、全零输入 | 输出保持0 |
| 单位冲激 | 逐渐衰减到接近0,不持续增长 |
| 单位阶跃 | 最终接近1;过渡过程可能有过冲 |
| 500 Hz单音 | 增益约0.974,即约−0.23 dB |
| 1000 Hz单音 | 增益约0.707,即约−3.01 dB |
| 2000 Hz单音 | 增益约0.169,即约−15.44 dB |
这些是8 kHz采样、上述二阶系数的稳态参考,不是实板测量结果。可以运行验证脚本复算。对混合信号,2000 Hz输入幅度是0.5,所以该分量输出幅度约为0.0845,不能直接和增益0.169混为一谈。
本例极点模均约为0.57735,小于1;因此精确系数下的系统稳定。输入有界而输出持续变大时,应查实现与数据,不应把“无限冲激响应”理解成幅值无限增大。
若输出突然变成很大的数或NaN,按顺序检查:a0归一化、反馈符号、输入是否越界、状态数组是否被其他变量覆盖、是否把定点数据当浮点使用。换成Q15/Q31之后还要重新检查动态范围、缩放和溢出,不能只替换函数名。
6 FIR与IIR怎么选
| 比较点 | 本节之前的31抽头FIR | 本节二阶IIR |
|---|---|---|
| 历史信息 | 过去输入 | 过去输入和输出 |
| 单个输出的乘法数 | 31次 | 5次 |
| 相位 | 对称系数提供线性相位 | 一般非线性相位 |
| 稳定性 | 有限系数的理想FIR为BIBO稳定 | 需要检查极点、量化与实现 |
| 滤波强度 | 由实际频响决定 | 由实际频响决定 |
这两个例子阶数和截止定义不同,不能据此宣布某一种“效果更好”。正确的比较要先统一通带、阻带、允许延迟和算力预算,再设计各自的滤波器。
7 🔌接回课程硬件
真实连续采样使用循环DMA方案,固定采样率,跨块保留状态。单帧停采演示则每帧作为独立实验,重新初始化并观察暂态,不能假装没有停采。
若把结果送回外置MCP4725,要先恢复直流偏置、限制到0~4095,并检查IIC更新带宽。本课程的外置IIC DAC不能按片内DAC的DMA触发例程使用。
最后做一个综合实验:同一段数据分别经过FIR和IIR,再用FFT观察500 Hz和2000 Hz分量,记录幅度、延迟与处理耗时。这样我们就把“采集—搬运—分析—处理”这条线完整连起来了。