☰
单片机位移测量中的一阶低通滤波:原理、代码与参数整定指南
2026/10/15 16:35:24 网站建设 项目流程

1. 一阶低通滤波用在位移法里,到底解决什么问题

做单片机项目的人,八成都在某个时刻对着示波器上毛刺乱跳的位移曲线发过愁。用编码器读轮子位置,波形里全是高频毛刺;用加速度计积分出位移,曲线漂得连亲妈都不认识;用激光测距模块读障碍物距离,偶尔蹦出一个离谱的跳变。这类问题,我第一个想到的算法就是一阶低通滤波。

一阶低通滤波大概是所有信号处理算法里性价比最高的一招。它不需要矩阵运算,不需要FFT,不占用多少Flash和RAM,一个变量存上一次输出,每次采样做一次加法和一次乘法,就完成了一次滤波。在8位单片机上都跑得动,在Cortex-M系列上更是零压力。而“位移法”这个场景,恰好是把一阶低通滤波价值体现得最充分的地方之一:位移信号本身是低频主导的物理量,而传感器采集过程中叠加的高频噪声、量化噪声、机械振动干扰,全部落在高频段。一个简单的低通滤波器,就能把这两类成分干净利落地分开。

这篇文章我想从数学原理讲到实际代码,再讲到我调参时踩过的坑。内容覆盖浮点实现、定点实现、截止频率计算、采样率选择、相位滞后等问题。适合正在做编码器测距、加速度积分位移、超声波/激光测距滤波、以及平衡车或机械臂位置闭环的朋友参考。如果你只是想让抖动的位置数据变得平滑,同时又不想引入复杂的卡尔曼滤波,那这篇文章应该能给你一套直接能用的方案。

需要先说清楚的是:一阶低通滤波不是万能药。它是一把刀,用好了切菜,用不好切手。尤其在位移法这种场景下,它有一个非常显著的副作用叫相位滞后,搞不好会让你的闭环系统直接震荡。我后面会专门讲这部分。

2. 一阶低通滤波的原理,用大白话拆开揉碎

2.1 从微分方程到一行代码

一阶低通滤波器的连续域传递函数,玩过自动控制的人应该都见过:

H(s) = 1 / (1 + s·τ)

其中τ是时间常数。把它离散化,最常用的是一阶后向差分,最后得到的就是这个递推式:

y[n] = y[n-1] + α · (x[n] - y[n-1])

这里x[n]是当前采样值,y[n]是当前滤波输出,y[n-1]是上一次的输出,α是滤波系数,取值在0到1之间。这一行代码就是整个一阶低通滤波的全部秘密。

它做的事情可以这样理解:每次采样,新的输出等于上一次的输出,然后往当前输入的方向挪动一小步。α越大,挪动的步子越大,跟踪越快,滤波越弱;α越小,挪动的步子越小,输出越平滑,但响应越迟钝。α=1的时候相当于直通,α=0的时候输出恒为初值不再变化。

从频域看,α对应的截止频率大致可以这样估算:

fc ≈ α / (2π·Ts)

其中Ts是采样周期。这个公式是近似值,当α比较小的时候精度尚可,α接近1时会有明显误差,但用于工程选参已经足够。后面我会讲更精确的计算方式。

2.2 为什么它能把毛刺滤掉

我习惯用一个比喻来解释低通滤波:它像是一个“有惯性的水龙头”。你拧开水龙头,水流不会瞬间达到最大,而是慢慢变大;你关掉它,水流也不会瞬间归零,而是慢慢变小。高频信号意味着变化快,水体还没来得及反应,下一波变化又来了,所以高频成分被平均掉了;低频信号变化慢,水体有足够时间跟上,所以低频成分基本保留。

落到位移数据上就是这样的场景:轮子真正在往前移动时,位移曲线是一条缓慢变化的斜线,这是低频成分;而码盘读数因为机械振动、编码器码道脏污、电磁干扰产生的跳变,是叠加在平滑曲线上的高频尖峰。一阶低通滤波让高频尖峰被“平均掉”,输出的曲线就是一条干净的趋势线。

2.3 位移法的特殊性:低频是主料,高频是噪声

位移或者位置信号,和加速度信号有本质区别。加速度计在振动测试里测的本身就是高频振动,加速度信号里有大量有效高频成分,但对它做低通滤波往往损失掉关键信息。位移不一样,无论是小车的位置、机械臂的关节角、还是平台的高度,有效信息基本都集中在低频段。哪怕是快速的往返运动,主频通常也在几赫兹以内。

