单片机位移测量中的一阶低通滤波:原理、代码与参数整定指南
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截止频率10Hz2π·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对应32768typedef 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对应-327681.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即Ts1ms不同目标截止频率对应的alpha值目标截止频率 fc近似计算 alpha ≈ 2π·fc·Ts/(2π·fc·Ts1)输出响应大概什么样1 Hz0.0063非常平滑明显滞后适合极低速慢变量5 Hz0.0305平滑滞后约50ms左右适合一般位移显示10 Hz0.0591兼顾平滑和响应大多数位置闭环的起点20 Hz0.1116响应较快毛刺依然有一定残留50 Hz0.2389接近直通主要用于去掉极高频率干扰如果采样率不是1kHz那就按公式重新算。比如采样率降到100Hz想要10Hz截止频率2π·fc·Ts 0.628alpha为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%的位移数据问题而且实现比卡尔曼简单一个数量级。至于卡尔曼等真的需要融合多传感器数据的时候再学也不迟。