一、离散化方法
设计数字滤波器时,常见的离散化方案主要包括以下几种:
直接型(Direct Form)
直接型I(Direct Form I): 结构简单,易于实现,但由于存在较多的延迟单元存储历史样本,因此在有限字长效应下可能产生较大的量化噪声。
直接型II(Direct Form II): 相对于直接型I,减少了存储单元的数量,降低了量化噪声,实现起来同样简单,是实际应用中最为常见的结构,尤其适用于低通和高通滤波器。
级联型(Cascade Form)
级联型结构将滤波器分解为若干个一阶节或二阶节进行连接,易于理解和实现,且方便进行模块化设计。不过,级联型滤波器在频率响应的过渡区可能会产生更多极点,导致相位非线性更加明显。
频率采样型(Frequency Sampling Method)
通过将理想滤波器的频率响应离散化,转化为一个无限长的脉冲响应,然后截取有限长度实现。此方法直观简便,适用于设计简单的滤波器,但难以精确控制过渡带宽度和形状。
窗函数法(Windowing Method)
基于傅里叶变换理论,将连续时间的理想滤波器转换为离散时间滤波器,通过在理想频率响应上施加一个窗函数来实现离散化。这种方式可以获得较精确的通带和阻带特性,但可能会引入窗函数导致的边带纹波和过渡带宽度增加。
变换方法(Transformation Methods)
如双线性变换(Bilinear Transformation)和多项式变换(Impulse Invariant Transform),可以将连续时间滤波器映射到离散时间域,双线性变换在低频段具有较好的逼近效果,但高频段可能会产生扭曲;而冲击不变变换在低频段的相位失真较大,但在高频段有更好的相位保持特性。
优化算法(Optimization Techniques)
利用优化算法直接在数字域设计滤波器,如格型搜索(Lattice Structure)、雷米兹交换算法等,可以根据指定的滤波器性能指标优化滤波器系数,通常能得到较为理想的性能,计算复杂度较高。
在实际应用中,选择哪种离散化方案通常要考虑以下因素:
信号类型:连续信号还是离散信号,信号的带宽、采样率和信噪比等特性。
滤波器性能要求:通带平坦度、阻带衰减、过渡带陡峭程度、线性相位要求等。
实现复杂度:硬件资源限制、计算能力、存储容量和运算速度要求等。
二、C语言实现
在C语言中实现二阶带通滤波器的各种设计方法,由于篇幅原因,此处仅给出各设计方法的简述和伪代码示意,实际编程实现需要根据具体设计参数进行详细计算。
直接型I、直接型II(Butterworth滤波器设计)这种设计方法主要是通过解析计算滤波器的传递函数系数,然后转换为数字滤波器的系数。以二阶Butterworth带通滤波器为例,需要根据给定的中心频率和品质因数Q来计算模拟滤波器的传递函数系数,再通过Tustin(双线性变换)等方法转化为数字滤波器的系数。
void design_butterworth_bandpass(int order, float wc1, float wc2, float fs, float *b, float *a) {
// 根据wc1, wc2计算模拟滤波器的ωc和Q
// 计算模拟滤波器传递函数的分子和分母系数
// 应用双线性变换或其他变换方法将模拟滤波器转换为数字滤波器
// 将转换后的数字滤波器系数赋值给b和a数组
}
// 使用设计好的滤波器进行信号处理
float process_signal(float b[], float a[], float input) {
static float x_n_1, x_n_2, y_n_1, y_n_2; // 滤波器状态变量
// 根据IIR滤波器公式计算输出
float output = (b[0]*input + b[1]*x_n_1 + b[2]*x_n_2) - a[1]*y_n_1 - a[2]*y_n_2;
// 更新滤波器状态
x_n_2 = x_n_1;
x_n_1 = input;
y_n_2 = y_n_1;
y_n_1 = output;
return output;
}
窗函数法(Windowing Method)
// 假设已经计算得到模拟滤波器的理想冲击响应impulseResponseIdeal[n]
// 窗函数选择Hamming窗
const int ORDER = 2; // 我们知道二阶滤波器的长度为3,包括中心点
float window[ORDER + 1]; // 创建一个窗函数数组
// 初始化Hamming窗函数
for (int n = 0; n <= ORDER; n++) {
float normalized_n = (float)n / ORDER;
window[n] = 0.54 - 0.46 * cos(2 * M_PI * normalized_n);
}
// 将理想冲击响应与窗函数相乘
float impulseResponseWindowed[ORDER + 1];
for (int n = 0; n <= ORDER; n++) {
impulseResponseWindowed[n] = impulseResponseIdeal[n] * window[n];
}
// 将加窗后的冲击响应转换为滤波器系数
float b[3]; // 分子系数
float a[3]; // 分母系数,假设是二阶滤波器,所以a[0]通常为1,a[1]和a[2]为分母系数
// 根据加窗后的冲击响应计算滤波器系数
// 这部分通常涉及傅里叶变换或者双线性变换等过程,这里简化表示
calculateFilterCoefficientsFromImpulseResponse(impulseResponseWindowed, b, a);
// 定义滤波器处理函数
float processSignal(float b[], float a[], float input, float x_n_minus1, float x_n_minus2, float y_n_minus1, float y_n_minus2) {
float output = b[0] * input + b[1] * x_n_minus1 + b[2] * x_n_minus2 - a[1] * y_n_minus1 - a[2] * y_n_minus2;
// 更新状态变量
x_n_minus2 = x_n_minus1;
x_n_minus1 = input;
y_n_minus2 = y_n_minus1;
y_n_minus1 = output;
return output;
}
// 使用滤波器
float input_signal[signal_length];
float output_signal[signal_length];
// 初始化状态变量
float x_n_minus1 = 0.0f;
float x_n_minus2 = 0.0f;
float y_n_minus1 = 0.0f;
y_n_minus2 = 0.0f;
for (int i = 0; i < signal_length; ++i) {
output_signal[i] = processSignal(b, a, input_signal[i], x_n_minus1, x_n_minus2, y_n_minus1, y_n_minus2);
}
以上设计方法都需要结合具体的滤波器设计理论和公式,根据设计目标(截止频率、带宽、品质因数等)计算对应的滤波器系数,然后再用这些系数来实现滤波器的程序代码。