所以位移法场景里,高频成分大概率是噪声,低频成分大概率是有效信号,这正好把一阶低通滤波的长处发挥到极致。这也是为什么在很多位移测量项目中,一个简单的一阶低通滤波就能让数据质量提升一个档次,而不需要上卡尔曼或者滑动平均滤波。

3. 位移法的完整数据链路:滤波该放在哪个位置

3.1 位移数据的三种典型来源

做位移测量,常见的数据来源有三类,它们的噪声特性完全不同,滤波策略也有差异。

第一类是编码器/码盘。增量式编码器输出脉冲,单片机计数得到位置。它的噪声主要来自码盘振动导致的边沿抖动、计数方向切换时的毛刺、以及电磁干扰。这类信号的特点是:低频位移真实可信,高频毛刺幅度通常不大,滤波需求中等。

第二类是激光测距/超声波测距。这类传感器读取距离值,噪声来源包括目标表面反射不均匀、多径效应、环境光干扰、以及偶尔的彻底丢包。超声波测距还特别容易在温度变化时产生偏移。这类数据的噪声往往是脉冲型的,一个异常值能跳出去好几厘米。

第三类是加速度计二次积分。这是最麻烦的一种。加速度计输出经过两次积分得到位移,低频漂移和零偏会被积分放大,几乎没有直流的可信度,只能提取一段时间内的相对位移变化。这里如果只用一阶低通,结果通常很难看,需要配合高通滤波来压制漂移。

3.2 滤波位置的选择:前置、后置还是环路内

滤波放哪里,直接影响系统行为。我见过很多新手把滤波加在传感器读取之后、控制算法之前,这是最常见的做法,也基本合理。但要注意的是,滤波同时引入了相位滞后,而控制回路内的相位滞后是稳定性的大敌。

这里有一个实践原则:如果滤波器放在开环的测量显示链路上,滞后一点无所谓,平滑最重要;如果滤波器放在闭环反馈回路里,滞后就是致命伤,必须在平滑度和截止频率之间做严格权衡。

举个例子。我做某机器人的位置闭环时,检测轮子位移用的是编码器,PID输出控制电机。一开始我把一阶低通截止频率定在2Hz,位置曲线确实漂亮得不带一点毛刺,但电机响应明显“黏糊”,系统在高速往返时甚至发出嗡嗡的振荡声。后来我把截止频率提到20Hz,毛刺稍微多了一点,但系统稳定性完全恢复。这个教训让我明白:滤波参数不是单独看曲线好不好看,要和整个系统的带宽一起考虑。

3.3 加速度积分场景里为什么低通不够用

如果你是用加速度计积分得到位移,那先给你泼盆冷水:单靠一阶低通滤波,救不了积分漂移。

加速度计存在零偏,哪怕只有几十毫克级别的微小偏移,积分一次变成速度斜坡,再积分一次就变成抛物线型的位移漂移。低通滤波对低频漂移几乎没有压制能力,因为漂移本身是低频甚至直流分量。所以在做加速度积分位移时,正确的做法通常是带通处理思路:用高通滤波器去掉低频漂移,用低通滤波器去掉高频噪声,只保留某个频段内的相对位移变化。

具体到工程上,高通滤波可以用类似的递推式实现,y[n] = α·(y[n-1] + x[n] - x[n-1]),高通和低通配合起来,效果就比单独低通强很多。这个思路在很多惯性测量项目里是标配,我在第四部分还会提到。

4. 单片机上的C语言实现:从浮点到定点

4.1 基础浮点版本:三行代码的事

假设你用的是带FPU的单片机,比如Cortex-M4F或者M7,直接用浮点就行。代码非常简单:

typedef struct { float output; float alpha; } LowPassFilter; float lowpass_filter_update(LowPassFilter *f, float input) { f->output = f->output + f->alpha * (input - f->output); return f->output; }

初始化的时候把alpha设置好,output设置为第一次输入值,之后每次新采样进来调用一次_update函数,拿到的返回值就是滤波后的数据。整个滤波器只占用一个结构体,4字节状态加4字节系数。

这里有一个关键细节:滤波器系数alpha需要根据采样周期Ts和期望截止频率fc来计算。常用近似式是:

alpha = 2π · fc · Ts / (2π · fc · Ts + 1)

