嵌入式ADC滤波算法实战:从限幅到卡尔曼的10种噪声处理方案

📅 2026/8/12 21:32:20
嵌入式ADC滤波算法实战:从限幅到卡尔曼的10种噪声处理方案
1. ADC滤波从“读数不准”到“信号可信”的必经之路如果你在玩单片机、ESP32或者STM32用ADC读取传感器数据时肯定遇到过这样的烦恼明明电压没变ADC读回来的数值却像跳舞一样上下跳动或者电机一启动传感器的读数就飘得没边了。这背后就是ADC采样中无处不在的噪声在作祟。噪声来源五花八门——可能是电源纹波、可能是数字电路的开关干扰、也可能是传感器本身的热噪声。这些噪声叠加在真实的信号上让我们的单片机“看”到了一个失真的世界。直接使用这样的原始数据轻则导致屏幕显示数值闪烁重则让PID控制 loop 剧烈震荡系统根本无法稳定工作。因此ADC滤波算法就成了嵌入式开发、物联网设备、工控采集等领域工程师的必备技能。它不是什么高深莫测的玄学而是一系列经过实践检验的数学或逻辑方法目的就是从混杂着噪声的原始采样序列中尽可能地提取出接近真实信号的值。今天我们就抛开教科书式的理论堆砌直接切入实战盘点十种在工程中最常用、最接地气的ADC滤波算法。我会结合具体的C语言代码片段、参数选择依据以及大量踩坑经验告诉你每种方法适合什么场景又有什么坑等着你。无论你是在调校ESP32的电池电压检测还是在打磨STM32F407的电机电流采样这篇文章都能给你提供可以直接“抄作业”的方案和避坑指南。2. 基础认知为什么滤波以及滤波前你必须做的三件事在深入算法之前我们必须建立一个核心认知软件滤波是最后一道防线而非首选方案。盲目依赖复杂的算法去修正一个硬件层面就“病入膏肓”的信号往往是事倍功半。一个稳定的读数是“硬件调理 软件滤波”共同作用的结果。2.1 硬件调理给ADC一个干净的“食材”软件算法是“厨师”ADC原始数据是“食材”。如果食材本身已经腐烂变质信号噪声极大再厉害的厨师也做不出美味佳肴。因此在写第一行滤波代码前请先检查这三件事电源是否干净这是噪声的最大来源。尤其是给模拟传感器供电的电源最好使用LDO低压差线性稳压器而非DCDC开关稳压器并在电源引脚就近放置大小电容如10uF钽电容 0.1uF陶瓷电容进行退耦。对于ESP32、STM32等MCU其模拟电源引脚VDDA、VREF等的滤波电容容值和布局必须严格遵循数据手册推荐这是很多ADC读数不准的根源。信号路径是否被干扰模拟信号线应远离数字信号线特别是时钟、PWM线如果无法远离中间用地线隔离或采用屏蔽线。对于长距离传输的传感器信号考虑使用电压跟随器运放进行缓冲提高驱动能力避免信号在传输线上衰减和被干扰。参考电压是否稳定ADC的精度天花板取决于它的参考电压VREF。如果VREF本身就在波动那么ADC转换结果必然失准。确保VREF引脚连接了高质量、低噪声的基准源并做好滤波。很多MCU允许使用内部参考电压但其精度和温漂通常较差在对精度有要求的场合外接一颗专用的基准电压芯片如REF3030是值得的投资。注意我曾调试过一个温湿度采集项目ADC读数始终有几十个字的无规律跳动。排查了半天软件算法最后发现是传感器供电线和一块继电器的控制线并排走了15厘米。继电器一动作电源线上就产生一个尖峰脉冲直接被ADC采集到。将线缆分开走线后噪声立即大幅降低。硬件问题软件很难根治。2.2 ADC配置别让配置拖了后腿即使硬件没问题ADC本身的配置不当也会引入误差。采样时间这是最容易忽略的参数。ADC采样输入信号需要时间这个时间必须足够长让采样电容上的电压充分接近外部信号电压。如果信号源阻抗较大如分压电阻值大就需要更长的采样时间。在STM32的CubeMX或HAL库中你需要关注SamplingTime这个参数根据数据手册的公式和你的信号源阻抗来计算和设置。时间太短转换结果会不准确且不稳定。采样频率与滤波根据奈奎斯特采样定理你的采样频率至少需要是信号中最高频率成分的2倍以上。但更重要的是你需要确保没有高于采样频率一半的噪声混叠进来。这就需要在ADC前端加入硬件RC低通滤波器抗混叠滤波器滤除高频噪声。软件滤波主要处理的是低频噪声或周期性干扰。校准大多数MCU的ADC都提供自校准功能。在上电初始化ADC后务必执行一次校准如STM32的HAL_ADCEx_Calibration_Start。这可以消除ADC内部的偏移和增益误差。做好以上硬件和配置检查你的ADC读数可能已经稳定了不少。接下来才是软件滤波算法大显身手的时候。3. 十大常用滤波算法详解与实战下面我们进入核心环节。我将这些算法分为几个大类抗脉冲干扰型、平滑递推型、复合智能型。每种算法我都会给出核心思想、C语言实现非完整驱动而是滤波函数、关键参数选择依据以及我踩过的坑。3.1 抗脉冲干扰型应对突发性“毛刺”这类算法的目标是快速识别并剔除采样值中偶然出现的、幅度很大的异常点脉冲干扰。3.1.1 限幅滤波法程序判断滤波法核心思想根据经验设定一个最大允许偏差A。本次采样值new_val与上次有效值last_val的差值绝对值若超过A则认为本次是干扰丢弃并用上次值代替否则认为本次值有效。C语言实现#define A 50 // 最大允许偏差值根据实际信号变化率设定 int value; // 上次有效值 int LimitingFilter(int new_val) { int result; if (abs(new_val - value) A) { result value; // 超出限幅取上次值 } else { result new_val; // 有效值 value new_val; // 更新上次有效值 } return result; }参数A怎么定A是关键。它必须大于信号正常变化时的最大斜率单位采样周期内的变化量。例如一个温度传感器温度最快每秒变化0.5度对应ADC值变化约10个字采样周期100ms那么A可以设为10 * 0.1 1不这样太临界了。通常需要留有余量可以设为2或3。最好的办法是长时间记录数据观察正常波动范围。优点与坑点能有效滤除偶然的脉冲干扰算法极其简单计算量小。但它的缺点是无法滤除周期性的、幅度小于A的干扰而且会引入滞后当信号真实变化超过A时会被误判为干扰而延迟更新。适用于变化缓慢的物理量如温度、液位。3.1.2 中位值滤波法核心思想连续采样N次N为奇数将这N个值进行排序取中间大小的那个值作为本次有效值。这本质上是取统计中位数对脉冲干扰和偶然的抖动有很好的抑制效果。C语言实现#define N 5 // 采样次数建议取奇数 3,5,7 int MedianFilter(int new_val) { static int data_buf[N]; static int init_idx 0; int temp_buf[N]; int i, j, temp; // 循环存入数据 data_buf[init_idx] new_val; init_idx (init_idx 1) % N; // 复制到临时数组进行排序避免破坏原数组顺序 for (i 0; i N; i) { temp_buf[i] data_buf[i]; } // 使用冒泡排序数据量小效率可接受 for (i 0; i N - 1; i) { for (j 0; j N - 1 - i; j) { if (temp_buf[j] temp_buf[j 1]) { temp temp_buf[j]; temp_buf[j] temp_buf[j 1]; temp_buf[j 1] temp; } } } // 返回中值 return temp_buf[N / 2]; }参数N与排序选择N越大滤波效果越好但延迟和计算量也越大。N3是兼顾效果和速度的常用选择。对于N3排序可以用比较和交换直接实现效率更高。当N较大时可以考虑使用更高效的排序算法或直接使用C标准库的qsort。优点与坑点对脉冲干扰和随机噪声有良好的抑制尤其适合消除由于开关动作、接触不良引起的偶发尖峰。但它对周期性干扰的抑制能力较差且同样有滞后滞后N/2个采样周期。适用于压力、流量等存在冲击性干扰的场合。3.2 平滑递推型让曲线变得“柔和”这类算法的目标不是剔除某个点而是让整个数据序列变得平滑抑制高频波动。3.2.1 算术平均滤波法核心思想最简单直接的平滑。连续取N个采样值进行算术平均。C语言实现#define N 10 // 平均项数 int MeanFilter(int new_val) { static int sum 0; static int data_buf[N]; static int idx 0; sum - data_buf[idx]; // 减去最旧的值 sum new_val; // 加上最新的值 data_buf[idx] new_val; // 更新缓冲区 idx (idx 1) % N; // 循环索引 return sum / N; // 返回平均值 }滑动平均优化上述实现是“滑动平均”只需做一次减法和一次加法计算效率高适合实时处理。N的选择至关重要N太大信号平滑但响应迟钝N太小响应快但平滑效果差。优点与坑点对周期性干扰有良好的抑制平滑度高。但它的致命缺点是灵敏度低对信号变化的反应速度慢并且会引入固定N-1个周期的滞后。适用于信号本身波动较快但你需要一个更稳定读数的场景比如平滑陀螺仪的原始数据用于显示。3.2.2 递推平均滤波法滑动平均滤波法这其实就是上面实现的滑动平均。它还有一个变种加权滑动平均。即认为离当前时刻越近的数据越重要赋予更高的权重。#define N 5 int WeightedMovingAverageFilter(int new_val) { static int data_buf[N]; static int idx 0; // 权重系数例如 [1, 2, 3, 2, 1]总和为9 const int weights[N] {1, 2, 3, 2, 1}; int i, sum 0, weight_sum 0; data_buf[idx] new_val; idx (idx 1) % N; for (i 0; i N; i) { int data_idx (idx i) % N; // 从最旧的数据开始 sum data_buf[data_idx] * weights[i]; weight_sum weights[i]; } return sum / weight_sum; }加权平均能比简单算术平均更快地跟上信号的真实变化滞后更小。3.2.3 一阶滞后滤波法一阶低通数字滤波器核心思想这是从模拟电路RC低通滤波器数字化而来在嵌入式领域应用极其广泛特别是在电机控制如滤波电流环反馈、姿态传感器融合中。公式为Y(n) α * X(n) (1-α) * Y(n-1)。其中X(n)是本次采样值Y(n)是本次滤波输出Y(n-1)是上次输出α是滤波系数0 α ≤ 1。C语言实现// 方法1浮点运算直观 #define ALPHA 0.1f // 滤波系数越小越平滑响应越慢 float FirstOrderLagFilter_Float(float new_val) { static float last_output 0; float output ALPHA * new_val (1 - ALPHA) * last_output; last_output output; return output; } // 方法2定点运算避免浮点提高速度 #define ALPHA_Q15 3277 // 对应 0.1 Q15格式 (0.1 * 32768) #define ONE_MINUS_ALPHA_Q15 (32768 - ALPHA_Q15) // 0.9 * 32768 int FirstOrderLagFilter_Fixed(int new_val) { static int last_output 0; // 计算: output (alpha*new_val (1-alpha)*last_output) / 32768 long long temp (long long)ALPHA_Q15 * new_val (long long)ONE_MINUS_ALPHA_Q15 * last_output; int output (int)(temp 15); // 右移15位相当于除以32768 last_output output; return output; }参数α与截止频率α决定了滤波器的“惯性”。α越小惯性越大输出越平滑但对信号变化的响应越慢。α与数字滤波器的截止频率fc和采样周期Ts有关α ≈ 2π * fc * Ts当α较小时。例如采样率100Hz (Ts0.01s)希望截止频率为2Hz则α ≈ 2 * 3.14 * 2 * 0.01 0.1256。优点与坑点计算量极小仅两次乘加对周期性干扰具有良好的抑制作用相位滞后比平均滤波小。但它的缺点是带来了滞后相位延迟且无法滤除频率高于采样频率一半的噪声需靠硬件抗混叠滤波器。适用于高频噪声大但信号本身变化不很快的场景如滤波MPU6050的加速度计原始数据。3.2.4 消抖滤波法防脉冲干扰平均滤波法核心思想这是“限幅”和“平均”的结合体专门用于开关量或位式判断的滤波比如按键检测、接近开关。它设定一个阈值A和一个计数器。连续N次采样值的变化都在A之内才认为信号稳定并输出这N次的平均值。C语言实现#define N 5 // 消抖次数 #define A 10 // 阈值 int DebounceFilter(int new_val) { static int last_val; static int count 0; static int sum 0; static int buf[N]; static int init_flag 0; int i, result; if (!init_flag) { for(i0; iN; i) buf[i] new_val; last_val new_val; sum new_val * N; init_flag 1; return new_val; } // 检查变化是否在阈值内 if(abs(new_val - last_val) A) { sum - buf[count]; // 减去最早的值 sum new_val; // 加上最新的值 buf[count] new_val; count (count 1) % N; // 连续N次稳定才输出平均值 // 这里简化每次稳定都输出平均值。严格实现需另一个计数器。 result sum / N; } else { // 变化超限重置状态 count 0; sum 0; for(i0; iN; i) { buf[i] new_val; sum new_val; } result new_val; // 或保持上次输出取决于策略 } last_val new_val; return result; }优点与坑点既能抑制偶然脉冲又能输出平滑结果非常适合消除抖动。但它的延迟很大从信号变化到稳定输出至少需要N个采样周期。是按键消抖的经典软件实现方案。3.3 复合与智能型强强联合应对复杂场景单一算法总有局限将多种算法组合使用往往能取得更好的效果。3.4.1 中位值平均滤波法防脉冲干扰平均滤波法核心思想先“中位值”剔除异常点再“平均”平滑。连续采样N个数据去掉一个最大值和一个最小值然后对剩下的N-2个数据求算术平均。这是比赛中测量评分常用的方法。C语言实现#define N 10 // 采样次数建议3 int MedianMeanFilter(int new_val) { static int data_buf[N]; static int idx 0; int i, j, temp, sum 0; data_buf[idx] new_val; idx (idx 1) % N; // 复制并排序找最值 int temp_buf[N]; for (i 0; i N; i) temp_buf[i] data_buf[i]; for (i 0; i N - 1; i) { for (j 0; j N - 1 - i; j) { if (temp_buf[j] temp_buf[j 1]) { temp temp_buf[j]; temp_buf[j] temp_buf[j 1]; temp_buf[j 1] temp; } } } // 去掉首尾最小和最大对中间值求和 for (i 1; i N - 1; i) { sum temp_buf[i]; } return sum / (N - 2); }优点与坑点融合了中位值和平均的优点既能抑制脉冲干扰又能平滑随机噪声。但计算量相对较大需要排序且N不能太小否则去最值后数据量太少影响统计意义。3.4.2 限幅消抖滤波法核心思想先“限幅”剔除显著异常点再“消抖”判断信号是否真正稳定。相当于把限幅滤波作为消抖滤波的前置条件。实现在消抖滤波的判断逻辑前先加入限幅判断。只有通过限幅判断的值才进入消抖的缓冲区。这进一步提高了对突发强干扰的鲁棒性。3.4.3 加权递推平均滤波法上文在递推平均中已提及通过赋予不同时刻数据不同权重可以灵活调整滤波器对近期数据的“记忆力”在平滑度和响应速度间取得更好平衡。权重系数的设计可以模拟更多复杂的滤波器特性。3.4.4 卡尔曼滤波Kalman Filter核心思想这已经属于“最优估计理论”的范畴。它不仅仅是一个滤波器更是一个状态估计器。它通过系统的动态模型你知道系统大概会怎么变化和观测模型ADC测量值结合两者并考虑各自的不确定性噪声以最优化的方式最小均方误差估计出系统的真实状态。它特别适合处理线性高斯系统。简易一维卡尔曼滤波用于ADC值滤波// 超简化版用于理解概念。实际应用需要根据系统调整Q、R。 float Q 0.001; // 过程噪声协方差模型信任度 float R 0.1; // 观测噪声协方差传感器信任度 float P 1.0; // 估计误差协方差 float K 0; // 卡尔曼增益 float X 0; // 状态估计值我们想求的滤波值 float KalmanFilter(float Z) { // Z为ADC测量值 // 预测阶段一维简化通常状态不变 // X X; // 状态预测假设系统静止 P P Q; // 预测误差协方差增大 // 更新阶段 K P / (P R); // 计算卡尔曼增益 X X K * (Z - X); // 状态更新 P (1 - K) * P; // 更新误差协方差 return X; }参数Q和RQ表示你对过程模型的信任程度。Q越大表示你认为系统变化越剧烈模型不准滤波器会更相信观测值Z响应变快但噪声大。R表示你对传感器的信任程度。R越大表示你认为传感器噪声越大滤波器会更相信自己的预测X结果平滑但响应慢。调试卡尔曼滤波主要就是调Q和R。优点与坑点在模型准确的情况下它是理论上的最优滤波器能同时提供良好的平滑性和快速的跟踪能力。但它的实现和调试相对复杂需要一定的数学基础。对于简单的ADC滤波一阶滞后滤波可能更简单实用但对于需要融合多个传感器如IMU进行状态估计如姿态、位置的场景卡尔曼滤波及其变种扩展卡尔曼、互补滤波几乎是唯一选择。4. 算法选型与实战场景指南了解了这么多算法到底该怎么选没有“最好”只有“最合适”。下面这个表格可以帮你快速决策算法名称核心优势主要缺点典型应用场景参数调试关键限幅滤波消除偶发大幅脉冲计算量极小对小幅噪声无效会引入滞后缓慢变化信号中的抗突发干扰如温度采集防开关噪声阈值A需大于信号正常最大变化率中位值滤波有效抑制脉冲和随机跳动对周期性干扰差有滞后计算量中等存在随机冲击的信号如压力、流量防气泡或振动采样点数N3或5兼顾效果与速度算术平均滤波平滑效果好抑制周期性噪声滞后大灵敏度低降低分辨率需要稳定读数的快速变化信号如平滑显示的陀螺仪数据平均项数N在平滑度和响应速度间权衡一阶滞后滤波计算量小抑制高频噪声相位滞后相对小无法滤除频率混叠噪声有滞后广泛用于模拟RC滤波的数字实现电机电流、传感器原始数据平滑滤波系数α与期望截止频率和采样时间相关消抖滤波专门消除信号抖动判断稳定延迟非常大开关量、数字输入信号按键、接近开关的消抖稳定次数N和阈值A决定消抖严格程度中位值平均滤波兼顾抗脉冲和平滑能力计算量大需排序要求较高的测量场合剔除异常值后求平均如竞赛评分、高精度AD采样点数N需足够大以保证去最值后仍有统计意义卡尔曼滤波理论最优平滑与跟踪能力平衡实现复杂需模型和调参多传感器融合、动态系统状态估计无人机姿态、导航定位过程噪声Q和观测噪声R调整对模型和传感器的信任度实战经验分享组合使用是王道我处理一个电池电压采样的项目电源环境复杂。最终方案是硬件上在ADC输入端加了一个RC低通滤波器截止频率约10Hz软件上先对原始数据做一次限幅滤波剔除因电源切换产生的巨大毛刺再将结果送入一个一阶滞后滤波器α0.05进行平滑。效果非常好读数稳定又能跟上电池放电的真实电压下降趋势。警惕“过度平滑”一昧追求读数“不动”是错误的。例如在电机控制中电流环的反馈信号如果滤波过度α太小或平均点数N太大会导致相位滞后严重整个控制环路产生振荡甚至失稳。滤波参数的选取必须考虑控制系统的带宽要求。动态调整参数高级的玩法是根据信号特征动态调整滤波参数。例如在系统静止时使用更强的滤波更小的α来获得极稳定的读数当检测到信号开始快速变化时自动增大α或切换到更灵敏的滤波模式以减少跟踪滞后。这需要额外的逻辑来判断系统状态。5. 进阶话题从滤波到传感器融合当你需要处理更复杂的问题比如从陀螺仪和加速度计数据中解算出稳定的姿态角时单一的ADC滤波算法就力不从心了。这时你需要的是传感器融合算法。互补滤波这是一阶滞后滤波思想在传感器融合中的经典应用。例如陀螺仪积分得到角度但会漂移加速度计可以测量静态角度但动态响应差。互补滤波就是用高通滤波器滤掉加速度计的低频噪声动态误差用低通滤波器滤掉陀螺仪的高频噪声漂移然后将两者融合。它比卡尔曼滤波简单在无人机飞控中广泛应用。自适应滤波滤波参数如α、Q、R不是固定的而是根据实时估计的信号噪声水平动态调整。当噪声大时滤波更强噪声小时滤波减弱以保持响应速度。这需要在线估计噪声统计特性算法更复杂。ADC滤波是嵌入式信号处理的第一步也是基石。理解每种算法的内在逻辑和适用边界结合硬件设计你就能为你的项目选择或组合出最合适的“去噪”方案让数据真正为你所用而不是被噪声牵着鼻子走。