
ADC采样回来的信号带着毛刺传感器输出又抖又飘这时候很多人的第一反应就是加一阶数字低通滤波器。代码确实简单三行就能写完但真正让人卡住的是那个 α 系数到底怎么定。采样频率 1000Hz、想要截止频率 10Hzα 该给 0.05 还是 0.6网上搜到的公式版本不少有的给指数形式有的给比值形式照着抄又怕抄错。这篇文章就把 α 的来龙去脉、计算公式、单片机实现和常见坑全部讲透看完你就能直接照着算不需要再靠猜。1. 这个参数到底在解决什么问题一阶数字低通滤波器的经典递推公式长这样y[n] α * x[n] (1 - α) * y[n-1]其中 x[n] 是当前采样值y[n-1] 是上一次的输出α 就是那个决定滤波强度的系数。很多资料直接把 α 叫做“平滑因子”这个叫法容易让人误以为它是随便调的。实际上 α 不是经验值它由采样频率和截止频率共同决定背后对应的是一个模拟 RC 低通滤波电路离散化后的结果。1.1 为什么所有资料都在说 α 又不给值先看这个递推公式本身的含义。α 越大当前采样值 x[n] 的权重越大输出越“贴”原始信号噪声抑制能力变弱α 越小历史值 y[n-1] 占的比重大输出越平滑但信号变化也越迟钝。所以 α 本质上是在噪声抑制和信号滞后之间找一个平衡点。这个平衡点不能拍脑袋定必须和你的采样频率 fs、期望截止频率 fc 挂钩。比如同样想滤掉 50Hz 的干扰在 1000Hz 采样率下算出来的 α 可能是 0.06但在 100Hz 采样率下算出来可能就是 0.4 左右。如果直接照搬别人的 α0.06效果完全不是一回事。这就是为什么不能只背数值要会算。1.2 它是从模拟 RC 低通滤波器来的一阶模拟低通滤波器最常见的形式是 RC 电路传递函数是H(s) 1 / (1 sRC)它的截止频率 fc 和时间常数 τ 的关系是fc 1 / (2πRC) τ RC 1 / (2πfc)把这个模拟滤波器放到数字域采样周期 Ts 1/fs离散化之后那个“记忆”上一拍输出的系数会变成一个指数衰减项a e^(-Ts/τ) e^(-2π·fc/fs)而递推公式里的 α 实际上就是 1 减掉这个衰减项α 1 - e^(-2π·fc/fs)所以 α 不是一个孤立调出来的数字它是时间常数与采样周期共同作用的结果。理解了这一层再看到不同公式就不会乱。2. 频率参数与 α 系数的完整计算流程计算 α 之前必须先明确两个输入采样频率 fs 和截止频率 fc。这两个参数定错了后面算出来的 α 再精确也没用。2.1 先确定采样频率 fs采样频率是滤波器工作的“节拍”。它由你的 ADC 配置、传感器输出速率、程序循环周期共同决定。比如 STM32 的 ADC 定时触发采样每秒触发 1000 次fs 就是 1000Hz一个 IMU 传感器内部以 200Hz 输出数据fs 就是 200Hz。采样频率首先要满足采样定理至少大于信号最高频率的两倍。工程上一般会留余量取信号最高频率的 5 到 10 倍。以常见的振动监测为例机械振动主要能量在 50Hz 以内采样频率取 500Hz 以上才比较稳实际项目里直接用 1kHz 或更高。这个 fs 不一定是最终进入滤波器的原始采样率。如果 ADC 先做过抽取降采样那滤波器输入的采样频率就是降采样后的实际采样率不是 ADC 的硬件触发频率。这个细节很容易忽略我在项目里就吃过亏滤波器参数按 8kHz 算的实际数据流是 1kHz结果截止频率偏了 8 倍。2.2 截止频率 fc 怎么选截止频率是天平的另一个秤砣。它的定义是幅频响应衰减到 -3dB 的那个频率点。对于一阶低通来说它不是一堵“墙”截止频率以下的信号基本通过截止频率以上的信号逐渐衰减衰减速度大约是每十倍频程 20dB也就是常说的 -20dB/dec。选 fc 的原则很简单把你想保留的信号频率放在 fc 以下把你不想要的噪声频率放在 fc 以上。比如处理一个直流或缓变传感器信号有效信息集中在 5Hz 以内而高频噪声从 20Hz 往上就到处都是那 fc 可以取 5 到 10Hz。如果想让波形肉眼看起来平滑fc 可以再低一些如果需要在控制回路里快速响应fc 就得适当抬高。我个人的习惯是先用 fc 信号最高频率的 2 到 3 倍试跑一次再根据输出波形慢慢压低。不要一开始就把 fc 压得很低否则会发现波形倒是光滑了但信号突变被严重拉平整个系统响应慢半拍。2.3 α 的两种常用公式目前工程上用到的 α 计算公式主要有两种它们对应不同的离散化思路。第一种是上面推导过的指数形式也是我推荐优先使用的方式α 1 - exp(-2π·fc/fs)第二种是免指数运算的近似形式x 2π·fc/fs α x / (x 1)第二种公式在 fc 远小于 fs 时和第一种非常接近而且只需要一次除法在一些不支持 exp 函数的小单片机里很实用。拿 fs1000Hz 来算几组常见截止频率两边结果差异会更直观截止频率 fc公式1指数形式公式2比值形式1Hz0.006260.006245Hz0.030920.0304510Hz0.060890.0591150Hz0.269580.23902100Hz0.466520.38588从表里能看出来fc 在 fs 的 1/100 到 1/50 这个区间内两种公式误差很小但 fc 一旦到了 fs 的 1/10 以上差距就明显了。对于一阶低通通常建议 fc 不要超过 fs 的 1/10否则抗混叠和滤波效果都会变差也容易让系统稳定裕度下降。所以在合理使用区间内选哪个公式问题都不大但如果你希望和理论时间常数严格对应就用指数形式。3. 浮点实现到单片机移植的实操过程公式看懂了接下来就是落地。这一节给出一套可以直接抄的代码流程以及移植到不支持浮点运算的单片机上时怎么处理。3.1 从公式到可运行代码先看一个完整的 C 语言实现。为了不污染全局环境我习惯把滤波器的参数和状态封装成一个简单结构体#include math.h #define PI 3.14159265358979323846 typedef struct { float alpha; float y; } LowPass1; void lowpass_init(LowPass1 *f, float fs, float fc) { f-alpha 1.0f - expf(-2.0f * PI * fc / fs); f-y 0.0f; } void lowpass_reset(LowPass1 *f, float first_sample) { f-y first_sample; } float lowpass_process(LowPass1 *f, float x) { f-y f-alpha * x (1.0f - f-alpha) * f-y; return f-y; }这段代码核心就一行y alpha * x (1-alpha) * y。alpha 只需要在初始化时算一次不要在循环里反复调用expf否则会白白浪费 CPU 时间也让代码变得难看。还有一点必须注意初始化时要用第一个有效采样值去重置y。如果你把y初始化为 0而真实信号是 2.5V 的直流偏置输出会从 0 慢慢爬到 2.5V这个爬升过程会误导后面的逻辑。用第一次采样值初始化输出从第一拍开始就在真实信号附近省去很多麻烦。3.2 没有 FPU 的单片机怎么算很多 Cortex-M0、51 这类单片机没有硬件浮点单元直接写 float 运算会被编译器转成软件浮点速度慢还占 Flash。这种场景下可以把 α 转成定点数。最简单的做法是把 α 放大 1024 倍所有运算在整数域完成最后右移 10 位还原uint16_t alpha_q (uint16_t)(alpha * 1024.0f 0.5f); uint16_t y x[0]; for (i 1; i n; i) { y (uint16_t)(((uint32_t)alpha_q * x[i] (uint32_t)(1024 - alpha_q) * y) 10); }这个写法里alpha_q是放大后的 α1024 - alpha_q是放大后的(1-α)。每次输出就是一个加权平均。采样值如果是 12 位 ADC也就是 0 到 4095那么(1024 - alpha_q) * y最大值大约在 1024 乘 4095 的量级用 uint32_t 完全够。如果希望滤波器平滑得更细腻可以把放大倍数提高到 4096 或 65536。提高倍数会改善低 α 情况下的量化误差但也要注意乘法结果会不会超过变量范围。定点实现的本质是用分辨率换计算速度选多大倍数需要根据你的 ADC 位数和中间变量宽度来定。3.3 采样率和截止频率在运行时要能改实际项目中采样率不一定是固定常量比如设备刚启动时用 1kHz后续切换到 500Hz。这种情况下滤波器初始化不能只做一次要在采样率切换后重新计算 α。更稳妥的做法是直接封装成函数在配置变更时调用一次lowpass_init。遇到需要在运行时动态调整截止频率的场景比如用户可调滤波强度千万别直接硬切 α。硬切会导致输出发生跳变相当于给信号加了一个阶跃干扰。这时候可以对 α 本身再做一次低通平滑alpha_target 1.0f - expf(-2.0f * PI * fc_new / fs); alpha_actual 0.95f * alpha_actual 0.05f * alpha_target;这样 α 的变化过程是渐进的输出信号不会突然跳一下。这种方法适合在控制面板上实时调节滤波效果的场景。4. 常见问题与参数选择避坑一阶低通看上去简单实际使用时踩坑的人不少。最典型的几个问题基本都集中在 α 算错、初始化不对、以及把滤波器的行为想得过于理想。4.1 症状对应排查表现象可能原因调整方向输出还是很抖α 太大截止频率太高降低 fc重新计算 α信号滞后明显α 太小截止频率太低提高 fc重新计算 α上电瞬间输出跳变y 初始化为 0用第一次采样值初始化滤波结果看起来有阶梯感定点分辨率太低提高放大倍数或改用 float滤波器输出幅值被明显压低fc 太接近有效信号频率提高 fc相同代码换板子后效果变化fs 没跟着改确认实际采样频率这几个问题里最隐蔽的是最后一个。很多人把代码从 8kHz 采样率的板子移植到 1kHz 采样率的板子上fs 宏定义没改最终得到的实际截止频率和预期差很远。移植时第一件事就是看采样频率。4.2 三个容易踩的坑第一个坑是把 fc 当成角频率。公式里的 fc 是普通频率单位是 Hz不是角频率 rad/s。如果你把角频率 ω2πfc 当成 fc 直接代进公式算出来的 α 会小掉 2π 倍滤波会变得异常迟缓。我的经验是写代码时所有变量的单位写清楚命名里带_hz、_hz_sps这种后缀避免单位混淆。第二个坑是在滤波循环里重复计算 α。有些初学代码写成这样for (i 0; i n; i) { alpha 1.0f - expf(-2.0f * PI * fc / fs); y alpha * x[i] (1 - alpha) * y; }虽然功能上没错但每次迭代都在调 expf严重影响实时性。α 只和 fs、fc 有关和数据无关完全可以在初始化时算一次。第三个坑是忽略了截止频率低于奈奎斯特频率的前提。一阶低通虽然可以设置 fc 接近 fs/2但那样滤波效果几乎没有意义。传统经验里fc 至少要比 fs/2 小一个数量级才有一阶滤波该有的平滑效果。如果非得在靠近奈奎斯特频率的位置处理信号那要考虑的已经不是一阶低通了而是抗混叠和采样率本身的问题。4.3 拿 MATLAB fdatool 导出的系数做对照很多做信号处理的人习惯打开 MATLAB 的 fdatool也就是新版本里的 Filter Designer设计完滤波器之后直接导出系数。如果你想用一阶低通别直接把 fdatool 导出来的 b0、a1 往这里的 α 公式里硬套。fdatool 默认生成的 Direct-Form I 结构是y[n] b0·x[n] b1·x[n-1] - a1·y[n-1]以 fs1000Hz、fc10Hz 为例MATLAB 里执行butter(1, 10/500)得到的典型系数大约是 b0≈0.0628、b1≈0.0628、a1≈-0.8744。换算之后它的单步增益在 0.06 左右和我们用指数公式算出来的 α0.06089 很接近但并不是同一个东西。fdatool 里用的是双线性变换得到的是带一个前馈项的滤波器结构而本文里的单系数一阶低通是匹配指数衰减的结构。两者幅频响应相似相频响应略有区别混用容易让参数调试变得混乱。如果你确实需要从 fdatool 导出高阶滤波器系数那是另一套流程。但对于一阶低通直接算 α 更简单、更可控没有必要把 fdatool 拉进来。5. 选参经验和小技巧内容写到这儿基本的计算公式和代码实现已经足够用了。最后再分享一些我实际调参时积累的经验这些不属于任何标准文档但很实用。5.1 延迟受不了的时候怎么办一阶低通带来的滞后是固有特性α 越小滞后越大。如果你发现滤波后的信号在控制回路里导致响应太慢不要一味降低 fc。可以先算一下当前截止频率对应的时间常数 τ1/(2πfc)。在 1kHz 采样率、fc10Hz 的情况下τ 大约是 15.9ms这意味着信号突变后输出要达到最终值的 63% 需要约 16ms。这个延迟在温度采集、慢速传感器场景下无所谓但在电机电流环这类高速控制场景里就很致命。如果控制回路对延迟极其敏感可以考虑用更高阶的互补滤波器或者把滤波器从控制回路里移除只在显示或数据记录环节做平滑。滤波不是越狠越好关键是放对位置。5.2 用波形而不是单看数字判断 α调参时建议用一段真实采样数据离线跑仿真把原始信号和滤波后的信号画在一张图里对比。不要只看滤波输出是否平滑要同时看它和原始信号之间的相位差。如果噪声降低了 90%但信号峰值点被往后拖了 20 个采样点那这个 α 对于实时系统来说可能并不合适。单看均值或者单看平滑度都会误判。我常用的一个流程是先用记录仪抓一段最恶劣工况下的原始数据离线算不同 fc 对应的输出然后挑一个噪声可接受、延迟又不影响逻辑的 fc。确定之后再烧到板子上验证一遍。这样比在板子上反复改参数、看示波器效率高得多。5.3 加一个宏就能让代码复用最后给一个工程小建议把 α 计算封装成独立的初始化函数而不是在多处代码里手写公式。这样顶层的业务代码只关心“我要 10Hz 截止频率”不关心内部是 exp 还是近似。后续换采样率、换传感器、调滤波强度都只需要改一个初始化参数。等积累几次之后你会发现一阶数字低通滤波器最值钱的其实不是那三行递推代码而是能不能把 α 从采样频率和截止频率里算得明明白白。