我习惯先算好一个常量,然后硬编码进去,而不是每次运行都实时算,因为实时算涉及浮点除法,费时间也没必要。比如采样周期1ms,截止频率10Hz,2π·fc·Ts就是0.06283,代入公式得到alpha约0.0591。如果用近似式fc ≈ α/(2π·Ts)反推,得到的fc是9.4Hz,差别不大,工程上可接受。

更精确的公式是考虑离散化误差的,Tustin变换之后的计算式:

alpha = 2·π·fc·Ts / (2 + 2·π·fc·Ts)

这个更准一些。但我实测下来,大部分项目用传统近似公式就够用,因为传感器噪声和目标频率本身就不是精确参数,差个0.5Hz根本体现不出来。

4.2 定点版本:无FPU单片机上的优化写法

如果你的单片机是Cortex-M0、8051这类没有硬件浮点单元的主控,用浮点运算会让CPU负载飙升。这时候可以把alpha转成定点小数来处理。比如alpha用Q15格式,即一个16位整数代表0~1之间的小数,1对应32768:

typedef struct { int32_t output_q15; uint16_t alpha_q15; } LowPassFilterQ15; int16_t lowpass_filter_update_q15(LowPassFilterQ15 *f, int16_t input_q15) { int32_t diff = (int32_t)input_q15 - f->output_q15; int32_t product = ((int32_t)f->alpha_q15 * diff) >> 15; f->output_q15 += product; if (f->output_q15 > 32767) f->output_q15 = 32767; if (f->output_q15 < -32768) f->output_q15 = -32768; return (int16_t)f->output_q15; }

这里输入输出都规范到Q15格式,即-1.0对应-32768,+1.0对应32767。如果你需要的是毫米单位的位移,可以自己定义一个比例,比如1mm对应100个LSB,然后做好映射。这样乘法用32位中间变量,不存在溢出问题,在16位MCU上也能轻松跑。

定点版本有个坑:alpha很小的时候,diff很大,乘出来的product可能很小,多次累加才能看到变化,这在整数运算下容易产生“台阶感”。解决办法有两个:一是用Q30甚至Q31格式,把中间精度提高;二是把滤波器状态量也放大,用更高精度的定点数表示输出值。我在一个用STM8做的低成本测距项目里就遇到过这个台阶问题,后来把滤波器状态改成32位、输入输出用16位映射,台阶感就消失了。

4.3 初值和抗饱和处理:别让启动瞬间坏了整个曲线

滤波器初值怎么设,大部分教程不会说,但实际项目里特别重要。

最合理的做法是把output初始化成第一次采样值,而不是0。原因很简单:单片机开机瞬间,传感器读到的是当前真实位移,如果滤波器从0开始慢慢爬,输出需要一个过渡过程才能接近真实值,这个过程中控制程序读到的位移就是错的,轻则启动抖动,重则导致执行器猛的冲击一下。我在做电动推杆位置控制时就遇到过,开机瞬间设备猛蹿一下,就是因为滤波初值从0开始导致的。

另外,注意滤波输出的限幅。如果位移信号被用于闭环控制,输出异常大的值会导致执行器超速,所以建议在滤波函数里加输出限幅,或者至少在调用处判断范围。有些传感器偶尔会吐出错误值,幅度超出量程几倍,低通滤波会把这种异常值逐渐吸收,导致输出慢慢偏移,虽然比瞬时跳变温和,但一样会对控制产生影响。更稳妥的做法是在滤波前做一次合理性检查,数据超出物理量程直接丢弃。

再一个容易忽略的点:非均匀采样。如果你的采样周期不稳定,比如主循环里因为其他任务执行时间抖动导致采样间隔忽长忽短,滤波效果就会变差。一阶低通滤波是基于采样周期Ts计算系数的,Ts变了,实际截止频率也跟着变。最稳妥的办法是用定时器中断做固定周期采样,或者用预留时间戳的方式计算实际间隔。如果只是在主循环里顺手采样,滤波效果会时好时坏,我建议至少把采样放进定时器中断里,哪怕优先级低一点,也比在主循环里强。

5. 参数整定:截止频率到底怎么定才靠谱

5.1 先了解自己信号的频率结构

整定滤波参数,第一步是搞清楚目标信号和噪声分别处在什么频段。这步不用频谱仪,靠经验和简单测试也能估个大概。

