ADC采集结果为什么不稳定?硬件与软件原因及STM32排查方法详解
ADC采集结果为什么不稳定?硬件与软件原因及STM32排查方法详解
前言
在使用 STM32、GD32、ESP32 或其他 MCU 采集模拟电压时,经常会遇到下面这些问题:
输入电压明明没有变化,ADC数值却一直跳动;
万用表测量是1.65V,ADC换算后却只有1.58V;
同一个通道偶尔出现很大的异常值;
切换ADC通道后,第一次采样结果明显不正确;
电机、风扇或继电器启动后,ADC结果突然剧烈波动;
使用DMA采集后,数据比轮询采集更加混乱;
增加平均滤波后数值稳定了,但响应速度明显变慢。
很多人遇到 ADC 不稳定时,第一反应是增加平均滤波:
adc_value = (value1 + value2 + ... + value100) / 100;
平均滤波确实能减小部分随机噪声,但如果问题来自参考电压、输入阻抗、采样时间、PCB布局或者通道配置,单纯增加平均次数只能掩盖问题,无法真正解决问题。
ADC 采集问题需要从以下几个层面分析:
模拟信号源
↓
输入调理电路
↓
ADC引脚和参考电压
↓
采样保持电路
↓
ADC配置
↓
软件读取
↓
数字滤波
↓
电压换算
本文将从 ADC 基本原理开始,详细分析 ADC 采集结果不稳定的硬件原因、软件原因、滤波方法和完整排查流程。
一、ADC是什么
ADC 的英文全称是:
Analog-to-Digital Converter
中文称为:
模数转换器
它的作用是将连续变化的模拟电压转换为 MCU 可以处理的数字量。
例如,一个 12 位 ADC 使用 3.3V 参考电压:
输入0V → ADC结果接近0
输入1.65V → ADC结果接近2048
输入3.3V → ADC结果接近4095
对于 N 位 ADC,一共可以表示:
2^N
个离散等级。
12 位 ADC 可以表示:
2^12 = 4096
个等级,输出代码通常为:
0~4095
理想情况下,一个最低有效位所对应的电压约为:
1 LSB = VREF / 2^N
当参考电压为 3.3V、分辨率为 12 位时:
1 LSB = 3.3V / 4096
≈ 0.0008057V
≈ 0.806mV
这意味着输入电压变化不到约 0.806mV 时,数字结果不一定发生变化。
反过来,即使输入完全稳定,ADC 结果在相邻的几个代码之间轻微变化,也不一定代表系统存在严重故障。
二、ADC“稳定”和“准确”不是一回事
这是分析 ADC 问题前必须区分的两个概念。
1. 稳定性
稳定性表示连续采样结果的波动程度。
例如:
2046
2048
2047
2049
2047
2048
这些结果平均值比较固定,波动范围也比较小,可以认为稳定性较好。
2. 准确性
准确性表示 ADC 测量结果与真实输入值之间的差距。
例如,真实输入为:
1.650V
ADC连续输出:
1.600V
1.601V
1.600V
1.601V
结果非常稳定,但存在约 50mV 的系统误差。
因此:
稳定但不准确:
通常与参考电压、增益误差、偏置误差和换算公式有关。
准确但波动:
通常与随机噪声、量化误差、采样干扰和滤波不足有关。
既不稳定也不准确:
需要同时检查硬件和软件配置。
STM32 的 ADC 精度不仅受到量化误差影响,还可能受到偏置误差、增益误差、线性误差、参考电压以及模拟输入电路等因素影响。ST 的 ADC 精度应用笔记也将这些误差和外部设计条件列为主要优化对象。
三、ADC内部是如何采样的
很多 ADC 不稳定问题,根本原因是没有理解 ADC 引脚内部并不是一个理想的无限大阻抗电压表。
典型 SAR ADC 输入端可以简化为:
模拟信号源
│
├── 信号源电阻 RSOURCE
│
▼
ADC输入开关 RSW
│
▼
采样保持电容 CSH
ADC采样过程分为两个阶段:
采样阶段:
开关闭合,外部信号给内部采样电容充电。
保持和转换阶段:
开关断开,ADC根据采样电容上的电压进行数字转换。
如果信号源输出阻抗过大,或者采样时间太短,内部采样电容还没有充到接近真实输入电压,ADC 就结束了采样。
最终结果可能表现为:
- ADC结果偏低;
- 连续采样值不一致;
- 通道切换后第一笔数据错误;
- 输入电压变化后ADC响应缓慢;
- 采样频率越高,误差越大。
SAR ADC 的采样保持电容需要在有限采集时间内充电并完成稳定,因此高输入阻抗通常需要更长采样时间、缓冲器或适当的输入电容。
四、ADC结果轻微跳动一定是故障吗
不一定。
例如12位ADC连续读取一个稳定电压:
ADC = 2047
ADC = 2048
ADC = 2047
ADC = 2049
ADC = 2048
这种相邻几个 LSB 的轻微跳动可能来自:
- ADC量化误差;
- MCU内部数字电路噪声;
- 电源微小波动;
- 热噪声;
- 参考电压噪声;
- 时钟耦合;
- 环境电磁干扰。
理想 ADC 本身也存在量化过程,通常可以将理想量化误差理解为约 ±0.5 LSB。
真正需要重点排查的是:
波动达到几十甚至几百个ADC码;
数值周期性大幅变化;
切换通道后结果明显串扰;
电机启动后采样值突然异常;
静态输入下采样结果缓慢漂移;
ADC结果经常突然变成0或满量程。
五、ADC结果不稳定的硬件原因
1. 参考电压不稳定
ADC测量的本质不是直接测量绝对电压,而是测量:
输入电压与参考电压之间的比例
理想情况下:
ADC_CODE ≈ VIN / VREF × (2^N - 1)
因此,即使 VIN 完全不变,只要 VREF 发生变化,ADC结果也会变化。
例如输入电压固定为1.65V。
当参考电压为3.30V时:
ADC ≈ 1.65 / 3.30 × 4095
≈ 2048
当参考电压下降到3.20V时:
ADC ≈ 1.65 / 3.20 × 4095
≈ 2112
输入电压没有变化,但ADC结果已经明显改变。
常见问题
- VDDA直接使用噪声较大的数字3.3V;
- VREF引脚附近没有去耦电容;
- 电机、继电器或风扇与ADC共用电源;
- LDO输出纹波较大;
- 大电流器件启停引起电压跌落;
- 模拟地和数字地回流路径不合理;
- VREF通过过长走线连接;
- 参考源驱动能力不足。
改进方法
- 按照芯片数据手册配置 VDDA、VREF+ 去耦;
- 将去耦电容尽量靠近芯片引脚;
- 将模拟供电与强干扰负载适当隔离;
- 优化地平面和电流回流路径;
- 必要时使用更稳定的外部基准源;
- 通过 MCU 内部 VREFINT 估算实际 VDDA。
需要注意:
ADC换算时不能永远假设参考电压精确等于3.300V。
更可靠的方法是测量实际参考电压,或者利用芯片内部校准过的参考通道进行补偿。
2. ADC输入引脚悬空
如果 ADC 引脚没有连接到确定电压,输入端就处于悬空状态。
悬空引脚很容易耦合:
- 工频干扰;
- 人体静电;
- 相邻数字信号;
- 时钟信号;
- 空间电磁噪声。
此时ADC结果可能从0到满量程之间大幅跳动。
验证方法
将ADC引脚通过一个较小电阻连接到地:
ADC输入 → 1kΩ~10kΩ → GND
如果结果立即稳定在接近0,说明ADC本身可能正常,之前的问题是输入悬空或者信号源连接不可靠。
也可以将ADC输入连接到稳定基准电压进行测试。
3. 信号源输出阻抗过大
这是 MCU 内部 ADC 最常见、也最容易忽略的问题之一。
例如使用两个大电阻分压:
24V
│
R1 = 1MΩ
│──── ADC
R2 = 100kΩ
│
GND
虽然分压比例可能正确,但等效输出阻抗非常大。
ADC内部采样电容在较短采样时间内无法充满,就会导致转换值偏差或者波动。
ST 的 ADC 精度说明明确指出,高信号源阻抗会影响内部采样保持电容的充电过程,增加采样时间可以减小由此产生的附加误差。
改进方法一:降低分压电阻
例如将:
1MΩ和100kΩ
适当降低为:
100kΩ和10kΩ
但需要同时考虑:
- 分压器功耗;
- 被测信号源负载;
- 输入保护电流;
- 电阻精度。
改进方法二:增加ADC采样时间
例如将:
ADC_SAMPLETIME_2CYCLES_5
改为:
ADC_SAMPLETIME_47CYCLES_5
或者更长采样时间。
不同STM32系列支持的采样周期选项不同,应根据对应数据手册和参考手册选择。
改进方法三:增加运算放大器缓冲
可以使用电压跟随器:
高阻抗信号
↓
运算放大器电压跟随器
↓
低输出阻抗
↓
ADC输入
需要选择适合ADC驱动的运算放大器,并考虑:
- 输入共模范围;
- 输出摆幅;
- 稳定性;
- 带宽;
- 建立时间;
- 失调电压;
- 供电范围。
改进方法四:在ADC输入端增加电容
可以在ADC引脚附近增加一个小电容:
信号源 ── R ── ADC
│
C
│
GND
电容可以:
- 为内部采样电容提供局部电荷;
- 滤除高频噪声;
- 减小采样瞬间的电压跌落。
但电容不是越大越好。
它与前级电阻构成低通滤波器:
FC = 1 / (2πRC)
如果电容过大,会导致:
- 信号响应变慢;
- 快速变化信号被严重衰减;
- 多通道扫描稳定时间增加。
增加输入电容对直流或低频高阻抗信号可能有效,但它同时会降低输入带宽,因此必须根据目标信号频率设计。
4. ADC采样时间太短
ADC采样时间决定内部采样电容连接到外部信号的时间。
采样时间过短时,结果可能表现为:
低阻抗信号正常;
高阻抗信号异常;
采样速度降低后结果变准;
增加采样周期后数值明显稳定。
采样时间与以下因素有关:
- ADC分辨率;
- ADC时钟;
- 信号源输出阻抗;
- ADC内部采样电容;
- 输入电压变化速度;
- 目标精度。
RC充电过程可以近似表示为:
VC(t) = VIN × [1 - e^(-t/RC)]
要达到更高精度,采样电容必须更加接近最终输入电压,因此需要更长的稳定时间。
实际开发中不建议仅凭理论公式决定采样时间,而应该:
- 查看芯片数据手册中的ADC输入阻抗要求;
- 查看不同采样周期允许的最大外部阻抗;
- 从较长采样时间开始测试;
- 使用示波器或稳定电压源验证误差。
5. ADC输入没有RC低通滤波
传感器输出或者长线信号中可能包含大量高频噪声。
如果直接进入ADC:
传感器输出 ───────── ADC
高频干扰会直接反映到采样结果中。
可以增加简单RC低通滤波:
传感器输出 ── R ── ADC
│
C
│
GND
截止频率为:
FC = 1 / (2πRC)
例如:
R = 1kΩ
C = 100nF
截止频率约为:
FC ≈ 1591Hz
但RC参数必须根据被测信号带宽确定。
例如温度、电池电压等缓慢信号,可以使用较低截止频率;电流环、音频、振动信号则需要保留更高带宽。
6. 没有进行抗混叠滤波
如果输入信号中包含超过采样频率一半的频率成分,高频信号可能折叠到低频区域,形成混叠。
根据采样理论,采样频率应高于目标信号最高频率的两倍,但实际系统通常还要预留模拟滤波器过渡带。
例如:
目标信号最高频率:1kHz
ADC采样率:2kHz
理论上刚好满足两倍条件,但真实滤波器无法在1kHz到1kHz之间瞬间完成无限衰减,因此工程上应提高采样率并设计模拟抗混叠滤波器。
TI 的 ADC 资料将混叠列为采样系统中的误差来源,并建议在ADC前使用抗混叠滤波器抑制超出目标带宽的频率成分。
7. 电源纹波和负载干扰
当系统中存在以下负载时,ADC容易受到明显干扰:
- 直流电机;
- 无刷风扇;
- PWM负载;
- 蜂鸣器;
- 继电器;
- DC-DC电源;
- 大功率LED;
- 高频通信模块。
例如PWM控制风扇时,电源电流会周期性变化。
如果ADC采样时间恰好与PWM开关时刻重合,采样结果可能出现周期性波动。
改进方法
- 模拟电源与功率电源适当隔离;
- 增加局部去耦和储能电容;
- 优化接地和回流路径;
- ADC采样避开PWM开关边沿;
- 使用定时器触发ADC,在固定相位采样;
- 对传感器供电增加滤波;
- 使用差分测量或者仪表放大器。
8. PCB布局不合理
ADC前端是模拟小信号电路,PCB布局会直接影响采样结果。
常见问题包括:
- ADC输入线与PWM、SPI、时钟线平行;
- 模拟信号跨越数字地分割区域;
- ADC输入走线过长;
- VREF去耦距离芯片过远;
- 大电流回流经过模拟地;
- DC-DC电感靠近ADC输入;
- 运放反馈回路过长;
- 高阻抗节点面积过大。
布局建议
- ADC输入走线尽量短;
- 远离高频时钟、PWM和开关节点;
- 模拟输入周围保持干净;
- 去耦电容靠近ADC供电和参考引脚;
- 避免大电流回流穿过模拟区域;
- 高阻抗输入节点尽量缩小;
- 必要时使用完整地平面和合理分区。
9. 模拟输入超过允许范围
ADC输入电压通常必须位于规定范围内,例如:
VSSA ≤ VIN ≤ VDDA
具体范围需要查看芯片数据手册。
如果输入超过范围,可能出现:
- ADC读数满量程;
- 相邻通道受到影响;
- 内部保护二极管导通;
- 向模拟电源注入电流;
- 采样结果异常;
- 严重时损坏芯片。
不要认为增加一个串联电阻就一定安全。
还要检查:
- 最大注入电流;
- 输入钳位结构;
- 输入负压;
- 上电时序;
- 外部信号在MCU掉电时是否仍然存在。
10. 传感器自身不稳定
ADC只是采集工具,输入源本身也可能波动。
例如:
- 热敏电阻受气流影响;
- 压力传感器电源不稳定;
- 电流传感器零点漂移;
- 光敏电阻受环境光变化;
- 电位器接触不良;
- 长线传感器受到工频干扰;
- 电桥输出受温度影响。
排查时需要同时测量:
ADC引脚真实电压
不能只看软件打印值。
建议使用:
- 高精度万用表;
- 示波器;
- 稳定直流源;
- 示波器统计功能。
六、多通道ADC为什么会互相影响
STM32等MCU通常使用一个ADC转换核心,通过模拟多路复用器依次连接不同通道。
例如:
通道1:3.0V
通道2:0.2V
通道3:1.5V
如果通道1刚刚采样了3.0V,内部采样电容中还保存着较高电压。
紧接着切换到高阻抗的通道2,采样时间又很短,内部电容可能无法及时从3.0V放电到0.2V。
结果就可能出现:
通道2第一次采样值偏高;
第二次或第三次采样才恢复正常。
解决方法
方法一:增加采样时间
这是最直接的方法。
方法二:降低信号源阻抗
通过降低分压电阻或者增加运放缓冲。
方法三:切换通道后丢弃第一次结果
例如:
ADC_SelectChannel(channel);
ADC_ReadOnce(); /* 丢弃第一次 */
value = ADC_ReadOnce();
但这只是工程补偿方法,不能代替合理的模拟前端设计。
方法四:调整通道扫描顺序
可以尽量让相邻通道电压接近,或者将高阻抗通道安排在更有利的位置。
方法五:增加通道间隔时间
使用定时器触发或者软件延时,为信号稳定留出时间。
七、ADC结果不稳定的软件原因
1. ADC时钟设置过高
ADC时钟通常存在最大允许频率。
如果ADC时钟超过数据手册规定范围,可能导致:
- 转换误差增大;
- 数据不稳定;
- 校准异常;
- 某些温度或电压条件下失效。
需要检查:
系统时钟
↓
APB时钟
↓
ADC时钟源
↓
ADC分频器
↓
实际ADC时钟
不能只看主频,也不能直接认为ADC时钟等于CPU时钟。
2. 采样时间配置不合理
以STM32 HAL库为例:
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_5;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
如果输入阻抗较大,2.5 cycles可能太短。
可以逐步增加:
sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
或者:
sConfig.SamplingTime = ADC_SAMPLETIME_47CYCLES_5;
再比较采样结果。
不同STM32系列的采样周期枚举值不同,以上名称仅用于说明思路。
3. 没有执行ADC校准
部分STM32系列支持ADC偏置校准。
例如某些系列可以使用:
if (HAL_ADCEx_Calibration_Start(
&hadc1,
ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler();
}
部分旧系列的HAL函数可能是:
HAL_ADCEx_Calibration_Start(&hadc1);
不同芯片系列的校准流程和函数参数并不相同,必须查阅对应HAL驱动和参考手册。
ST 的ADC资料指出,部分STM32 ADC具有自动校准或偏置校准机制,可以用于减小ADC内部偏置误差。
推荐顺序:
ADC初始化
↓
执行校准
↓
等待校准完成
↓
启动ADC转换
4. 读取时序错误
轮询方式读取ADC时,应等待转换完成。
uint32_t ADC_ReadOnce(void)
{
uint32_t value;
if (HAL_ADC_Start(&hadc1) != HAL_OK)
{
return 0;
}
if (HAL_ADC_PollForConversion(
&hadc1,
10) != HAL_OK)
{
HAL_ADC_Stop(&hadc1);
return 0;
}
value = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
return value;
}
常见错误包括:
- ADC还没转换完成就读取数据寄存器;
- 启动失败后仍然读取旧数据;
- 超时后没有处理错误;
- 连续模式和单次模式理解错误;
- EOC标志配置不正确;
- 过载后数据被覆盖。
5. ADC通道配置错误
需要检查:
- ADC实例是否正确;
- 通道号是否正确;
- GPIO是否配置为模拟模式;
- Rank顺序是否正确;
- 扫描通道数量是否正确;
- DMA缓冲区长度是否匹配;
- 引脚复用和封装是否对应;
- 内部通道是否完成启动时间等待。
如果引脚没有设置为模拟模式,数字输入结构可能增加功耗或者引入干扰。
6. DMA数据对应关系错误
多通道DMA采集时,数据通常按照Rank顺序写入数组。
例如:
uint16_t adc_buffer[3];
配置顺序:
Rank 1:ADC_CHANNEL_1
Rank 2:ADC_CHANNEL_5
Rank 3:ADC_CHANNEL_8
那么缓冲区通常对应:
adc_buffer[0] → CHANNEL_1
adc_buffer[1] → CHANNEL_5
adc_buffer[2] → CHANNEL_8
如果软件误以为:
adc_buffer[0] → CHANNEL_5
就会出现“ADC数据乱跳”的假象。
实际并不是采样不稳定,而是通道映射错误。
7. DMA缓冲区类型或长度错误
例如ADC配置为半字传输,但缓冲区错误地使用了8位类型:
uint8_t adc_buffer[8];
更合理的是:
uint16_t adc_buffer[8];
还需要检查:
- DMA数据宽度;
- 内存数据宽度;
- 循环模式;
- 缓冲区长度;
- 数据对齐;
- 半传输和全传输回调。
8. Cache一致性问题
在带数据缓存的Cortex-M7等平台中,DMA直接写入内存后,CPU可能仍然读取Cache中的旧数据。
此时可能表现为:
- DMA数据似乎不更新;
- 数据间歇性变化;
- 调试器看到的值与程序读取值不一致;
- 修改优化等级后现象改变。
需要根据平台处理:
- Cache失效;
- Cache清理;
- DMA缓冲区对齐;
- 将DMA缓冲区放入非Cache区域;
- 配置MPU属性。
这个问题主要出现在带D-Cache的MCU,不适用于所有STM32型号。
9. 整数计算溢出
例如计算平均值:
uint16_t sum = 0;
for (int i = 0; i < 100; i++)
{
sum += adc_buffer[i];
}
12位ADC最大值为4095。
100个最大值相加:
4095 × 100 = 409500
已经超过16位无符号整数最大值65535。
结果会发生溢出,平均值当然错误。
应该使用:
uint32_t sum = 0;
如果采样数量更多,甚至需要使用:
uint64_t sum = 0;
10. 电压换算公式错误
12位ADC常见换算方式为:
voltage = adc_value * vref / 4095.0f;
常见错误包括:
voltage = adc_value / 4095 * 3.3f;
如果 adc_value 和 4095 都是整数,整数除法会先执行。
例如:
2048 / 4095 = 0
最终结果会变成0。
应改为:
voltage = (float)adc_value * 3.3f / 4095.0f;
或者使用整数毫伏计算:
uint32_t voltage_mv;
voltage_mv = adc_value * 3300UL / 4095UL;
11. 参考电压写死为3.3V
下面的代码并不一定准确:
voltage = adc_value * 3.3f / 4095.0f;
因为实际VDDA可能是:
3.27V
3.31V
3.24V
如果对精度有要求,应使用实际测量值或者VREFINT补偿。
12. 采样频率选择不合理
采样频率太低,可能导致:
- 快速信号丢失;
- 混叠;
- 波形失真;
- 偶发采到尖峰。
采样频率过高,也可能导致:
- 输入电路没有足够建立时间;
- 高阻抗信号采样误差增大;
- CPU负担增加;
- DMA数据量过大;
- 电源噪声增加。
采样频率应根据:
- 信号带宽;
- 输入阻抗;
- ADC转换速度;
- 滤波器截止频率;
- 系统处理能力;
综合确定。
13. 采样时刻不固定
如果软件在主循环中直接读取ADC:
while (1)
{
value = ADC_ReadOnce();
DoSomething();
}
由于其他任务耗时不固定,ADC采样间隔也会变化。
对于周期信号,这可能造成采样值看起来非常混乱。
更可靠的方法是:
定时器产生固定触发
↓
ADC开始采样
↓
DMA保存结果
这样采样周期更加稳定。
八、ADC滤波是不是采样越多越好
不是。
增加采样数量虽然可以减小部分随机噪声,但会带来:
- 响应速度下降;
- CPU占用增加;
- 延迟增大;
- 瞬态变化被削弱;
- 无法消除系统误差;
- 无法解决参考电压漂移。
滤波之前必须先判断噪声类型。
九、常用ADC数字滤波方法
1. 限幅滤波
如果新数据与上一次数据相差过大,可以先认为它可能是异常值。
uint16_t ADC_LimitFilter(
uint16_t new_value,
uint16_t old_value,
uint16_t threshold)
{
uint16_t difference;
if (new_value > old_value)
{
difference = new_value - old_value;
}
else
{
difference = old_value - new_value;
}
if (difference > threshold)
{
return old_value;
}
return new_value;
}
适合:
- 缓慢变化信号;
- 偶发尖峰噪声;
- 温度和电池电压。
不适合:
- 本身变化很快的信号;
- 电流突变检测;
- 快速控制系统。
2. 算术平均滤波
连续采样N次后求平均值:
uint16_t ADC_AverageFilter(uint16_t sample_count)
{
uint32_t sum = 0;
if (sample_count == 0)
{
return 0;
}
for (uint16_t i = 0; i < sample_count; i++)
{
sum += ADC_ReadOnce();
}
return (uint16_t)(sum / sample_count);
}
优点:
- 实现简单;
- 可以减小随机噪声;
- 适合直流和缓慢信号。
缺点:
- 采样次数越多,响应越慢;
- 无法有效处理大幅异常值;
- 无法消除系统误差。
3. 去极值平均滤波
先采集多个数据,去掉最大值和最小值,再计算平均。
uint16_t ADC_TrimmedMeanFilter(uint16_t sample_count)
{
uint32_t sum = 0;
uint16_t value;
uint16_t max_value = 0;
uint16_t min_value = 0xFFFF;
if (sample_count < 3)
{
return ADC_ReadOnce();
}
for (uint16_t i = 0; i < sample_count; i++)
{
value = ADC_ReadOnce();
sum += value;
if (value > max_value)
{
max_value = value;
}
if (value < min_value)
{
min_value = value;
}
}
sum -= max_value;
sum -= min_value;
return (uint16_t)(sum / (sample_count - 2));
}
适合存在偶发尖峰的场景。
4. 中值滤波
将采样数据排序,取中间值。
例如:
原始数据:
1000、1002、1001、3000、999
排序后:
999、1000、1001、1002、3000
中值:
1001
中值滤波对脉冲型异常值非常有效。
uint16_t ADC_Median5(
uint16_t a,
uint16_t b,
uint16_t c,
uint16_t d,
uint16_t e)
{
uint16_t data[5] = {a, b, c, d, e};
for (uint8_t i = 0; i < 4; i++)
{
for (uint8_t j = 0; j < 4 - i; j++)
{
if (data[j] > data[j + 1])
{
uint16_t temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
return data[2];
}
5. 滑动平均滤波
滑动平均不是每次重新采集N个数据,而是保存最近N个样本。
新数据进入时,最旧数据移出。
#define ADC_WINDOW_SIZE 16
typedef struct
{
uint16_t buffer[ADC_WINDOW_SIZE];
uint32_t sum;
uint16_t index;
uint8_t initialized;
} ADC_MovingAverage_t;
uint16_t ADC_MovingAverageUpdate(
ADC_MovingAverage_t *filter,
uint16_t new_value)
{
if (filter == NULL)
{
return new_value;
}
if (filter->initialized == 0)
{
for (uint16_t i = 0; i < ADC_WINDOW_SIZE; i++)
{
filter->buffer[i] = new_value;
}
filter->sum =
(uint32_t)new_value * ADC_WINDOW_SIZE;
filter->index = 0;
filter->initialized = 1;
}
filter->sum -= filter->buffer[filter->index];
filter->buffer[filter->index] = new_value;
filter->sum += new_value;
filter->index++;
if (filter->index >= ADC_WINDOW_SIZE)
{
filter->index = 0;
}
return (uint16_t)(
filter->sum / ADC_WINDOW_SIZE
);
}
优点:
- 计算效率较高;
- 适合连续采样;
- 波动比较平滑。
缺点:
- 存在窗口延迟;
- 窗口越大,响应越慢。
6. 一阶IIR低通滤波
一阶低通滤波常用公式为:
Y(k) = Y(k-1) + α × [X(k) - Y(k-1)]
其中:
X(k):当前原始采样值
Y(k):当前滤波结果
Y(k-1):上一次滤波结果
α:滤波系数,范围通常为0~1
例如:
float ADC_IIRFilter(
float input,
float alpha)
{
static float output = 0.0f;
static uint8_t initialized = 0;
if (alpha < 0.0f)
{
alpha = 0.0f;
}
if (alpha > 1.0f)
{
alpha = 1.0f;
}
if (initialized == 0)
{
output = input;
initialized = 1;
}
output += alpha * (input - output);
return output;
}
参数规律:
α越大:
响应越快,滤波越弱。
α越小:
响应越慢,滤波越强。
十、过采样能提高ADC分辨率吗
在满足条件时,过采样和抽取可以改善信噪比,并可能提高有效分辨率。
基本流程是:
以更高频率采样
↓
累加多个结果
↓
平均或右移
↓
降低输出数据率
例如进行16倍过采样:
连续采样16次
↓
累加
↓
除以16
部分STM32型号带有硬件过采样模块,可以自动完成累加和移位,减少CPU参与。ST的资料同时介绍了硬件和软件过采样方法,主要用于改善信噪比和有效分辨率。
但过采样不是万能的。
它通常要求噪声具有一定随机性。如果输入和参考完全固定、结果始终停留在同一个ADC代码上,简单重复采样并不会凭空产生额外信息。
过采样还会降低最终输出数据率:
采样率越高
↓
累加数量越多
↓
输出更新速度越慢
十一、STM32 ADC稳定采集示例
下面以单通道轮询采集为例。
1. ADC初始化后进行校准
void ADC_ApplicationInit(void)
{
MX_ADC1_Init();
/*
* 注意:
* 不同STM32系列的校准函数参数不同。
* 请根据对应HAL库修改。
*/
if (HAL_ADCEx_Calibration_Start(
&hadc1,
ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler();
}
}
2. 单次读取函数
HAL_StatusTypeDef ADC_ReadRaw(
ADC_HandleTypeDef *hadc,
uint32_t *value)
{
HAL_StatusTypeDef status;
if ((hadc == NULL) || (value == NULL))
{
return HAL_ERROR;
}
status = HAL_ADC_Start(hadc);
if (status != HAL_OK)
{
return status;
}
status = HAL_ADC_PollForConversion(
hadc,
10);
if (status != HAL_OK)
{
HAL_ADC_Stop(hadc);
return status;
}
*value = HAL_ADC_GetValue(hadc);
return HAL_ADC_Stop(hadc);
}
3. 32次去极值平均
#define ADC_SAMPLE_COUNT 32U
HAL_StatusTypeDef ADC_ReadFiltered(
ADC_HandleTypeDef *hadc,
uint16_t *filtered_value)
{
uint32_t sum = 0;
uint32_t value;
uint32_t max_value = 0;
uint32_t min_value = 0xFFFFFFFFUL;
if ((hadc == NULL) ||
(filtered_value == NULL))
{
return HAL_ERROR;
}
for (uint32_t i = 0;
i < ADC_SAMPLE_COUNT;
i++)
{
if (ADC_ReadRaw(hadc, &value) != HAL_OK)
{
return HAL_ERROR;
}
sum += value;
if (value > max_value)
{
max_value = value;
}
if (value < min_value)
{
min_value = value;
}
}
sum -= max_value;
sum -= min_value;
*filtered_value = (uint16_t)(
sum / (ADC_SAMPLE_COUNT - 2U)
);
return HAL_OK;
}
4. 换算为毫伏
uint32_t ADC_CodeToMillivolt(
uint16_t adc_code,
uint32_t vref_mv,
uint8_t resolution_bits)
{
uint32_t full_scale;
if ((resolution_bits == 0U) ||
(resolution_bits >= 31U))
{
return 0U;
}
full_scale =
(1UL << resolution_bits) - 1UL;
return (
(uint32_t)adc_code * vref_mv
) / full_scale;
}
调用示例:
uint16_t adc_value;
uint32_t voltage_mv;
if (ADC_ReadFiltered(
&hadc1,
&adc_value) == HAL_OK)
{
voltage_mv = ADC_CodeToMillivolt(
adc_value,
3300U,
12U);
}
十二、如何利用VREFINT补偿参考电压
部分STM32内部带有参考电压通道VREFINT,并在系统存储区保存出厂校准值。
基本思路是:
已知内部参考电压比较稳定
↓
使用ADC测量VREFINT
↓
根据VREFINT ADC结果估算实际VDDA
↓
用实际VDDA换算其他通道
概念公式可以写为:
VDDA实际值
≈ 校准时VDDA × VREFINT_CAL / VREFINT_ADC
不同STM32系列的:
- 校准电压;
- 校准数据地址;
- 计算宏;
- 温度条件;
可能不同。
必须使用该芯片数据手册提供的公式和校准常量,不能直接套用其他型号代码。
十三、典型现象与原因对照表
| ADC现象 | 可能原因 |
|---|---|
| 上下波动1~3个码 | 量化误差、正常底噪 |
| 随机跳动几十个码 | 电源噪声、输入悬空、布局干扰 |
| 周期性波动 | PWM、电机、DC-DC或工频干扰 |
| 数值稳定但整体偏高 | VREF估计错误、增益误差 |
| 数值稳定但整体偏低 | 采样时间不足、输入阻抗过大 |
| 切换通道后第一笔异常 | 采样电容残留、通道串扰 |
| 读数缓慢漂移 | 温度、参考源或传感器漂移 |
| 经常读取0 | 引脚接地、通道错误、读取失败 |
| 经常读取满量程 | 输入超范围、引脚开路或配置错误 |
| DMA数据顺序混乱 | Rank、缓冲区和DMA长度不匹配 |
| 电机启动后异常 | 电源跌落、地弹、EMI干扰 |
| 降低采样率后变准 | 输入阻抗过大、建立时间不足 |
| 增加采样时间后变准 | 内部采样电容未充分充电 |
| 平均值正常但瞬时值很乱 | 高频噪声或缺少滤波 |
| 万用表正常、ADC异常 | 万用表带宽低,可能看不到高频噪声 |
十四、ADC完整排查流程
遇到ADC不稳定时,可以按照以下顺序排查。
第一步:使用稳定电压源替代传感器
先不要直接测试复杂传感器。
可以使用:
- 稳压电源;
- 精密基准;
- 电池;
- 电位器;
- DAC输出。
观察ADC是否仍然波动。
第二步:测量ADC引脚真实波形
使用示波器测量:
ADC引脚对模拟地的电压
重点查看:
- 平均电压;
- 纹波大小;
- 高频尖峰;
- PWM同步噪声;
- 50Hz或60Hz干扰;
- 电机启停瞬态。
第三步:检查参考电压
测量:
VDDA
VREF+
VSSA
观察负载变化时是否波动。
第四步:检查输入阻抗
计算信号源等效输出阻抗。
对于电阻分压器:
ROUT = R1 || R2
即:
ROUT = R1 × R2 / (R1 + R2)
如果阻抗较大:
- 增加采样时间;
- 降低分压电阻;
- 增加输入电容;
- 增加运放缓冲。
第五步:增加采样时间
将ADC采样周期逐步增加。
如果采样时间越长,结果越准确,通常说明:
信号源阻抗过大
或者
输入建立时间不足
第六步:降低ADC时钟
检查ADC时钟是否超过芯片规格,并适当降低ADC时钟进行对比测试。
第七步:执行ADC校准
如果芯片支持校准,应按照参考手册执行。
第八步:只保留一个ADC通道
暂时关闭多通道扫描和DMA,只测试一个通道。
如果单通道正常、多通道异常,重点检查:
- 通道Rank;
- 采样时间;
- 通道切换残留;
- DMA顺序;
- 缓冲区长度。
第九步:取消数字滤波看原始数据
先打印原始ADC结果。
不要一开始就使用复杂滤波,否则可能掩盖问题。
建议同时打印:
最小值
最大值
平均值
峰峰值
例如:
printf(
"ADC min=%lu max=%lu avg=%lu pp=%lu\r\n",
min_value,
max_value,
average,
max_value - min_value);
第十步:最后再设计数字滤波
确认硬件和ADC配置正确后,再根据应用选择:
- 平均滤波;
- 滑动平均;
- 中值滤波;
- 限幅滤波;
- IIR低通;
- 过采样;
- 卡尔曼滤波。
十五、一个实用的ADC稳定性测试程序
下面程序连续采集一组数据,并统计最小值、最大值、平均值和峰峰值。
typedef struct
{
uint32_t minimum;
uint32_t maximum;
uint32_t average;
uint32_t peak_to_peak;
} ADC_Statistics_t;
HAL_StatusTypeDef ADC_GetStatistics(
ADC_HandleTypeDef *hadc,
uint32_t sample_count,
ADC_Statistics_t *statistics)
{
uint64_t sum = 0;
uint32_t value;
uint32_t minimum = 0xFFFFFFFFUL;
uint32_t maximum = 0;
if ((hadc == NULL) ||
(statistics == NULL) ||
(sample_count == 0U))
{
return HAL_ERROR;
}
for (uint32_t i = 0;
i < sample_count;
i++)
{
if (ADC_ReadRaw(hadc, &value) != HAL_OK)
{
return HAL_ERROR;
}
sum += value;
if (value < minimum)
{
minimum = value;
}
if (value > maximum)
{
maximum = value;
}
}
statistics->minimum = minimum;
statistics->maximum = maximum;
statistics->average =
(uint32_t)(sum / sample_count);
statistics->peak_to_peak =
maximum - minimum;
return HAL_OK;
}
调用:
ADC_Statistics_t adc_stats;
if (ADC_GetStatistics(
&hadc1,
1000U,
&adc_stats) == HAL_OK)
{
printf(
"min=%lu, max=%lu, avg=%lu, pp=%lu\r\n",
adc_stats.minimum,
adc_stats.maximum,
adc_stats.average,
adc_stats.peak_to_peak);
}
这个程序比只观察单个ADC值更加有意义。
例如:
min = 2045
max = 2050
avg = 2048
pp = 5
说明平均值比较稳定,峰峰波动约5个ADC码。
如果结果是:
min = 1800
max = 2300
avg = 2040
pp = 500
则说明存在明显干扰,不能仅依靠平均滤波处理。
十六、常见错误做法
错误一:无限增加平均次数
平均1000次虽然可能很稳定,但系统响应可能已经无法满足要求。
错误二:看到波动就认为ADC坏了
几个LSB的轻微波动可能是正常现象。
错误三:只看万用表,不看示波器
万用表显示的是低带宽平均结果,可能看不到高频纹波和尖峰。
错误四:默认参考电压一定是3.3V
ADC结果与VREF直接相关,参考电压必须测量或补偿。
错误五:高阻分压后直接接ADC
必须检查输出阻抗与采样时间是否匹配。
错误六:滤波解决所有问题
数字滤波不能修复:
- 输入超范围;
- VREF错误;
- 采样时间不足;
- 通道映射错误;
- ADC时钟超限;
- PCB布局问题。
错误七:所有通道都使用最短采样时间
不同信号源阻抗可能不同,需要分别配置合理采样时间。
十七、工程中如何设计一个稳定ADC采集系统
一个比较完整的ADC采集设计应该包含:
传感器或模拟输入
↓
输入保护
↓
分压或放大
↓
RC低通滤波
↓
运放缓冲(可选)
↓
ADC输入
↓
稳定参考电压
↓
定时器固定触发
↓
DMA连续采集
↓
异常值处理
↓
数字低通滤波
↓
校准和单位换算
设计时需要同时考虑:
- 输入电压范围;
- 输入阻抗;
- 信号带宽;
- 采样频率;
- 分辨率;
- 参考电压;
- 噪声水平;
- 响应速度;
- PCB布局;
- 温度漂移;
- 校准方式。
十八、面试中如何回答ADC结果不稳定
如果面试官问:
ADC采集结果为什么不稳定,应该怎么排查?
可以这样回答:
ADC结果不稳定需要从硬件和软件两个方面排查。
硬件方面首先检查参考电压和模拟电源是否稳定,
然后检查ADC输入是否悬空、信号源输出阻抗是否过大、
采样引脚是否存在高频干扰,以及PCB模拟地和数字地的回流是否合理。
由于MCU内部ADC通常包含采样保持电容,
如果输入阻抗过大或者采样时间太短,
采样电容无法充分充电,也会造成测量误差。
软件方面需要检查ADC时钟、采样时间、通道Rank、
校准流程、转换完成标志、DMA缓冲区和电压换算公式。
确认硬件和配置正确后,
再根据应用使用平均滤波、中值滤波、滑动平均、
IIR低通或过采样提高稳定性。
十九、总结
ADC采集结果不稳定,不一定是ADC外设本身损坏。
常见硬件原因包括:
参考电压不稳定;
ADC输入悬空;
信号源阻抗过大;
采样时间不足;
缺少模拟滤波;
电源和地线干扰;
PCB布局不合理;
输入电压超范围;
多通道之间相互串扰。
常见软件原因包括:
ADC时钟配置错误;
采样周期太短;
没有执行ADC校准;
读取时序不正确;
通道Rank和DMA顺序错误;
缓冲区数据类型错误;
整数计算溢出;
参考电压写死;
采样频率不合理;
数字滤波方法不合适。
排查ADC问题时,应按照以下顺序进行:
先检查真实模拟电压
↓
再检查参考电压和硬件电路
↓
再检查ADC时钟、采样时间和校准
↓
再检查DMA、通道和计算公式
↓
最后才增加数字滤波
最后用一句话总结:
ADC稳定采集不是单纯增加平均次数,而是要让信号源、参考电压、采样时间、模拟前端、软件配置和数字滤波共同满足系统要求。
更多推荐


所有评论(0)