今天在一本书上看到了个计算数字滤波器频响的代码,这个代码还算有用,就整理了一下。代码写的挺长的,下面是原始的代码。
/**
* 计算数字滤波器的频率响应
* num 是数字滤波器的分子多项式系数
* den 是数字滤波器的分母多项式系数
* num_order 是分子多项式的阶数
* den_order 是分母多项式的阶数
* sign = 0 时,x_out 为频率响应的实部, y_out 为频率响应的虚部
* sign = 1 时,x_out 为频率响应的模, y_out 为频率响应的幅角
* sign = 2 时,x_out 为以 dB 为单位的频率响应, y_out 为频率响应的幅角
* len 为,频率响应的取样点数
*/
void gain(double num[], double den[], int num_order, int den_order,
double x_out[], double y_out[], int len, int sign)
{
int i, k;
double zr, zi;
double re, im;
double ar, ai, br, bi;
double numr, numi;
double de, temp;
double freq;
for(k = 0; k < len; k++)
{
freq = 0.5 * k / (len - 1);
zr = cos(-2.0 * M_PI * freq);
zi = sin(-2.0 * M_PI * freq);
br = 0.0;
bi = 0.0;
for(i = num_order; i > 0; i--)
{
re = br;
im = bi;
br = (re + num[i]) * zr - im * zi;
bi = (re + num[i]) * zi + im * zr;
}
ar = 0.0;
ai = 0.0;
for(i = den_order; i > 0; i--)
{
re = ar;
im = ai;
ar = (re + den[i]) * zr - im * zi;
ai = (re + den[i]) * zi + im * zr;
}
br = br + num[0];
ar = ar + 1.0;
numr = ar * br + ai * bi;
numi = ar * bi - ai * br;
de = ar * ar + ai * ai;
x_out[k] = numr / de;
y_out[k] = numi / de;
switch(sign)
{
case 1:
temp = hypot(x_out[k], y_out[k]);
y_out[k] = atan2(y_out[k], x_out[k]);
x_out[k] = temp;
break;
case 2:
temp = x_out[k] * x_out[k] + y_out[k] * y_out[k];
y_out[k] = atan2(y_out[k], x_out[k]);
x_out[k] = 10 *log10(temp);
break;
default:
break;
}
}
}
下面是个测试例子,滤波器系数如下:
#include
#include
#include
void gain(double num[], double den[], int num_order, int den_order,
double x_out[], double y_out[], int len, int sign);
#define N 200
int main()
{
double den[] = {1.0, 0.0, 0.9};
double num[] = {0.0, -1.0};
double x_out[N], y_out[N];
int i;
double f;
gain(num, den, 1, 2, x_out, y_out, N, 1);
for(i = 0; i < N; i ++)
{
f = 0.5 * i / (N - 1);
printf("%f, %f, %f\n", f, x_out[i], y_out[i]);
}
return 0;
}
下面是计算出的幅频特性曲线。
下面是计算出的相频特性曲线。
上面的代码虽然计算结果看起来没什么问题,不过这么简单的功能写了这么多行也有点啰嗦。下面是我给出的另外几种实现方法。
数字滤波器的传递函数可以写为:
其中a[0] 通常为 1。
频率响应为:
其中
可以看出传递函数其实就是两个多项式的结果相除。所以如果能写出个多项式求值的代码,自然也就能写出频率特性计算的代码了。
因此先写个多项式求值的代码,用到了些C99的特性(对复数运算的支持)
double _Complex poly_val(double p[], int n, double omega)
{
int i;
double _Complex z, sum = 0;
for (i = 0; i < n; i++)
{
z = cos(i * omega) - I * sin(i * omega);
sum += p[i] * z;
}
return sum;
}
有了这个代码,后面的代码就很容易写出来了。
void gain(double num[], double den[], int num_size, int den_size, double _Complex out[], int n)
{
double omega;
int i;
for (i = 0; i < n; i++)
{
omega = M_PI * i / (n-1);
out[i] = poly_val(num, num_size, omega) / poly_val(den, den_size, omega);
}
}
下面是测试代码,可以验证计算结果是相同的。
#define N 200
int main()
{
double den[] = {1.0, 0.0, 0.9};
double num[] = {0.0, -1.0};
double _Complex out[N];
int i;
double f;
gain(num, den, 2, 3, out, N);
for(i = 0; i < N; i ++)
{
f = 0.5 * i / (N - 1);
printf("%f, %f, %f\n", f, cabs(out[i]), carg(out[i]));
}
return 0;
}
这个代码与原来的代码最大的区别就是调用 sin 和 cos 的次数多一些。之所以代码调用 sin 和 cos次数少,是因为用到了多项式计算的Horner 方法。
我专门有一篇文章介绍 多项式计算的Horner 方法,所以这里就不多说了。下面给个利用Horner 的代码,实现的功能与书上的代码的功能完全相同。
double _Complex poly_val2(double p[], int order, double omega)
{
int i;
double _Complex z, sum = 0;
sum = 0.0;
z = cos(omega) - I * sin(omega);
for (i = order; i >= 0; i--)
{
sum = sum * z + p[i];
}
return sum;
}
/**
* 计算数字滤波器的频率响应
* num 是数字滤波器的分子多项式系数
* den 是数字滤波器的分母多项式系数
* num_order 是分子多项式的阶数
* den_order 是分母多项式的阶数
* sign = 0 时,x_out 为频率响应的实部, y_out 为频率响应的虚部
* sign = 1 时,x_out 为频率响应的模, y_out 为频率响应的幅角
* sign = 2 时,x_out 为以 dB 为单位的频率响应, y_out 为频率响应的幅角
* len 为,频率响应的取样点数
*/
void gain2 (double num[], double den[], int num_order, int den_order,
double x_out[], double y_out[], int len, int sign)
{
int k;
double omega;
double _Complex h;
for (k = 0; k < len; k++)
{
omega = M_PI * k / (len-1);
h = poly_val2(num, num_order, omega) / poly_val2(den, den_order, omega);
switch(sign)
{
case 1:
y_out[k] = carg(h);
x_out[k] = cabs(h);
break;
case 2:
y_out[k] = carg(h);
x_out[k] = 20 * log10(cabs(h));
break;
default:
x_out[k] = creal(h);
y_out[k] = cimag(h);
break;
}
}
}
#define N 200
int main( void )
{
double den[] = {1.0, 0.0, 0.9};
double num[] = {0.0, -1.0};
int i;
double f;
double x_out[N], y_out[N];
gain2(num, den, 1, 2, x_out, y_out, N, 1);
for(i = 0; i < N; i ++)
{
f = 0.5 * i / (N - 1);
printf("%f, %f, %f\n", f, x_out[i], y_out[i]);
}
return 0;
}