如果你处理的是电机位置信号,先看电机最高转速是多少。比如轮子最快每秒转2圈,编码器每圈1000线,那么位置信号的主频大约在2Hz以内,急加速急减速可能到5Hz左右。这时候如果编码器读数每秒跳变几十次,那这些跳变就是高频噪声,截止频率选在10~20Hz是合理的。

如果你处理的是机械臂关节角度,关节动作一般比轮子更慢,1~2Hz已经是很快的挥舞了,截止频率可以放在5~10Hz。如果你处理的是超声波测距,目标移动速度大概每秒1米,测距更新率是20Hz,那信号带宽也就是几赫兹,但超声波数据里往往有间歇性的大尖峰,截止频率压到5Hz以下效果更好。

我在实际项目里习惯这样操作:先用一个临时工程把原始数据串口打印出来,用串口绘图工具看波形。把目标运动跑起来,肉眼估计信号的主要变化周期;然后静止放置,观察噪声毛刺的跳变频率。这两步看完,截止频率的范围基本就出来了。

5.2 采样率、截止频率和alpha的关系

alpha的计算公式我们已经写过了,实际操作时先定采样率和截止频率,再算alpha。我列一个表格,假设采样率固定为1kHz(即Ts=1ms),不同目标截止频率对应的alpha值:

目标截止频率 fc近似计算 alpha ≈ 2π·fc·Ts/(2π·fc·Ts+1)输出响应大概什么样
1 Hz0.0063非常平滑,明显滞后,适合极低速慢变量
5 Hz0.0305平滑,滞后约50ms左右,适合一般位移显示
10 Hz0.0591兼顾平滑和响应,大多数位置闭环的起点
20 Hz0.1116响应较快,毛刺依然有一定残留
50 Hz0.2389接近直通,主要用于去掉极高频率干扰

如果采样率不是1kHz,那就按公式重新算。比如采样率降到100Hz,想要10Hz截止频率,2π·fc·Ts = 0.628,alpha为0.386,比1kHz采样下的0.0591大得多,滤波力度也弱得多。所以在低采样率下要获得同样的滤波效果,截止频率只能更低。

这个表格是近似值,但足够作为起点。真正要精细调,还是要靠示波器或者绘图工具观察实际效果。

5.3 实测调试的步骤和判断标准

调滤波器参数,我是这样操作的:

第一步,把滤波前后的数据同时通过串口输出,波形画在一起。滤波前的曲线毛刺多但响应快,滤波后的曲线平滑但有延迟。两条曲线叠在一起,能非常直观地看出滞后量和平滑效果。

第二步,做阶跃测试。让目标位置快速从一个值变到另一个值,观察滤波输出的过渡过程。如果输出要五六百毫秒才能跟上真实值,说明截止频率太低;如果输出依然有明显毛刺,说明截止频率太高。

第三步,也是对闭环最关键的:把滤波后的信号接入控制回路,观察系统是否有振荡。从某个截止频率开始,如果电机出现高频抖动的叫声,说明滞后太大导致系统相位裕度不足,需要提高截止频率。这个时候哪怕滤波后的曲线看起来还不够平滑,也必须以系统稳定性为准。

我自己调试时有个习惯:先选一个偏大的截止频率,比如50Hz,确认系统稳定,然后逐步降低,每次降低一半,直到出现系统抖动或数据平滑度不满意为止,再退回一档。这个“二分查找”式的调法效率很高,推荐一试。

5.4 相位滞后到底有多大的影响

一阶低通滤波在截止频率处,相位滞后大约是45度,频率越低滞后越不明显,频率越高滞后越大。如果信号主频率等于截止频率,滞后大约45度;如果信号主频率远低于截止频率,比如只有截止频率的十分之一,滞后大约只有几度,完全可以接受。但如果你把截止频率定得很低,比如1Hz,而信号里有3Hz的运动成分,那这部分信号的滞后就很大了,控制回路很容易出问题。

我在某项目里用一阶低通对编码器位移做10Hz滤波,实测滞后大概是15毫秒级别。对显示来说这个值微不足道,但对转速较快的闭环系统来说,15毫秒的相位延迟已经可能让PD控制器的微分项产生误判。所以结论是:显示和开环测量可以放心滤波,闭环控制要格外谨慎,必要时把截止频率放到信号带宽的5到10倍以上。

6. 常见问题与排查技巧实录

6.1 输出还是抖:先确认滤波真的生效了

