单片机ADC采集值乱跳?C语言十大滤波算法详解与工程选型

发布时间:2026/9/16 10:56:29
单片机ADC采集值乱跳?C语言十大滤波算法详解与工程选型 简介面向具备一定C语言基础的单片机及嵌入式开发者的ADC滤波算法资料包聚焦STM32平台下模拟量采集的噪声抑制与信号处理适合传感器数据、仪表采集等场景。压缩包共82个文件以37个C源文件和35个头文件为主体另含启动文件、Keil工程配置、HEX固件及说明文本整体仅206KB结构紧凑适合快速移植。内容覆盖移动平均、滑动平均、中值、指数平均、巴特沃斯、卡尔曼、维纳等十大经典滤波算法并配有LCD显示、串口调试等外围模块代码便于在真实硬件上观察滤波效果。每个算法均以独立模块组织配合简洁的调用接口可直接嵌入现有项目减少重复开发成本。已有1984人学习浏览适合正在做数据采集、传感器预处理或需要提升AD采样稳定性的工程师参考。1. 为什么单片机ADC采集值乱跳C语言十大滤波要解决的第一个问题给水泵电流传感器做采集时ADC读到的值在4.02mA和4.17mA之间来回跳传感器标定书上写着误差±0.5%。把示波器接到信号调理输出端看到的却是毛刺叠加在有用信号上。ADC输出跳动不一定是电路坏绝大多数情况是两类问题参考电源不干净、信号在采样瞬间被噪声调制。C语言十大滤波算法就是嵌入式社区流传的一套用软件平滑ADC采样值的经典做法包含限幅、中位值、滑动平均、一阶低通等十种思路覆盖从消除单点毛刺到抑制周期性干扰的各种场景。做51、STM32这类单片机AD采集的工程师都值得在工程里备一套。接下来按噪声来源、算法选型、C语言实现、STM32落地的顺序把这条链路说透。2. 十大滤波算法的三种分类与单片机ADC场景选型2.1 为什么ADC原始值会跳噪声来源先定性再滤波ADC转换的本质是采样保持加量化。采样瞬间输入引脚上的电压同时受三个东西影响传感器输出本身的有用信号、线路耦合进来的电磁干扰、ADC内部采样电容从引脚抽取电荷产生的跌落。电磁干扰通常表现为共模或差模的高频毛刺电源纹波表现为与开关频率同步的周期性波动而采样电容的电荷注入表现为每个采样点上的随机抖动。定量看12位ADC在3.3V参考下1LSB约等于0.8mV只要PCB走线耦合进来几毫伏噪声量化结果的最低3位就会无规则翻转。软件滤波不是把假信号变真而是根据已知的信号特点把“物理上不可能出现的变化”在代码里滤掉。硬件上串RC抗混叠、参考电压加去耦是第一步软件上的十种滤波算法是第二道工序两道工序各管一段不能互相替代。2.2 十种算法的本质限幅、平滑、抗脉冲三族嵌入式圈子里流传的十大滤波算法并不是一个标准委员会的产物而是多年工程实践的整理。这十个算法可以按处理思路分进三个族看下表算法族类核心思路常用参数限幅滤波限幅相邻值差超阈值则丢弃或钳住阈值A按信号最大变化率估中位值滤波抗脉冲连续N次采样取中间值N取5/7/9等奇数算术平均滤波平滑连续N次求平均N取4~16递推平均滤波滑动平均平滑滑窗内平均每采一次替换最旧值窗口N取4~32中位值平均滤波抗脉冲平滑去最大最小后对剩余求平均N取3~14限幅平均滤波限幅平滑先限幅再平均阈值AN取4~16一阶滞后滤波一阶低通平滑本次输出为上次输出与新值的加权和系数a取0~1加权递推平均滤波平滑新值权重高按比例递减窗口N取3~20消抖滤波抗脉冲连续M次同值才确认阈值N、计数M限幅消抖滤波抗脉冲先限幅再消抖阈值计数十种算法里没有一个是万能滤波器。限幅族处理尖峰平滑族压低随机噪声抗脉冲族处理偶发干扰。中位值平均、限幅平均这类组合算法本质是把两类需求叠加代价是代码量和响应时间同时增加。想清楚这一点参数就好设了。2.3 按信号特点选型慢变信号和快变信号的取舍选型先看信号变化速度。水温、液位、光照这类慢变信号采样周期在100ms级别尖峰少直接滑动平均窗口取8到16如果偶尔冒出一两个尖刺换成中位值平均N取5或7就能挡掉一个脉冲。需要快速响应的电流环、电压环用限幅滤波或一阶低通系数从0.1开始往大调。按键、阀门到位这类开关量消抖滤波最直观连续4次到8次确认再翻转状态。压阻式压力传感器是典型的两类噪声同时存在的场景既有高频随机抖动又有尖峰常见做法是“限幅滤尖峰、滑动平均压噪声”二级串联第一级阈值按满量程的1%折算成LSB第二级窗口取8。还要注意一个启动问题平均类算法在刚上电时输出会从零慢慢爬升工程里初始化时应该把窗口填满第一次采到的值而不是全填零否则上电后前几秒输出明显偏低一旦接到报警逻辑上就可能误动作。3. 用C语言写出十大滤波核心代码限幅、平均、低通分组实现3.1 限幅滤波与消抖变体的C语言实现限幅滤波是响应最快的一类它不做平均只判断“这次的值值不值得信”。代码如下#include stdint.h #define A_VALUE 50 /* 最大允许偏差按信号最大变化速度折算 */ int16_t limit_filter(int16_t new_val, int16_t last_val) { if ((new_val - last_val A_VALUE) || (last_val - new_val A_VALUE)) { return last_val; /* 超过偏差认为本次是干扰保持上次值 */ } return new_val; /* 正常则直接通过 */ }判断条件刻意写成两个方向的比较而不是用abs(new_val - last_val)。因为两个int16_t做减法时如果差值超过32767会先溢出结果变成一个错误的小数abs之后判断就失效了。A_VALUE怎么定假设采样周期10ms信号最大变化速率是2LSB/msA_VALUE取20应变片这类慢信号取3到5就够取小了会把真实快变信号也削平。消抖滤波变体是每采一个值先与当前确认值比较连续M次差值都在阈值内才更新输出否则计数器清零。限幅消抖滤波就是先跑限幅逻辑再跑计数确认两者代码叠加适合阀门到位检测和需要防误报的报警输入。3.2 中位值滤波与中位值平均滤波的实现中位值滤波对抗单点脉冲很有效原理是保留最近N次采样排序后取中间值。N取奇数中间值唯一。#define N_SAMPLE 5 static int16_t sample_buf[N_SAMPLE]; static uint8_t buf_idx 0; int16_t middle_filter(int16_t new_val) { int16_t tmp[N_SAMPLE]; sample_buf[buf_idx] new_val; buf_idx (buf_idx 1) % N_SAMPLE; /* 环形覆盖最旧数据 */ for (uint8_t i 0; i N_SAMPLE; i) { tmp[i] sample_buf[i]; /* 复制到临时数组再排序 */ } for (uint8_t i 0; i N_SAMPLE - 1; i) { for (uint8_t j i 1; j N_SAMPLE; j) { if (tmp[j] tmp[i]) { int16_t t tmp[i]; tmp[i] tmp[j]; tmp[j] t; } } } return tmp[N_SAMPLE / 2]; }排序前必须复制一份因为滤波函数要保持sample_buf的原始采样顺序直接冒泡排序会打乱环形缓冲的逻辑。N5时完整排序只有10次比较在12MHz的51上耗时几十微秒对慢采样场景没有任何压力。中位值平均滤波是它的直接变体同样排序去掉最大值和最小值对中间N-2个元素求平均既保留了抗脉冲能力又增加了平滑效果N取7时能挡住一个尖峰同时把白噪声平均掉。注意这类算法一个窗口才算出一个输出值输出周期变成N倍采样周期用在控制环里要算清这个延迟。3.3 滑动平均与加权递推平均窗口参数怎么设递推平均滤波即滑动窗口平均是工程里用得最多的一个。它用累加和避免每次重算整个窗口复杂度O(1)。#define WINDOW_SIZE 8 static int32_t win_sum 0; static int16_t win_buf[WINDOW_SIZE] {0}; static uint8_t win_pos 0; int16_t sliding_average(int16_t new_val) { win_sum - win_buf[win_pos]; /* 减掉最旧的一个 */ win_buf[win_pos] new_val; /* 写入新采样值 */ win_sum new_val; win_pos (win_pos 1) % WINDOW_SIZE; return (int16_t)(win_sum / WINDOW_SIZE); }窗口大小直接决定延迟量输出相对输入的平均延迟是(N-1)/2个采样周期。N4延迟1.5拍N8延迟3.5拍N16延迟7.5拍N32延迟15.5拍。选窗口前先问两个问题你允许信号慢多少噪声频段集中在多高允许延迟50ms、采样周期10ms那窗口最多取10。累加和用int32_t是因为8个int16_t的最大累加值是262136超过int16_t范围窗口更大时还要再留余量。加权递推平均是滑窗的改进版给窗口内每个位置预置权重越新的数据权重越大。比如N5时权重表{1,2,3,4,5}归一化因子是15。它对真实变化的跟随性比普通滑动平均好适合既要平滑又要反应快的控制量代价是每次需要重新加权求和代码略长但窗口不大时计算量可忽略。3.4 一阶低通滤波系数a不是滤波强度是截止频率一阶低通用迭代方式实现每个新值只做一次乘加是目前实时性最好的平滑算法。浮点版本int16_t lowpass_filter(int16_t new_val, int16_t last_out, float alpha) { return (int16_t)((float)last_out alpha * ((float)new_val - last_out)); }alpha不能当成“滤波强度”拍脑袋设它对应一个物理截止频率fc近似关系为alpha约等于2πfcTs/(12πfcTs)其中Ts是采样周期。采样率1kHz、想滤掉5Hz以上的分量算出来alpha约0.03而不是常见的0.5。无FPU内核的MCU跑float运算比较贵工程上常用定点化替代#define ALPHA_Q8 8 /* alpha 8/256 0.03125 */ int16_t lowpass_q8(int16_t new_val, int16_t last_out) { int32_t delta (int32_t)new_val - last_out; last_out (int16_t)((delta * ALPHA_Q8) 8); return last_out; }定点化后整个函数只有一次乘法一次移位Cortex-M0和51都能跑。ALPHA_Q8取1到64对应alpha约0.0039到0.25实际调参时在串口上看响应曲线比纯理论计算更直接。alpha太小输出反应迟钝太大滤不干净这个度在十大算法里最需要耐心。4. STM32 ADC滤波落地采样周期、滑窗参数与调试方法4.1 采样周期与滤波窗口先匹配再谈算法STM32的ADC采样时间从1.5到239.5个ADC时钟周期可配采样时间越长对信号源等效内阻的要求越宽采集越稳。但真正决定滤波效果的是“多长时间喂一次新值给滤波函数”。用定时器触发每10ms采一次滑动窗口8的含义是“最近80ms的平均值”改成每1ms采一次、窗口还是8含义就变成“最近8ms”。同样的窗口数采样周期不同滤波效果完全两样所以写代码前先把采样周期写在注释里。如果用的是外部ADC芯片数据到达间隔由SPS决定思路一样。外部ADC的偏移和增益误差要在滤波之前标定否则输出整体偏移三点校准就是在滤波前去掉零点和满量程误差它和数字滤波的去抖动用途互补不是同一件事。这也是很多工程里“滤波了但精度没提升”的原因系统误差压根不在滤波能解决的范围内。4.2 STM32上的最小实现定时器触发采样加滑动平均HAL库工程里单通道ADC用中断方式采集滤波函数放在转换完成回调里static int16_t adc_raw; static int16_t adc_filtered; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { UNUSED(hadc); adc_raw (int16_t)ADC1-DR; /* 读DR自动清EOC标志 */ adc_filtered sliding_average(adc_raw); /* 整型滑动平均无浮点 */ }先用定时器按固定周期调用HAL_ADC_Start_IT触发单次转换或者用定时器TRGO硬件触发ADC再在回调里做滤波。回调里只做整数运算、只改全局变量不要printf、不要跑浮点、不要做RTOS阻塞操作。无FPU内核上软浮点一阶低通一次要几十微秒看着每次转换间隔有富余但主循环里一旦有协议解析或Flash写入中断被挤占后采样间隔就开始抖动滤波器输出会跟着抖。多通道DMA场景下滤波状态必须按通道独立。四通道巡回采集时用一个结构体数组分别维护typedef struct { int16_t last_out; int16_t buf[8]; uint8_t pos; int32_t sum; } adc_filter_ctx_t; adc_filter_ctx_t ch_ctx[4]; /* 每个通道独立滤波状态 */DMA回调用ch_ctx[ch]分别调用滤波函数避免通道间串扰。DMA配置里循环模式的数据长度必须和通道数匹配否则滤波状态错位输出会周期性跳变这个坑排查起来比算法本身耗时长得多。4.3 参数怎么调串口回传原始值和滤波值做对比调参不要靠猜把两组数据拉出来看。测试模式下在主循环里打印if (uart_tx_ready) { printf(%d,%d\r\n, adc_raw, adc_filtered); /* 串口发原始值和滤波值 */ uart_tx_ready 0; }用串口助手或Excel收200个点画两条曲线。滤波后极差缩小到原始极差的1/3以下、曲线没有明显台阶、延迟在可接受范围算基本合格。滤波后仍有孤立尖峰说明有偶发脉冲先加限幅或改中位值平均输出跟随太慢缩短窗口或调大alpha输出周期性波动先查电源纹波和采样周期是否与开关频率存在整数倍关系软件上把采样周期避开这个频点。常见问题对照现象原因处理输出有周期性波浪电源纹波与采样不同步ADC参考电源去耦采样周期避开开关频率滤波后曲线滞后大窗口太长或alpha太小缩短窗口改用加权递推平均偶发尖峰仍残留滑动平均对脉冲不敏感前级加限幅或改中位值平均浮点滤波导致采样抖动无FPU内核软浮点开销改定点化一阶低通4.4 中断里放滤波主循环里做标定和输出滤波处理放在中断里还是主循环里按采样率决定。采样率低于100Hz中断里做整型滤波没问题采样率到1kHz以上滤波和标志位更新也要精简。工程上更常见的做法是中断里只存原始值、置一个标志主循环检测到标志后取数、滤波、标定、输出。这样能保证采样节拍不被其他任务干扰滤波器输入的时间间隔才均匀。回调和主循环之间有竞争风险用volatile修饰共享变量滤波状态和原始数据在同一个临界区里读改写。多通道DMA场景下ADC数据天然按通道排列主循环按序取用即可不需要再加锁。5. 用方差量化滤波效果再确认一阶低通与卡尔曼的边界5.1 在测试模式里直接算方差改善比肉眼评估滤波效果容易误判用方差量化。测试阶段把原始值和滤波后值各存200点调用一个简单方差函数float calc_variance(const int16_t *data, uint16_t len, float *mean_out) { float sum 0; for (uint16_t i 0; i len; i) { sum data[i]; } *mean_out sum / len; float var 0; for (uint16_t i 0; i len; i) { float d data[i] - *mean_out; var d * d; } return var / len; }算出原始方差A和滤波后方差B改善比(A-B)/A。改善比在60%到90%之间是合理区间接近100%反而要警惕真实信号也被滤掉了一部分这时回到第3章看延迟或者把窗口减半再测一次。数据里有一个大尖峰时方差会被它主导此时先统计极差再比较方差不要直接下结论。5.2 一阶低通系数与截止频率的对照验证采样周期固定时alpha和截止频率可以预先算好再上板验证。采样周期Ts1ms时对照如下目标截止频率alpha近似值2Hz0.01210Hz0.05950Hz0.239100Hz0.385实际使用时把alpha定好之后输入一个已知频率的方波或正弦信号看输出幅值衰减到-3dB的频率点和理论值相差在10%以内说明采样节拍稳定。RTOS环境下任务调度抖动会让实际Ts变大alpha的真实效果比理论偏小这就是为什么做音视频采样或高精度采集时建议用硬件定时器触发ADC而不是在任务循环里读时间。5.3 卡尔曼滤波不是十大算法的默认替代品卡尔曼滤波常被当成“更高级的滤波方案”但它的适用场景是动态估计目标跟踪、姿态解算这类需要状态预测的问题。传感器慢变信号上卡尔曼需要你给状态转移矩阵和噪声协方差调参成本比十大里任何一个算法都高初始值给不好还会出现不收敛的振荡。对ADC采集来说信号物理模型简单、采样周期固定滑动平均和一阶低通已经覆盖了绝大多数需求。真正值得关注的是算法组合和验证方法限幅负责挡尖峰滑窗压随机噪声定点一阶低通做实时性要求高的通道每种算法用方差和延迟两条指标去衡量。窗口从4改成5跑一遍方差对比你会看到滑动窗口滤波的“动”字到底在哪里。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询