目录
概述
本文主要介绍嵌入式C语言实现IIR滤波器的设计,IIR(无限冲激响应,Infinite Impulse Response)滤波器是一种数字滤波器,其输出不仅依赖于当前和过去的输入,还依赖于过去的输出(即包含反馈)。它的数学原理基于差分方程和Z变换,通过极点和零点的配置实现滤波特性。相对于 FIR 滤波器,IIR 滤波器的群延迟较小,从而瞬时响应更短。
1 IIR滤波器的介绍
FIR 滤波器的缺点之一是它们需要很大的滤波器阶数才能满足某些设计设定。如果波纹保持不变,滤波器阶数与过渡带宽度成反比。通过使用反馈,使用小得多的滤波器阶数即可满足一组设计设定。这就是 IIR 滤波器设计背后的思想。
“无限冲激响应” (IIR) 一词源于这样的事实:
1)当冲激施加到滤波器时,输出永远不会衰减到零。
2)当计算资源非常宝贵时,IIR 滤波器非常有用。然而,稳定的因果 IIR 滤波器无法提供完美的线性相位。在要求相位线性的情况下,避免使用 IIR 设计。
3) 使用 IIR 滤波器的另一个重要原因是相对于 FIR 滤波器,IIR 滤波器的群延迟较小,从而瞬时响应更短。
2 IIR滤波器的数学原理
IIR(无限冲激响应,Infinite Impulse Response)滤波器是一种数字滤波器,其输出不仅依赖于当前和过去的输入,还依赖于过去的输出(即包含反馈)。它的数学原理基于差分方程和Z变换,通过极点和零点的配置实现滤波特性。以下是其核心数学原理的详细解析:
2.1. 差分方程
IIR滤波器的输入输出关系由以下差分方程描述:
x[n]:当前输入样本
y[n]:当前输出样本
bk:前馈(分子)系数,控制零点位置
ak:反馈(分母)系数,控制极点位置
M:前馈阶数,
N:反馈阶数
特点:
-
反馈项(∑aky[n−k]∑aky[n−k])的存在使得冲激响应无限长(即“无限”冲激响应)。
-
可实现陡峭的过渡带,但需注意稳定性(极点必须在单位圆内)。
2.2 传递函数(Z域)
将差分方程转换为Z域,得到传递函数:
分子多项式:决定零点位置,影响幅频响应的波谷。
分母多项式:决定极点位置,影响幅频响应的波峰和稳定性。
2.3 极点和零点分析
-
零点:分子多项式的根,位于Z平面任意位置。
-
极点:分母多项式的根,必须位于单位圆内(即模长 ∣pk∣<1 )以保证稳定性。
-
频响特性:
2.4 级联二阶节(Biquad)结构
为了数值稳定性和实现方便,高阶IIR滤波器通常分解为多个二阶节(Biquad)的级联。每个二阶节的传递函数为:
总传递函数为各节乘积:
-
优点:避免高阶多项式带来的数值不稳定问题。
-
实现:每个二阶节对应代码中的
Biquad
结构体(如C语言示例)。
2.5 稳定性条件
IIR滤波器稳定的充要条件是所有极点位于Z平面的单位圆内:
∣pk∣<1(所有极点)
若极点接近单位圆,滤波器的幅频响应会在对应频率出现尖锐的峰值。
2.6 设计方法
IIR滤波器的设计通常通过模拟滤波器原型(如巴特沃斯、切比雪夫、椭圆滤波器)转换而来,常用方法包括:
-
双线性变换法(Bilinear Transform)
将模拟滤波器传递函数 H(s)H(s) 转换为数字域 H(z)H(z),避免频率混叠。 -
脉冲响应不变法
通过采样模拟滤波器的冲激响应得到数字滤波器,但可能引入混叠误差。
2.7 C代码与数学的对应
在之前的C语言实现中:
-
状态变量
v1
,v2
:对应差分方程中的历史输出项。 -
直接II型结构:通过中间变量 v[n] 减少计算量,其差分方程为:
-
系数符号处理:代码中分母系数取负(
a1 = -coeffs[4]
),以匹配传递函数分母的正规形式。
3 IIR滤波器设计要点
IIR滤波器的数学核心在于通过极点和零点配置实现所需频响,借助反馈机制以较少的阶数实现高效滤波。理解其数学原理有助于:
调整滤波器参数(如截止频率、通带波纹)。
避免设计中的稳定性问题。
优化代码实现(如选择级联或并联结构)
4 IIR滤波器设计的C语言实现
4.1 实现原理
为了实现一个IIR滤波器的C语言实现,我们可以采用级联的二阶节(Biquad)结构,每个节使用直接II型(典范型)以减少状态变量的数量。以下是详细的实现步骤和代码示例:
实现步骤
定义Biquad结构体:存储每个二阶节的系数和状态变量。
初始化滤波器:将设计好的系数配置到每个Biquad,注意分母系数的符号处理。
处理函数:逐个处理每个输入样本,依次通过各个二阶节,并应用总增益。
维护状态变量:确保在处理连续样本时状态正确更新。
4.2 代码实现
#include <stdlib.h>
// 定义二阶节结构体
typedef struct {
float b0, b1, b2; // 分子系数
float a1, a2; // 分母系数(已取负)
float v1, v2; // 状态变量
} Biquad;
// 定义IIR滤波器结构体
typedef struct {
Biquad *sections; // 二阶节数组
int numSections; // 二阶节数量
float gain; // 总增益
} IIRFilter;
// 初始化IIR滤波器
void initIIRFilter(IIRFilter *filter, const float *sos, int numSections, float gain)
{
filter->numSections = numSections;
filter->sections = (Biquad *)malloc(numSections * sizeof(Biquad));
filter->gain = gain;
for (int i = 0; i < numSections; ++i) {
Biquad *section = &filter->sections[i];
const float *coeffs = &sos[i * 6]; // sos每行格式:[b0, b1, b2, 1, a1, a2]
section->b0 = coeffs[0];
section->b1 = coeffs[1];
section->b2 = coeffs[2];
section->a1 = -coeffs[4]; // 取负
section->a2 = -coeffs[5];
section->v1 = 0.0f;
section->v2 = 0.0f;
}
}
// 释放滤波器内存
void freeIIRFilter(IIRFilter *filter)
{
free(filter->sections);
filter->sections = NULL;
filter->numSections = 0;
}
// 处理单个样本
float processIIR(IIRFilter *filter, float input) {
float output = input;
for (int i = 0; i < filter->numSections; ++i) {
Biquad *section = &filter->sections[i];
// 计算中间变量v
float v = output + section->a1 * section->v1 + section->a2 * section->v2;
// 计算本节输出
float y = section->b0 * v + section->b1 * section->v1 + section->b2 * section->v2;
// 更新状态变量
section->v2 = section->v1;
section->v1 = v;
output = y;
}
return output * filter->gain; // 应用总增益
}
4.3 应用范例
使用示例
假设设计了一个4阶低通滤波器,分解为两个二阶节(从MATLAB获取sos矩阵和增益):
结构体设计:
Biquad
存储每个二阶节的系数和状态变量,IIRFilter
管理所有二阶节和总增益。系数处理:将分母系数取负,确保反馈项符号正确。
状态更新:每个样本处理完后更新状态变量
v1
和v2
,保持连续性。总增益:在输出时乘以总增益,确保幅频响应正确。
此实现高效且模块化,适用于实时处理和块处理,只需维护每个二阶节的状态即可。
int main()
{
// SOS矩阵和增益(示例数值)
float sos[] = {
// 第一个二阶节 [b0, b1, b2, 1, a1, a2]
0.2929f, 0.5858f, 0.2929f, 1.0f, -0.0f, 0.1716f,
// 第二个二阶节
1.0f, 2.0f, 1.0f, 1.0f, -0.5f, 0.7f
};
float gain = 1.0f; // 假设总增益为1
IIRFilter filter;
initIIRFilter(&filter, sos, 2, gain);
// 示例输入(如阶跃信号)
float input[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
float output[5];
for (int i = 0; i < 5; ++i)
{
output[i] = processIIR(&filter, input[i]);
}
freeIIRFilter(&filter);
return 0;
}