
写这篇东西的起因是我在同时处理传感器数据平滑和一组时序指标的实时计算时发现团队里几个人对同一段代码给出了完全不同的解释搞控制系统出身的同事叫它“一阶低通滤波”做数据分析的同事坚持说这是“指数移动平均EMA”。两边都没错但真正有意思的是这俩名字底下其实是同一套数学结构。把事情彻底掰开揉碎之后你会发现很多工程上的模糊地带都会变得特别敞亮。指数移动平均和一阶低通滤波几乎出现在你能想到的所有实时数据处理场景里惯导模块的加速度计读数、热电偶的温度采样、编码器的速度估算、股票价格的均线指标、伺服电机的电流反馈整形。它们的共同任务就一句话从一堆带噪声的离散采样点里把趋势留下来把高频抖动压下去。这篇文章不绕弯子我直接讲清楚它们的数学本质、工程实现、参数标定以及我在实际项目里踩过的那些坑。如果你正在为“滤波参数到底怎么调”发愁或者想搞明白为什么同一个公式在论文里长成两种模样这篇内容应该能帮你省下不少查资料的功夫。看完之后你至少能回答三个问题α 和截止频率怎么互相换算离散实现时该注意什么为什么某些场景下这个“最简滤波器”反而是最优解。1. 核心概念两个名字一个爹先说结论离散域的指数移动平均和连续域的一阶低通滤波在数学上是同一个系统只是从不同的学科视角给了它不同的名字。理解这件事的起点是先把两套表达式都写出来对照着看。1.1 指数移动平均的离散递推式金融和数据分析里常见的形式是这样y[n] α · x[n] (1 - α) · y[n-1]其中 x[n] 是当前采样值y[n] 是当前输出y[n-1] 是上一次输出α 是平滑系数取值范围是 0 到 1。另一种等价的写法是增量式y[n] y[n-1] α · (x[n] - y[n-1])这两种形式在浮点运算里结果完全一致但增量式的好处是它告诉你一个直观事实每次输出只是在旧输出的基础上朝新采样值的方向挪动一小步。α 越大挪动的步子越大对新数据越敏感α 越小新数据的影响越微弱曲线越平滑。这个递推式的名字里带着“指数”是因为它的脉冲响应是一个指数衰减序列。如果输入只有一个冲击那后续每个周期的输出都是上一个周期输出的 (1-α) 倍形成一个等比衰减的尾巴。这正是指数移动平均名字的由来。1.2 一阶低通滤波的连续域原型再来看信号处理和控制系统里的表述。一阶低通滤波器的连续传递函数长这样H(s) 1 / (1 s·τ)其中 τ 是时间常数单位是秒s 是拉普拉斯算子。这个式子描述的是一个模拟电路就能搭出来的系统——一个电阻串联一个电容从电容两端取电压就是经典的一阶 RC 低通滤波器。它的幅频特性是低频信号几乎无损通过高频信号按每十倍频程 20dB 的速率衰减。-3dB 截止角频率 ωc 1/τ换算成频率就是 fc 1/(2πτ)。我当年第一次把这两个式子放在一起看的时候心里冒出的想法是一个讲离散递推一个讲连续频域这俩怎么可能是同一个东西直到我走了一遍从连续域到离散域的离散化推导才彻底想明白。1.3 从连续滤波器到离散 EMA 的完整推导在数字信号处理中把连续滤波器变成可编程实现的离散滤波器最常见的做法是双线性变换或后向欧拉法。这里我用后向欧拉来推一遍因为它的数学过程最直观而且结果跟工程实践高度吻合。后向欧拉法把拉普拉斯算子 s 近似为s ≈ (1 - z⁻¹) / T其中 T 是采样周期z⁻¹ 是单位延迟算子。把这个近似代入 H(s) 1 / (1 s·τ)H(z) 1 / (1 τ · (1 - z⁻¹) / T)分子分母同乘 TH(z) T / (T τ - τ·z⁻¹)把分母整理成 1 - (1-α)·z⁻¹ 的形式H(z) α / (1 - (1-α)·z⁻¹)其中α T / (T τ)到这里结构已经非常清楚了这个离散传递函数的输入输出关系正是 y[n] α·x[n] (1-α)·y[n-1]跟指数移动平均完全一致。所以结论是你写的那行 y y alpha * (x - y)本质上就是一个经过后向欧拉离散化的一阶低通滤波器。两边讲的不是“类似”而是“同一个东西在两种语境下的不同投影”。2. 参数换算α、τ、截止频率三者怎么互相转搞清楚数学同源性之后下一个实际问题就是我手里有一个需要滤波的传感器信号采样率是 1kHz我想滤掉 5Hz 以上的波动α 应该设多少这道题就是把上面的公式倒过来用。先把目标截止频率换算成时间常数τ 1 / (2π·fc)然后用采样周期 T 和 τ 计算 αα T / (T τ)把假设数字代进去T 1/1000 0.001 秒fc 5Hzτ 1/(2π×5) ≈ 0.0318 秒α 0.001 / (0.001 0.0318) ≈ 0.0305。也就是说在 1kHz 采样率下α 大约取 0.03就能得到一个 -3dB 截止频率约为 5Hz 的低通滤波器。这里有个必须提醒的点用这个公式算出来的 α对应的截止频率是模拟域意义上的 -3dB 点。由于离散化的映射关系数字域的实际截止频率会跟这个目标值存在一个微小的偏差具体大小取决于 α 的绝对值。α 越小也就是采样率相对截止频率越高偏差越小。对绝大多数工程场景来说这个偏差完全在可接受范围内。我在实际项目里通常不会把数学算到极致而是用这个公式算出初值然后通过观测波形微调。因为真实系统的噪声特性和你对“平滑度”的主观感受很难用单一频率指标完全刻画。算出一个量级正确的初值再用频谱分析或者直接看时域波形调是最有效率的做法。另外还有一个常见的工程近似值得记住当 α 远小于 1 时截止频率可以用这个简易公式估算fc ≈ α / (2π·T)这个近似式在 α 小于 0.1 时误差很小而且特别好心算。比如 1kHz 采样、α 0.01那 fc 大约就是 0.01 / (2π×0.001) ≈ 1.59Hz。我经常在调试现场用这个公式快速估一下当前参数的量级心里先有个数再动手调。2.1 α 的可视化直觉很多初学者对 α 和响应速度之间的关系缺乏直觉。我给一个参照系α 越大滤波器的“记忆”越短输出跟随输入的速率越快但噪声也保留得越多α 越小记忆越长输出越平滑但滞后也越严重。这里有一个常用的量化工具叫时间常数 τ 的离散对应。连续系统里阶跃响应上升到终值的 63.2% 需要 τ 这么长时间。对应的离散实现里用掉的时间大约是 τ ≈ T/α由 α T/(Tτ) 在 α 很小时反推得到。所以一个 α 0.1 的滤波器在 1kHz 采样下响应到目标值的大约六成需要 10 毫秒左右。想要更平滑就调小 α想要更灵敏就调大 α这是最核心的取舍。2.2 与滑动平均的差异说到平滑很多人会自然想到滑动平均moving average也就是取最近 N 个点求均值。既然都是平滑选谁我的经验是分场景滑动平均没有递归结构不存在累计误差实现起来也无脑但需要缓存 N 个历史值内存开销随 N 线性增长而且相位滞后是线性的、比较难调。EMA 只存一个状态变量内存几乎可以忽略计算量是常数级别适合嵌入式和高频场景。缺点是它对数据的“记忆”理论上是无限长的只是越老的数据权重越低。从频域角度看滑动平均的频率响应有一个固定的零点梳状结构在特定频率处会出现完全陷波而 EMA 的幅频响应是单调下降的。如果你的噪声集中在某个特定频率滑动平均可能在那个频点附近表现更好如果噪声是宽频的EMA 的性能通常更令人满意。有一个实际体验我记得很清楚做编码器速度估算时滑动平均窗口稍大一点响应就慢得让人难以接受而 EMA 在同等平滑效果下滞后要小不少。原因在于 EMA 对近期数据赋予了更高的权重而滑动平均对窗口内所有数据一视同仁看起来“平滑”实际上把最新的有效信息也平均掉了。3. 实操视角不同场景下的参数整定方法理论都知道之后实操才是真正分高下的地方。这一节我把最常见的几类使用场景单独拎出来讲一讲参数整定的思路和细节。3.1 传感器噪声滤除这是 EMA 最常见的应用。我用 MPU6050 这类 IMU 模块做倾角测量时原始读数带的高频抖动非常明显。此时的核心目标是让滤波后的曲线“稳而准”。参数整定思路分三步第一步先确定采样率。IMU 一般可以配置到 100Hz 到 1kHz我通常建议在功耗和计算资源允许的前提下采样率越高越好因为过采样本身就能帮助抑制噪声。第二步估算噪声的主要频率范围。把传感器静止放在桌面上用串口抓一段原始数据在电脑里做一次 FFT找出抖动能量聚集的频段。假设抖动主要集中在 10Hz 以上那我希望截止频率砍到 5Hz 以下这样不平滑都难。第三步用公式算 α 初值再现场微调。还以 500Hz 采样为例想砍到 5Hzτ 0.0318 秒T 0.002 秒α 0.002/(0.0020.0318) ≈ 0.059。取 0.06 起步看波形如果还是抖就降到 0.04如果反应太迟钝就升到 0.08。这样调出来的参数通常比拍脑袋设一个 0.1 或者 0.01 要靠谱得多。3.2 控制环路的反馈整形在控制领域一阶低通滤波经常被用在反馈通道上。比如电机电流环的采样信号如果不用滤波器PWM 开关噪声会直接进入控制环引起电流波动甚至振荡。但控制环路的滤波有一个特殊约束相位滞后会影响系统稳定性。你在反馈通道上加了一个低通滤波器就等于给整个闭环增加了一个滞后环节相位裕度会下降。所以这里的参数整定不能只盯着“平滑”还要盯着“系统稳不稳”。我一般会用两个指标约束参数选择一是滤波器引入的相位滞后在穿越频率附近不能超过某个阈值比如 10 到 15 度二是滤波后的信号噪声峰峰值要低于控制要求的纹波范围。具体做法先测出系统的穿越频率 fc_loop然后把滤波器的截止频率设得比它高 5 到 10 倍。比如穿越频率是 50Hz滤波器截止频率可以取 250 到 500Hz。这样做能保证在穿越频率附近滤波器带来的相位滞后很小不至于影响稳定性。如果你发现这个约束和噪声抑制的需求冲突了那就得考虑改用更高阶的滤波器或者采用状态观测器来避免滞后问题。这里我踩过一个很典型的坑某次做直流电机速度闭环我看速度反馈波形毛刺多为了画面好看把 EMA 的 α 调得很小截止频率压到比系统穿越频率还低。结果电机跑起来开始低频振荡速度曲线变成了一条上上下下的蛇形。后来用带宽分离的思路重新整定把滤波器截止频率拉高到穿越频率的三倍以上波形既平滑系统也稳定了。3.3 金融时序数据处理在量化分析里EMA 最常见的用法之一是计算趋势类指标比如 MACD。这里的“滤波”目标不是还原物理量的真实值而是提取价格走势的趋势成分。金融数据的特征是非平稳、强噪声、事件驱动。用 EMA 时通常不追求精确的截止频率而是按“周期”来定义参数。比如常见的 12 周期 EMA、26 周期 EMA这个周期数 N 和 α 的换算关系是α 2 / (N 1)如果 N12α≈0.154N26α≈0.074。这套换算在技术分析领域已经约定俗成几乎所有的行情软件都是这么算的。我做回测时还会额外注意一件事金融数据存在跳空、停牌这类异常事件普通的 EMA 递推会把这类异常点直接计入状态导致指标在一段时间内处于失真状态。实践中我会对输入数据做预处理比如剔除异常值或对跳空做复权处理避免脏数据污染滤波器状态。4. 嵌入式环境下的工程实现细节如果上面的内容是从算法角度讲 EMA这一节切换到工程实现视角。嵌入式环境跟桌面端有本质差异可能没有浮点运算单元数据类型位数有限实时性要求高。在这种环境下实现 EMA有很多用模拟器永远遇不到的坑。4.1 基于定点数的高效实现在常见的 Cortex-M 系列单片机或者低端 DSP 上浮点运算不是不能用但相对于定点运算慢不少。如果你的系统里 EMA 只是偶尔算一次那无所谓如果它是某个控制环里每个采样周期都要跑的环节浮点开销可能就会成为瓶颈。一个经典的做法是把 α 近似为 1/2^k 的形式。因为右移 k 位的运算在硬件上是一两个时钟周期的事而浮点乘法少说也要几十个周期。用这个近似之后EMA 的更新变成一个加法和一个右移y y (x - y) k这里 k 相当于 α 1/2^k。查一下之前的换算α 0.03 时最接近的 2 的幂是 1/32 ≈ 0.03125对应 k5。如果你不追求极端精度这个近似完全够用。定点实现时最需要注意的是整数除法和符号位移的坑。右移负数和正数的行为在 C 语言标准里是有区别的如果 (x - y) 可能为负建议先判断符号再位移或者使用算术右移大多数编译器在带符号类型上默认做的就是算术右移但为了可移植性还是建议显式处理。4.2 状态变量与初始化的细节EMA 的核心状态只有一个变量 y它的初值直接影响启动阶段的输出。我见过不少实现直接把 y 初始化成 0结果系统一上电输出从 0 慢慢爬到真实值这段爬升的暂态过程在某些场景下会导致误动作。好的做法是如果系统允许用第一个采样值初始化 y也就是 y[0] x[0]。这样输出从一开始就跟真实值接近不需要等待收敛。对于传感器滤波这个做法几乎零成本强烈推荐。另一个和初始化相关的问题是状态变量的类型。如果你在定点平台上做滤波y 的精度决定了稳态时的纹波大小。举个例子用 8 位整数保存 y而真实信号本身的幅度范围是 0 到 255那滤波器输出的分辨率就只有 1 个 LSBα 再小也无法消掉量化噪声。经验法则状态变量至少要比输入数据宽 4 到 8 位。输入是 12 位 ADC状态变量至少用 16 位输入是 16 位状态变量建议用 32 位。否则你会遇到一个诡异现象信号已经足够平滑了但输出曲线总是有一两个 LSB 的反复跳动怎么调 α 都没用。4.3 时间基准变化导致的参数失真EMA 的 α 是跟采样周期绑定的α T/(Tτ)。如果你的采样周期是不稳定的——比如主循环里轮询传感器、偶尔被更高优先级的任务抢占——那 EMA 的实际截止频率就会跟着采样周期漂移。在时间抖动不能接受、但又需要稳定滤波特性的场合我有两个建议第一个建议是改用基于时间戳的增量式计算。每次更新时不使用固定 α而是根据实际间隔 dt 重新计算等效 αα 1 - exp(-dt/τ)这个公式是从连续域阶跃响应的离散化直接导出的在变采样周期下依然能维持物理含义不变的时间常数 τ。代价是多算一个指数函数如果 CPU 紧张可以用查表或者多项式逼近。第二个建议是干脆把滤波移到定时器中断里做。用固定的采样基准去读传感器和执行滤波主循环只负责消费滤波结果。这样 α 可以保持常数代码也更简单。我大部分项目中都倾向这种方案因为它把采样抖动和滤波特性完全解耦了。4.4 参数突变与状态重置还有一种场景容易被人忽略滤波器的输入源突然切换或者系统从掉线状态恢复过来。比如你有一个冗余传感器系统A 通道故障后切到 B 通道。此时 B 通道的当前读数和 A 通道的滤波输出可能存在一个很大的差值如果 EMA 状态没有重置滤波输出会以一个较大的 α 步长缓慢逼近新值产生一个长长的过渡斜坡。处理方式很简单在信号源切换的事件里把 EMA 的 y 重置为当前新通道的首个采样值。这个操作看起来不起眼但在航空航天、医疗器械这类对信号连续性要求很高的场景里能省掉一大堆时序上的麻烦。5. 常见问题与排查技巧实录最后这一部分把我在多年实践中遇到的高频问题整理成一个速查表。这些坑的特点是不踩不知道踩了之后回头看都觉得“怎么这么蠢”。5.1 问题速查表现象可能原因排查方法解决方案输出完全不跟随输入α 为 0 或状态未更新打印 y 和 x 比对检查递推式是否正确执行确保 α 非零输出跳变剧烈α 过大看频谱确认滤波效果调小 α或用频响公式重新计算启动阶段有长斜坡状态 y 初值为 0观察前 100 个点输出用首个采样值初始化 y固定 α 但滤波效果时好时坏采样周期不稳记录相邻采样的时间戳改定时器采样或基于 dt 重算 α输出有打不开的细小纹波状态变量位宽不足把 y 的位宽翻倍测试加宽状态变量至输入位宽 4 或更多定点实现时数值溢出回绕差值 (x-y) 超出类型范围打印中间变量用更宽类型存差值或限制输入范围输出长期偏离真实值输入含有直流偏置或传感器未校准对比原始数据均值先校准传感器再做滤波切换输入源后过渡过长状态 y 未重置观察切换瞬间波形事件触发时重置 y5.2 我亲历的两个疑难问题第一个是 ADC 数据滤波时遇到的“拖尾”。现象是电机急停之后电流读数虽然主体掉得很快但尾巴上总有一段长时间的小幅漂移看起来像是传感器出了问题。排查了很久才发现罪魁祸首是滤波前的数据里有一个非常低频的偏置漂移。EMA 对这种极低频分量几乎没有任何衰减所以它被完整地传递到输出端。解决办法不是调滤波器而是先做一次硬件上的偏置校准或者软件上的漂移补偿。这个案例让我意识到滤波不是万能的它只能处理它该处理的那部分问题。第二个是浮点平台的精度问题。在 PC 上用 Python 做回测时一切正常同样的公式移植到嵌入式板子上的单精度浮点环境后输出出现了一种周期性的微小跳动。原因是单精度浮点只能表示约 7 位十进制有效数字当 x 和 y 的数值都很大的时候增量 α·(x-y) 可能会被舍入到不可忽略的误差。解决方法是改用双精度或者在累加时调整计算顺序。这类问题在仿真是发现不了的只有真机长时间跑才会暴露。5.3 调试技巧与观察点调试滤波算法时最有用的工具是三条曲线原始输入、滤波输出、两者之差。输入和输出告诉你“平滑得怎么样”差值告诉你“滤掉了什么”。如果差值里还有明显的周期性分量说明截止频率设得还不够低如果差值几乎为零说明大部分信号被保留滤波器形同虚设。另一个我常用的技巧是给系统一个阶跃输入观察滤波输出的上升过程。在阶跃响应曲线上你可以非常直观地看到时间常数从阶跃开始到输出到达目标值的 63.2%所需时间就是 τ。我当时用这个办法校准了一批传感器的滤波参数比对着频谱图瞎猜高效得多。最后说一个心法别把参数调得过于“激进”。平滑度提升的边际收益是递减的而滞后和相位损失的代价却是线性甚至超线性增长的。我见过不少工程师为了把波形调得“像丝一样滑”把 α 压到特别小结果系统动态响应一塌糊涂。滤波的本质是信息取舍你在哪个频段保留信息必然在时域上付出相应的滞后代价。做一个清醒的取舍者比做一个激进的平滑者重要得多。回到开头那个问题指数移动平均和一阶低通滤波到底是不是同一个东西我现在可以给一个简洁的答案它们是同一个物理系统在连续域和离散域的两个化身。理解这层关系之后你在任何一门课里学到的直觉——关于时间常数、截止频率、阶跃响应的所有经验——都可以无缝移植到另一门课的代码里。这种“底层相通”的洞察才是比任何具体公式都值钱的东西。