FIR与IIR低通滤波器的最简最快实现
novakon2016/01/14软件综合 IP:广东
在设计单片机程序的过程中,经常需要利用ADC采集外界模拟信号。有一些信号我们比较关注它的直流与低频分量,希望将高频噪声滤除,就需要借助低通滤波器。
低通滤波器常见的利用电子电路实现的方式是一阶RC无源滤波器。简单讲就是这样:

QQ20160114-0@2x.png

RC滤波器的各类计算略,有需要请谷歌。

RC滤波器用于单片机ADC输入有许多缺点。如果R的取值较小,就要求C较大,同时输入信号阻抗不能过大;如果R的取值较大,则ADC采样瞬间释放的电荷会使得端口电压升高而无法在采样时间内释放到稳定水平。这会导致采样精度问题。
而即便使用一个运放来缓冲RC滤波器的输出,再接入ADC,也只解决了输入阻抗问题,ADC电路受外界干扰仍然会在转换结果中产生噪声。因此,我们希望在单片机内部利用程序来实现低通滤波,彻底摆脱高频噪声。

数字低通滤波器有两种形式,IIR和FIR。
IIR是无限脉冲响应滤波器,它的特点是输出与无限久以前的输入有关。这就如同上面RC滤波器的响应,随着时间流逝,输出电压只会无限接近于输入电压,而不会等于。
用c语言实现IIR低通很简单:
<code class="lang-">int last = 0;</code>

//下面的函数以固定频率运行,函数输出就是IIR低通滤波器的输出。
<code class="lang-">int lowpass()
{
    int this = ADC();
    last = (this * 1 + last * 15) / 16; //新的 last 是旧的 last * 15 / 16 + this * 1 / 16
    return last;
}</code>

如果对函数返回值作图,得到的波形就会和RC滤波器的波形一样。要改变截止频率,只需要改变函数第二句中新的last的组成(例如改成3/4和1/4,截止频率会提高)。
值得注意的是,上述代码使用了整数乘法和2的n次幂除法,因此编译优化后,在8位平台上运行超快。如果你使用带有浮点运算模块的平台,请直接使用浮点数。
IIR的特点是节省内存,上面的滤波器只使用了两个变量。
IIR的缺点是不稳定。如果你把15改成17,显然这个滤波器的输出会在一段时间后溢出。你必须负责保证IIR滤波器稳定。

FIR是有限脉冲响应滤波器,它的特点是输出与有限久以前(一段时间内)的输入有关。由于数字系统中采样是离散的,每一段时间内的采样数是固定的,所以FIR滤波器的每一个输出值,可通过对之前的若干个数量固定的采样值进行计算得到。特别是,因为对固定数量的采样值的计算在FPGA电路中可以并行实现,因此FIR滤波器常常被用于基于FPGA的数字信号处理系统。相对的,IIR滤波器不依赖之前的采样,但依赖于之前的滤波器状态(存在反馈),因此在大部分比一阶低通滤波器复杂的应用场合,IIR滤波器的各项特性没有FIR稳定(若要实现稳定,对计算精度要求较高),而且计算起来比FIR要慢,设计上也没有通用性。


下面我用c语言实现一个最简单的FIR低通。我们将最近的8次采样值加起来,求平均值,作为输出。
<code class="lang-">int buf[8];
int lowpass()
{
    int k = 7;
    while(k--)
        buf[k] = buf[k-1]; //将buf[6] 移到 buf[7], buf[5] 移到 buf[6],等等,以空出 buf[0]
    }
    buf[0] = ADC();
    return (buf[0] + buf[1] +...+ buf[7]) / 8;
}</code>


这个滤波器的时域图像有点像这样:
QQ20160114-1@2x.png

值得注意的是,上面这个函数可以被进一步简化,以加速计算。上面这种滤波器有另一个名字:滑动平均滤波器,这个滤波器大部分股民应该很熟悉。
虽然时域图像很美观,但是这个滤波器的频域图像一点也不美观:
QQ20160114-2@2x.png
可见,随着点数的增加,图像看起来只是从右向左缩水,对高频的抑制并不好。
所以如果想要获得比较好的低通效果,不应该增加点数,而应该将多个一样的FIR滤波器串联使用(一个的输出作为下一个的输入)。
注:pass是“遍”的意思,表示迭代次数。
QQ20160114-3@2x.png

上面是串联使用的幅频响应。而时域图像也很美观:

QQ20160114-4@2x.png

结尾给出的是写这篇文章时随手找到的资料。大家一定要学会使用谷歌搜索英文关键词,因为老外比我们对待知识的态度更严肃也更开放。
XXXXXXXXXXXXXXXXXXXXX/media/en/technical-documentation/dsp-book/dsp_book_Ch15.pdf

[修改于 8年6个月前 - 2016/06/07 08:53:59]

来自:计算机科学 / 软件综合
16
 
1
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
novakon 作者
8年11个月前 IP:广东
804237
刚才的滑动平均滤波器,时间复杂度是O(n)(设每次处理n个采样)。


可以优化为O(1)的形式:


int buf[8];
int k=0;
int result=0;
int lowpass()
{
    result -= buf[k];
    buf[k] = ADC();
    result += buf[k];
    k = (k + 1) % 8;
    return result;
}
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
novakon作者
8年11个月前 IP:广东
804241
引用 布布卡:
这个过程是不是在求卷积,滤波器参数一般怎么定(之前用过一次这种类型的滤波,不过参数选取都是根据结果主观上粗调的,没发挥最大威力)
求lz解惑
上面给出的FIR滤波器是特殊形式。FIR滤波器的常见形式就是求输入信号序列的卷积。卷积函数由有限长的数组表示。这个数组就是我们常说的FIR滤波器参数。
通常,我们会使用滤波器设计软件(比如matlab内置的)针对所需的滤波特性得到合适的参数。
上面例子中的滤波器参数就是[1/8, 1/8, 1/8, 1/8...]

对于低通滤波器而言,这个参数配置非常不理想。不过这样的滤波器可以在CPU上于O(1)时间内完成,因此也有它的实用价值。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
novakon作者
8年8个月前 IP:广东
815456
引用 稀牛:
上面的:buf=ADC(); 不知道怎么编辑不了buf=ADC();
请使用“插入代码”功能。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
novakon作者
8年8个月前 IP:广东
815915
引用 ssis909:
还有你说的"即便使用一个运放来缓冲RC滤波器的输出,再接入ADC,也只解决了输入阻抗问题,ADC电路受外界干扰仍然会在转换结果中产生噪声",采样噪声不可避免,那么使用FIR滤波就能避免吗?难道就不受外界干扰?
FIR低通可衰减信号中的高频噪声,如果用户关心低频精度,这是个好办法。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
novakon
学者 机友 笔友
文章
1256
回复
8386
学术分
16
2008/03/29注册,2年10个月前活动

已走,勿送

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}