很多人改完代码发现波形纹丝不动,第一个要检查的是alpha是不是被写成了0。alpha为0时滤波器输出完全冻结,曲线是一条直线。第二个检查点是滤波函数的调用频率是否和预期一致。如果你以为采样率是1kHz,实际主循环只跑了100Hz,那截止频率就是预期值的十分之一,滞后严重。第三个检查点是是否误把输出变量直接赋值给了输入,导致输入输出互相覆盖。

还有一个很容易被忽略的问题:数据类型的截断误差。16位整型做定点滤波时,如果alpha非常小,比如0.0063,乘以32767也就是206左右,然后右移15位,得到的增量极小,每次只有一点点的变化,如果输入变化幅度只有几十个LSB,可能根本推不动输出。这种时候就需要提高状态量的精度。

6.2 滞后太大:不是滤波的错,是参数选错了

滞后大,唯一的调整手段就是提高截止频率。但有一种情况是级联滤波导致的滞后叠加。我在一个项目里发现,数据链路里面其实叠了三个低通滤波器:传感器模块自身带一个、MCU里加了一个、显示端绘图时又平滑了一次。三个滤波器叠加,截止频率都不同,最终效果就是滞后三倍。排查方法很简单,把链路里每一个滤波点都找出来,如果不是必要就直接删掉,只保留一个。

如果确实需要更强的平滑效果,可以考虑用滑动平均滤波替代一阶低通。滑动平均滤波器在同样的平滑程度下,相位滞后通常比同截止频率的一阶低通更小,但它需要额外一段环形缓冲区存储数据。在RAM富余的单片机上,值得做这个替换。

6.3 启动瞬间输出乱跳:初值没处理好

开机时滤波器输出乱跳,几乎都是初值和限幅的问题。解决办法在4.3节已经讲过:初值用第一次采样值,开机后的前几十毫秒里不把滤波输出接入控制回路,等滤波器状态稳定后再启用。后者虽然不是滤波器本身的问题,但在工程上很实用,我在做伺服驱动器限位保护时就是这么处理的。

6.4 滤波后反而更糟:大概率是场景选错了算法

一阶低通滤波处理普通位移数据效果很好,但如果你的数据本质上是阶跃性质的,比如光电开关检测到的到位信号、限位开关的触发信号,低通滤波会把陡峭的边沿变成缓坡,这反而让系统判断“到位”的时机变慢。这种场景应该用去抖算法而不是低通滤波。

加速度计积分出位移的场景也同样。如果只做低通,漂移无法消除,整体趋势还是偏离,看起来滤波后曲线平滑了,但实际上精度更差。这时候必须上高通/低通组合,或者互补滤波。这也在前面讲过。

我把我在项目中总结的排查表放在下面,可以当做一个速查手册:

现象可能原因解决方案
输出完全不变化alpha为0、调用频率为0检查系数和调用位置
输出抖动但明显滞后截止频率太低提高截止频率到信号带宽5倍以上
系统闭环振荡滤波滞后导致相位余量不足提高截止频率或把滤波器移出反馈回路
启动时输出冲击滤波器初值为0初值设为第一次采样值,启动延时启用
静止时输出缓慢漂移传感器零偏或积分漂移加速度积分场景用高通去漂移,编码器场景检查机械松动
突变尖峰滤不掉噪声频带和信号重叠改用中值滤波先剔除尖峰,再一阶低通平滑

7. 我最后的一点体会

做了这么多次一阶低通滤波的工程实践,我的最大感受是:这个算法简单到几乎不配被称为“算法”,但它对参数关系的敏感度一点不亚于复杂的滤波手段。最典型的就是截止频率、采样率、相位滞后这三个参数之间的三角关系,很多人栽跟头不是因为代码写错了,而是因为没有把这三个参数放在一个系统里综合考虑。

如果你只是一个刚接触单片机的学习者,我建议你按这个顺序来掌握:先把浮点版本跑通,观察滤波前后波形;再用定点版本替换,感受性能差异;然后用一个实际的编码器测距项目做参数整定,体验一下“截止频率调低太滞、调高太毛”的纠结过程。这一个流程走完,你对低通滤波的理解就超过大多数照抄代码的人了。

另外还有一个扩展方向值得提一下:当一阶低通不够用的时候,不要急着上卡尔曼。可以先用“中值滤波+一阶低通”的组合试试,中值滤波负责剔除脉冲尖峰,低通负责平滑剩余高频。我的经验是,这个组合能解决70%的位移数据问题,而且实现比卡尔曼简单一个数量级。至于卡尔曼,等真的需要融合多传感器数据的时候再学也不迟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询