☰
一阶数字低通滤波器α系数计算与单片机实现详解
2026/9/25 1:51:36 网站建设 项目流程

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 函数的小单片机里很实用。

拿 fs=1000Hz 来算几组常见截止频率,两边结果差异会更直观:

截止频率 fc公式1:指数形式公式2:比值形式
1Hz0.006260.00624
5Hz0.030920.03045
10Hz0.060890.05911
50Hz0.269580.23902
100Hz0.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]

以 fs=1000Hz、fc=10Hz 为例,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 采样率、fc=10Hz 的情况下,τ 大约是 15.9ms,这意味着信号突变后,输出要达到最终值的 63% 需要约 16ms。这个延迟在温度采集、慢速传感器场景下无所谓,但在电机电流环这类高速控制场景里就很致命。

如果控制回路对延迟极其敏感,可以考虑用更高阶的互补滤波器,或者把滤波器从控制回路里移除,只在显示或数据记录环节做平滑。滤波不是越狠越好,关键是放对位置。

5.2 用波形而不是单看数字判断 α

调参时建议用一段真实采样数据离线跑仿真,把原始信号和滤波后的信号画在一张图里对比。不要只看滤波输出是否平滑,要同时看它和原始信号之间的相位差。如果噪声降低了 90%,但信号峰值点被往后拖了 20 个采样点,那这个 α 对于实时系统来说可能并不合适。单看均值或者单看平滑度都会误判。

我常用的一个流程是:先用记录仪抓一段最恶劣工况下的原始数据,离线算不同 fc 对应的输出,然后挑一个噪声可接受、延迟又不影响逻辑的 fc。确定之后再烧到板子上验证一遍。这样比在板子上反复改参数、看示波器效率高得多。

5.3 加一个宏就能让代码复用

最后给一个工程小建议:把 α 计算封装成独立的初始化函数,而不是在多处代码里手写公式。这样顶层的业务代码只关心“我要 10Hz 截止频率”,不关心内部是 exp 还是近似。后续换采样率、换传感器、调滤波强度,都只需要改一个初始化参数。等积累几次之后,你会发现一阶数字低通滤波器最值钱的其实不是那三行递推代码,而是能不能把 α 从采样频率和截止频率里算得明明白白。

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

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

立即咨询