完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
转dsp系列教程
本章节讲解IIR滤波器直接I型的低通,高通,带通和带阻滤波器的实现。 40.1 IIR滤波器介绍 40.2 matlab工具箱fdatool生成IIR滤波器系数 40.3 IIR低通滤波器设计 40.4 IIR高通滤波器设计 40.5 IIR带通滤波器设计 40.6 IIR带阻滤波器设计 40.7 总结 40.1 IIR滤波器介绍 ARM官方提供的直接I型IIR库支持Q7,Q15,Q31和浮点四种数据类型。其中Q15和Q31提供了基于Cortex-M3和Cortex-M4的快速版本。 直接I型IIR滤波器是基于二阶Biquad级联的方式来实现的。每个Biquad由一个二阶的滤波器组成: y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2] 直接I型算法每个阶段需要5个系数和4个状态变量。 这里有一点要特别的注意,有些滤波器系数生成工具是采用的下面公式实现: y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] - a1 * y[n-1] - a2 * y[n-2] 比如matlab就是使用上面的公式实现的,所以在使用fdatool工具箱生成的a系数需要取反才能用于直接I型IIR滤波器的函数中。 高阶IIR滤波器的实现是采用二阶Biquad级联的方式来实现的。其中参数numStages就是用来做指定二阶Biquad的个数。比如8阶IIR滤波器就可以采用numStages=4个二阶Biquad来实现。 如果要实现9阶IIR滤波器就需要将numStages=5,这时就需要其中一个Biquad配置成一阶滤波器(也就是b2=0,a2=0)。 |
|
相关推荐
|
|
从上面的波形对比来看,matlab和函数arm_biquad_cascade_df1_f32计算的结果基本是一致的。为了更好的说明滤波效果,下面从频域的角度来说明这个问题,Matlab上面运行如下代码:
复制代码 fs=1000; %设置采样频率 1K N=400; %采样点数 n=0:N-1; t=n/fs; %时间序列 f=n*fs/N; %频率序列 x = sin(2*pi*50*t) + sin(2*pi*200*t); %50Hz和200Hz正弦波合成 subplot(211); y=fft(x, N); %对信号x做FFT plot(f,abs(y)); xlabel('频率/Hz'); ylabel('振幅'); title('原始信号FFT'); grid on; y3=fft(sampledata, N); %经过IIR滤波器后得到的信号做FFT subplot(212); plot(f,abs(y3)); xlabel('频率/Hz'); ylabel('振幅'); title('IIR滤波后信号FFT'); grid on; |
|
|
|
|
|
40.5 IIR带通滤波器设计
40.5.1 fdatool获取低通滤波器系数 设计一个如下的例子: 信号由50Hz正弦波和200Hz正弦波组成,采样率1Kbps,现设计一个巴特沃斯滤波器带通滤波器,采用直接I型,截止频率140Hz和,采样400个数据,滤波器阶数设置为4。fadtool的配置如下: 配置好带通滤波器后,具体滤波器系数的生成大家参考本章第二小节的方法即可。 |
|
|
|
|
|
40.5.2 带通滤波器实现
通过工具箱fdatool获得带通滤波器系数后在开发板上运行函数arm_biquad_cascade_df1_f32来测试带通滤波器的效果。 复制代码 #define numStages 2 /* 2阶IIR滤波的个数 */ #define TEST_LENGTH_SAMPLES 400 /* 采样点数 */ static float32_t testInput_f32_50Hz_200Hz[TEST_LENGTH_SAMPLES]; /* 采样点 */ static float32_t testOutput[TEST_LENGTH_SAMPLES]; /* 滤波后的输出 */ static float32_t IIRStateF32[4*numStages]; /* 状态缓存,大小numTaps + blockSize - 1*/ /* 巴特沃斯带通滤波器系数140Hz 400Hz*/ const float32_t IIRCoeffs32BP[5*numStages] = { 1.0f, 0.0f, -1.0f, -1.1276518720541668f, -0.47001314508753411f, 1.0f, 0.0f, -1.0f, 0.77495305804604886f, -0.36707750055668387f }; /* ********************************************************************************************************* * 函 数 名: arm_iir_f32_bp * 功能说明: 调用函数arm_iir_f32_hp实现带通滤波器 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ static void arm_iir_f32_bp(void) { uint32_t i; arm_biquad_casd_df1_inst_f32 S; float32_t ScaleValue; /* 初始化 */ arm_biquad_cascade_df1_init_f32(&S, numStages, (float32_t *)&IIRCoeffs32BP[0], (float32_t *)&IIRStateF32[0]); /* IIR滤波 */ arm_biquad_cascade_df1_f32(&S, testInput_f32_50Hz_200Hz, testOutput, TEST_LENGTH_SAMPLES); /*放缩系数 */ ScaleValue = 0.55815658576077365f * 0.55815658576077365f; /* 打印滤波后结果 */ for(i=0; i printf("%frn", testOutput[i]*ScaleValue); } } |
|
|
|
|
|
运行如上函数可以通过串口打印出函数arm_biquad_cascade_df1_f32滤波后的波形数据,下面通过Matlab绘制波形来对比Matlab
计算的结果和ARM官方库计算的结果。 对比前需要先将串口打印出的一组数据加载到Matlab中, arm_biquad_cascade_df1_f32的计算结果起名sampledata,加载方法在前面的教程中已经讲解过,这里不做赘述了。Matlab中运行的代码如下: 复制代码 fs=1000; %设置采样频率 1K N=400; %采样点数 n=0:N-1; t=n/fs; %时间序列 f=n*fs/N; %频率序列 x1=sin(2*pi*50*t); x2=sin(2*pi*200*t); %50Hz和200Hz正弦波 subplot(211); plot(t, x1); title('滤波后的理想波形'); grid on; subplot(212); plot(t, sampledata); title('ARM官方库滤波后的波形'); grid on; |
|
|
|
|
|
从上面的波形对比来看,matlab和函数arm_biquad_cascade_df1_f32计算的结果基本是一致的。为了更好的说明滤波效果,下面从频域的角度来说明这个问题,Matlab上面运行如下代码:
复制代码 fs=1000; %设置采样频率 1K N=400; %采样点数 n=0:N-1; t=n/fs; %时间序列 f=n*fs/N; %频率序列 x = sin(2*pi*50*t) + sin(2*pi*200*t); %50Hz和200Hz正弦波合成 subplot(211); y=fft(x, N); %对信号x做FFT plot(f,abs(y)); xlabel('频率/Hz'); ylabel('振幅'); title('原始信号FFT'); grid on; y3=fft(sampledata, N); %经过IIR滤波器后得到的信号做FFT subplot(212); plot(f,abs(y3)); xlabel('频率/Hz'); ylabel('振幅'); title('IIR滤波后信号FFT'); grid on; |
|
|
|
|
|
40.6 IIR带阻滤波器设计
40.6.1 fdatool获取带阻滤波器系数 设计一个如下的例子: 信号由50Hz正弦波和200Hz正弦波组成,采样率1Kbps,现设计一个巴特沃斯滤波器带阻滤波器,采用直接I型,截止频率100Hz和325Hz,采样400个数据,滤波器阶数设置为4。fadtool的配置如下: 配置好带阻滤波器后,具体滤波器系数的生成大家参考本章第二小节的方法即可。 |
|
|
|
|
|
40.6.2 带阻滤波器实现
通过工具箱fdatool获得带阻滤波器系数后在开发板上运行函数arm_biquad_cascade_df1_f32来测试带阻滤波器的效果。 复制代码 #define numStages 2 /* 2阶IIR滤波的个数 */ #define TEST_LENGTH_SAMPLES 400 /* 采样点数 */ static float32_t testInput_f32_50Hz_200Hz[TEST_LENGTH_SAMPLES]; /* 采样点 */ static float32_t testOutput[TEST_LENGTH_SAMPLES]; /* 滤波后的输出 */ static float32_t IIRStateF32[4*numStages]; /* 状态缓存,大小numTaps + blockSize - 1*/ /* 巴特沃斯带阻滤波器系数100Hz 325Hz*/ const float32_t IIRCoeffs32BS[5*numStages] = { 1.0f, -0.61400192638335005f, 1.0f, 1.1451427879497746f, -0.50298007146721391f, 1.0f, -0.61400192638335005f, 1.0f, -0.47458704658841883f, -0.35305199748708849f }; /* ********************************************************************************************************* * 函 数 名: arm_iir_f32_bs * 功能说明: 调用函数arm_iir_f32_bs实现带阻滤波器 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ static void arm_iir_f32_bs(void) { uint32_t i; arm_biquad_casd_df1_inst_f32 S; float32_t ScaleValue; /* 初始化 */ arm_biquad_cascade_df1_init_f32(&S, numStages, (float32_t *)&IIRCoeffs32BS[0], (float32_t *)&IIRStateF32[0]); /* IIR滤波 */ arm_biquad_cascade_df1_f32(&S, testInput_f32_50Hz_200Hz, testOutput, TEST_LENGTH_SAMPLES); /*放缩系数 */ ScaleValue = 0.58347920314378698f * 0.58347920314378698f; /* 打印滤波后结果 */ for(i=0; i printf("%frn", testOutput[i]*ScaleValue); } } |
|
|
|
|
|
运行如上函数可以通过串口打印出函数arm_biquad_cascade_df1_f32滤波后的波形数据,下面通过Matlab绘制波形来对比Matlab计算的结果和ARM官方库计算的结果。
对比前需要先将串口打印出的一组数据加载到Matlab中, arm_biquad_cascade_df1_f32的计算结果起名sampledata,加载方法在前面的教程中已经讲解过,这里不做赘述了。Matlab中运行的代码如下: 复制代码 fs=1000; %设置采样频率 1K N=400; %采样点数 n=0:N-1; t=n/fs; %时间序列 f=n*fs/N; %频率序列 x1=sin(2*pi*50*t); x2=sin(2*pi*200*t); %50Hz和200Hz正弦波 subplot(211); plot(t, x1); title('滤波后的理想波形'); grid on; subplot(212); plot(t, sampledata); title('ARM官方库滤波后的波形'); grid on; |
|
|
|
|
|
从上面的波形对比来看,matlab和函数arm_biquad_cascade_df1_f32计算的结果基本是一致的。为了更好的说明滤波效果,下面从频域的角度来说明这个问题,Matlab上面运行如下代码:
复制代码 fs=1000; %设置采样频率 1K N=400; %采样点数 n=0:N-1; t=n/fs; %时间序列 f=n*fs/N; %频率序列 x = sin(2*pi*50*t) + sin(2*pi*200*t); %50Hz和200Hz正弦波合成 subplot(211); y=fft(x, N); %对信号x做FFT plot(f,abs(y)); xlabel('频率/Hz'); ylabel('振幅'); title('原始信号FFT'); grid on; y3=fft(sampledata, N); %经过IIR滤波器后得到的信号做FFT subplot(212); plot(f,abs(y3)); xlabel('频率/Hz'); ylabel('振幅'); title('IIR滤波后信号FFT'); grid on; |
|
|
|
|
|
40.7 总结
本章节主要讲解了巴特沃斯低通,高通,带通和带阻滤波器的实现,有兴趣的可以使用同样的方法实现切比雪夫滤波器的设计。 |
|
|
|
|
|
大神,留个联系方式呗 向你学习学习
|
|
|
|
|
|
今天给大家带来单片机、嵌入式中比较常用的一种程序设计方法--分层设计模式,内核中就大量采用这种设计方式,一般对于某种硬件体系分为几层,
以一个核心层来管理,它会抽象出硬件或者个体的共性操作来进行管理,很像在用C语言实现面向对象的设计。 下面就以实际代码来简单说明。假设我们有这么一种需求,需要从某些设备读取一些数据,但是这些设备可能有51体系的,也可能有arm体系的。 那么我们应该抽象一个数据结构来表示这种这些发送数据的设备。这就是核心层需要做的工作,假如我们设备有两个共性: 1.使用前需要初始化 2.能收到数据 那么我们的数据结构就应该这么抽象: struct ReceiveOpr { int (*getDevData)(char data[]); //设备接收数据的函数 int (*devInit)(); //设备初始化函数 }; 但是核心层管理的不仅仅是一个设备,为了方便核心层能找到某个设备,那么就需要给设备一个标示,这个标示可以有很多种,比如设备名字,产品id等等, 这里我们以设备名来区分。所以上面结构体就应该继续添加名字成员,变为: struct ReceiveOpr { char *name; //接收设备的名字 int (*getDevData)(char data[]); //设备接收数据的函数 int (*devInit)(); //设备初始化函数 }; 那么核心层如何管理这些设备呢?比较简单也是比较常见的就是设备链表了,也就是我们普通的数据结构链表,所以还需要一个指针,来操作这个设备链表, 于是,结构体应该再添加一个成员,变为: struct ReceiveOpr { char *name; //接收设备的名字 int (*getDevData)(char data[]); //设备接收数据的函数 int (*devInit)(); //设备初始化函数 struct ReceiveOpr *next; //用来管理链表 }; 内核中有种双向链表的数据结构 list_head,它提供了更为强大的链表管理能力,是内核最核心的数据结构之一,有兴趣也可以移植那个来用。 到这里,我们的接收设备的功能抽象就基本完成了(如果在实现过程中发现还需要其他成员,可以随时添加)。 那么核心层如何具体去实现呢?贴一下代码通过注释就能明白。 receive_manager.h---输入设备核心层头文件 #ifndef _RECEIVE_MANAGER_H #define _RECEIVE_MANAGER_H struct ReceiveOpr { char *name; //接收设备的名字 int (*getDevData)(char data[]); //设备接收数据的函数 int (*devInit)(); //设备初始化函数 struct ReceiveOpr *next; //用来管理链表 }; //函数声明 int registerRecvOpr(struct ReceiveOpr *p); int selectRecvDev(char *name); int getDevData(char data[]); int recvDevInit(); int recvManagerInit(); #endif /* _RECEIVE_MANAGER_H */ receive_manager.c---输入设备核心层实现文件 #include #include #include static struct ReceiveOpr *listReceiveHead = NULL; //设备链表的链表头 static struct ReceiveOpr *defaultDev = NULL; //需要操作的设备指针 /** * @Brief 核心层提供给具体设备的注册函数,每个设备都要提供上面的结构体指针,然后注册到核心层 * @param p 表格行数 */ int registerRecvOpr(struct ReceiveOpr *p) { struct ReceiveOpr *listTmp; if (!listReceiveHead) { listReceiveHead = p; p->next = NULL; } else { listTmp = listReceiveHead; while (listTmp->next) { listTmp = listTmp->next; } listTmp->next = p; p->next = NULL; } return 0; } /** * @brief 根据name在链表中找到相应的设备,然后赋给指针 defaultDev * @param name 设备名字 */ int selectRecvDev(char *name) { struct ReceiveOpr *listTmp = listReceiveHead; while (listTmp) { if (strcmp(listTmp->name, name) == 0) { defaultDev = listTmp; return 0; } listTmp++; } return -1; } /** * @brief 根据指定的defaultDev,取出其中的数据,存到data数组中 * @param data 数组指针 */ int getDevData(char data[]) { int ret; if(defaultDev) { ret = defaultDev->getDevData(data); return ret; } else { return -1; } } /** * @brief 根据指定的defaultDev,对其进行初始化 */ int recvDevInit() { if(defaultDev) { if(defaultDev->devInit() == 0) { return 0; } else { return -1; } } else { return -1; } } /** * @brief 对需要管理的设备进行注册,后边会说到 */ int recvManagerInit() { registerArmRecv(); return 0; } 核心层简单的管理工作就完成了,总结下就是: 1.提供 registerRecvOpr 接口给具体设备用,并把设备添加到设备链表 2.提供 recvDevInit、selectRecvDev、getDevData接口给上层用,来选择设备并使用设备 那么我们具体的设备要怎么做呢,就是实现核心层定义的结构体,然后注册到核心层即可,下面以一个模拟设备来说明 arm_recv.c---假设这是arm体系下一个设备,功能没有实现,只是模拟用 #include static char buf[16]; static int fd; static int getArmRecvDev(char data[]); static int armRecvInit(); //这是核心层提供的设备抽象,具体设备文件就是需要去挨个实现这些成员 static struct ReceiveOpr armRecvDev = { .name = "arm_recv", .getDevData = getArmRecvDev, .devInit = armRecvInit, }; //提交数据的函数,只是模拟而已 static int getArmRecvDev(char data[]) { int i, j=0; int ret; ret = read(fd, buf, 16); if(ret > 0) { for(i=0; i<16; i++) { if(i%2 == 0) { data[j] = buf; j++; } } return (ret / 2); } else { return -1; } } //设备初始化函数 static int armRecvInit() { fd = open("/dev/power", O_RDWR); if (fd < 0) { printf("can't open arm_recv!n"); return -1; } else { printf("open arm_recv success!n"); } return 0; } //注册函数,核心层初始化时候会依次调用各个设备的注册函数 int registerArmRecv(void) { return registerRecvOpr(&armRecvDev); } 这里只是举了一个例子,具体的设备功能当然要复杂的多,假如还有个51体系的,或者stm32的,那么完全可以再实现两个文件:51recv.c和stm32.c来注册 到核心层。 最后,来看下上层如何通过核心层来操作具体设备 main.c---使用实例 #include "receive_manager.h" int main(int argc, char **argv) { int ret; char buf[8]; recvManagerInit(); //注册各个输入设备 selectRecvDev("arm_recv"); //选择要操作的设备 recvDevInit(); //对选择的设备进行初始化 while(1) { ret = getDevData(buf); //操作设备 printf("%dn", ret); } } 简单吧,这样的设计使我们的程序层次感更加明了,方便管理我们的项目。这里只是简单的做了介绍,实际项目中使用的核心层管理要复杂的多,具体需要 阅读更多的源码来深入体会。 |
|
|
|
|
|
385 浏览 0 评论
1178 浏览 0 评论
《DNESP32S3使用指南-IDF版_V1.6》第六十四章 LVGL 综合例程
915 浏览 0 评论
3535 浏览 1 评论
1736 浏览 0 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
13100 浏览 31 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-5-13 00:09 , Processed in 0.846582 second(s), Total 72, Slave 65 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191