☰
STM32 ADC电压测量读数不准?从原理到硬件软件全面排查指南
2026/9/28 16:58:19 网站建设 项目流程

干嵌入式这行,ADC绝对是个看着简单、用起来处处是坑的模块。不管是做电池电压监测、台灯亮度调节,还是采集传感器输出,STM32的ADC都是最常用的外设之一。但问题也往往从这里开始:明明数据手册写着12位分辨率,供电也稳定在3.3V,我接一个电位器分压去测电压,读回来的数值却经常和万用表对不上,不是整体偏低,就是上下乱跳。这篇文章就是围绕“STM32 ADC电压测量读数不准”这个老大难问题写的,从ADC采样原理、参考电压、采样时间,到硬件电路和软件滤波,把常见的坑和解决办法一条条捋清楚。不管你是刚开始学STM32的新手,还是做产品急需稳定采集的工程师,都能照着排查一遍。

1. 读数不准之前,先把ADC的“底盘”搞清楚

1.1 12位ADC量出来的其实是“比例”

STM32内置的ADC绝大多数是逐次逼近型,所谓12位分辨率,意思是把参考电压VREF+和地之间的范围切成4096份。它测量结果不是一个绝对电压,而是一个比值:ADC_VALUE = Vin / VREF * 4096。反过来,想要得到电压,就要用这个值乘以参考电压。关键点就在这里:如果参考电压本身不是3.3V,而是3.1V,那你的计算就必然错。这也是很多人一开始就忽略的事。

逐次逼近型ADC内部有个比较器和DAC,通过二分法逐位比较,最终输出数字码对应的是输入电压和参考电压的比值,而不是外部真实电压。所以一旦VREF漂移了0.1V,即使其他所有环节都理想,最终电压也会按比例偏出约3%。有时候用万用表测STM32的供电是3.28V,但代码里写死3.3V,换算出来自然会偏高。想要弄明白“读数不准”,首先要接受这个逻辑:ADC输出天然依赖参考电压,参考电压不准,后面全是白搭。

1.2 采样时间:ADC不是“秒读”的电压表

ADC转换分两个阶段:采样阶段和转换阶段。采样阶段内部开关闭合,让外接信号给内部的采样电容充电;充电结束后开关断开,转换期间保持电容上的电压。如果充电时间不够,电容电压没有完全充到输入电压值,转换结果就会偏低。这还不是小误差:当外部源阻抗很高时,偏低可能达到几十甚至上百mV。ADC采样周期这个参数,很多时候就是误差的源头。

STM32的ADC采样时间是可以配置的,常见有1.5周期到239.5周期,结合ADC时钟频率就能算出实际采样时间。比如ADC时钟12MHz,配置239.5周期,采样时间就是239.5/12M约20us;如果配置1.5周期,只有125ns。125ns对绝大多数外部电路来说都太短了,尤其是高阻信号源。实际中不少“读数偏低”的故障,就是采样时间配置太短造成的。测慢变电压时,直接把采样时间拉长是性价比最高的做法。

1.3 参考电压(VREF)才是那个“秤砣”

ADC的另一个核心是参考电压。对STM32F1系列来说,很多48脚封装的芯片没有独立VREF+引脚,参考电压直接接VDDA;而带VREF+引脚的封装,VREF+如果不特殊处理,也和VDDA相连。所以VDDA或者VREF+的电压质量,直接决定了ADC的“秤砣”准不准。如果VDD上有开关电源带来的纹波,ADC读数就会跟着抖。电源纹波越小,测得越稳。

还有内部参考电压VREFINT,它一般接到一个内部通道,典型值1.2V左右。虽然精度不算高,但可以作为粗校正的参考,用来反推VDDA真实值。不过它本身也随温度和芯片个体漂移,要求高的场合还是建议外部精密基准。总之,想让ADC读数准,第一步不是写代码,而是确保参考电压稳、准、干净。

2. 我实测踩过的坑:读数不准的五大主因

2.1 电源和参考电压的锅

症状很典型:单片机一跑起系统程序,比如轮询按键、刷新屏、驱动继电器,ADC读数就开始跳;但只要程序停在某个死循环里,读数又安静了。这种问题大多不是ADC模块坏了,而是参考电压被数字电路污染。数字IO翻转、内部LDO负载瞬变,都会在VDDA或VREF+上叠加毛刺。尤其接了电机、泵、加热丝这类大电流负载时,供电轨道上的噪声会非常明显。

处理办法有几条。如果有独立VREF+引脚,用高精度基准芯片单独供电;如果没有独立引脚,就把VDDA通过磁珠或小电阻从VDD分离,再接0.1uF和1到10uF电容到地。模拟地和数字地采用单点连接,别让数字电流流过模拟地。这是硬件层面的“止血”,比任何软件滤波都彻底。我曾经在一块电机控制板上做ADC采集,就是因为VDDA和VDD直接短接,导致每次PWM一开启,ADC读数就跳几十个码,后来加了磁珠和电容才稳定下来。

2.2 输入阻抗太高,分压电阻直接把电压“吃掉”

用电阻分压测电池电压,是很常见的玩法。但有人图省电,把分压电阻选成1M加1M,结果ADC读出来的电压低得离谱。原因很简单:ADC采样电容充电需要电流,而高阻源能提供的电流太小。你可以想象用一根很长的吸管喝瓶底剩的饮料,再使劲也吸不上来几口。ADC的采样时间有限,源阻抗越高,电容充电越慢,读数就越低。

工程经验是,分压电阻的总阻值尽量控制在10k到30k以内。比如测锂电池电压,用20k和10k分压,总阻值30k,功耗只有几mW,ADC输入阻抗带来的误差就很小。如果必须用高阻值省电,那就得在ADC引脚前加一个运放跟随器,用低输出阻抗去驱动ADC;或者把采样时间调长,并且在引脚并联一个100nF电容。但电容也不是越大越好,后面会专门讲。

2.3 采样时间配置得太短

我经常看到CubeMX里ADC配置页面,采样周期默认是几个cycle,很多人没改就用了。如果信号源是低阻抗的稳压电源,可能差别不明显;但如果信号源带有几十kΩ的电阻,短采样时间就会导致充电不足,读出来的值偏低,而且信号源阻抗越高,偏低越明显。这是一种“有规律”的误差,不是随机噪声。排查方法非常简单:把采样周期调大,比如改成239.5,如果读数明显回升,就是采样时间不够。

所以,采集慢变信号时,直接把采样时间拉满。以STM32F103、ADC时钟12MHz为例,对应约20us采样时间,牺牲一点转换速度换取准确度,非常划算。只有需要高速采集的应用,才应该仔细评估最短采样时间。另外,如果你的输入信号本身变化很快,还要考虑采样保持期间的信号稳定问题,必要时加采样保持电路。很多ADC读不准,改完采样时间立刻就好,这是成本最低的修复方式。

2.4 引脚配置错误或悬空

STM32的ADC引脚必须配置为模拟输入模式。有人习惯把GPIO配成复用推挽,或者为了省电开了内部上拉,结果读到的值不对。ADC引脚一旦开启上下拉,等于在外部信号上并联了一个内部电阻网络,会改变分压关系。如果引脚彻底悬空,读数就是随机的,这种时候换什么滤波算法都没用。先检查引脚模式,再查外围电路,顺序不能反。

还有一类坑是供电连接问题。有些开发板VDDA或VREF+没有单独接好,或者VREF+虽然接到了3.3V,但电源线上的压降让实际参考电压低于3.3V。你用万用表量芯片供电脚,量出来也许是3.3V,但VREF+引脚因为PCB走线细、接触电阻大,实际可能只有3.29V,这点差异就会让电压读数偏出零点几伏。建议用万用表直接量VREF+引脚和GND,而不是量电源输入端。

2.5 软件处理问题

如果硬件都正常,读数还是不对,就得查软件。最典型的是:连续转换模式下,读寄存器时机不对,读到的是上一次转换的数据;或者没有等待转换完成标志就立刻取结果。另一个是DMA多通道采集时,通道顺序和数据缓冲对应错位,几个通道的值全串了。打印每个通道的原始值,就能立刻发现到底是数值错位还是通道数不匹配。

还有几个容易忽略的细节。一是内部校准,STM32F3/F4/H7系列有ADC校准功能,使用外部信号前先调用校准函数,能有效消除电容失配误差;F1没有这个功能,但可以通过外部标定来补偿。二是左右对齐,读取寄存器时如果配置左对齐,需要自己移位,HAL库一般默认右对齐。三是计算电压时分母用4096还是4095,其实差异很小,标定后都会被吃掉,但别一会用4095一会用4096,不然代码和注释都对不上。

3. 硬件到软件,完整的ADC校正方案

3.1 硬件电路这样做才稳

先看参考电压。如果产品对精度要求比较高,最简单粗暴的办法是给VREF+外接一颗精密基准芯片,比如2.5V或3.0V输出。注意这时软件计算的分母要改成对应基准电压,而不是3.3V。如果没有独立VREF+引脚,就把VDDA用磁珠或小电阻隔离,再加10uF和0.1uF电容。电源入口处的LDO也尽量选低噪声的。ADC参考是测量链路的天花板,基准不稳,后面做再多都白费。

再看输入通道。传感器输出阻抗高的,第一选择是运放跟随器,把高阻信号换成低阻信号。如果只是简单分压测电池,用20k加10k,并在下臂并联一个100nF电容,组成RC低通。这个电容的取值有讲究:太小抗不住噪声,太大会让信号响应变慢。在测缓慢变化的直流电压时,100nF到1uF都合适;如果测的是快速变化信号,就要根据信号频率重新计算RC截止频率。分压电阻的总阻值不要一味贪大,低功耗设计可以用电子开关在测量时才接通分压网络。

PCB布局上,模拟信号走线尽量短、粗,远离PWM输出和高频数字线。ADC引脚周围不要放置频繁翻转的IO,否则会通过寄生电容耦合噪声。用地线或者铺铜把模拟走线包住,能明显降低串扰。模拟地和数字地单点连接,原则是数字电流不要流经模拟参考区。这些细节在原理图阶段就要想好,等板子做出来再飞线补救,效果有限。

3.2 CubeMX配置实操:从采样周期到DMA

用STM32CubeMX创建工程时,ADC配置页需要注意几项。Resolution选择12位;Scan Conversion Mode,单通道关闭,多通道打开;Continuous Conversion Mode看需求,如果只用DMA循环采集,可以打开连续模式;Discontinuous Mode不要乱开。关键是把Sampling Time选到Maximum value,也就是239.5 cycles。ADC时钟设置成不超过芯片允许的最大频率,一般12MHz或14MHz,分频从APB2时钟来。

多通道采集建议用DMA加扫描模式。在DMA设置里把模式设为Circular,数据宽度Half Word,方向PeripheralToMemory。开启DMA循环后,ADC会自动连续转换,结果写入数组。这里有个很容易踩的坑:如果数组定义的是uint16_t,12位分辨率没问题;但如果你把DMA数据宽度错配成Byte,高字节会丢失或错位,读出来的数字完全不对。另外,多通道时数组的第一个元素对应的是配置的第一个转换通道,不是物理通道编号,别搞混。

// 假设已经配置了ADC1_IN1(PA1)单通道,采样周期239.5 uint16_t adc_raw; float voltage; // 适用于F3/F4/H7系列,F1可以去到这行 HAL_ADCEx_Calibration_Start(&hadc1); HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) { adc_raw = HAL_ADC_GetValue(&hadc1); voltage = (float)adc_raw * 3.3f / 4096.0f; } HAL_ADC_Stop(&hadc1);

注意F1没有硬件校准函数,如果是F103系列,就把HAL_ADCEx_Calibration_Start这行去掉,否则编译会报错。对于多通道DMA,初始化之后调用HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, channel_num),然后在HAL_ADC_ConvCpltCallback回调里处理数据。回调里不要做太重的浮点运算,建议先把原始值搬出来,主循环再算电压。我们实际项目里,DMA缓冲区的长度和通道数量必须严格对应,数组越界会导致内存数据被踩,这是隐藏很深的bug。

3.3 软件滤波:不是简单平均就完事

ADC读数跳动,很多人第一反应就是滤波。但滤波不是万能的,先得排除硬件问题,否则滤波只是把错误数据做平滑。软件滤波方案要根据噪声类型选择:如果噪声是白噪声,滑动平均或均值滤波效果不错;如果存在脉冲干扰、偶尔跳一下大值,中值滤波更合适;实际工程最常用的是“中值平均滤波”,连续采N个值,去掉最大值和最小值,再对剩下的求平均,兼顾抗脉冲和抑制随机噪声。

#define ADC_SAMPLE_COUNT 10 uint16_t ADC_Filter(uint16_t *buf, uint8_t len) { uint16_t tmp; // 排序,也可以用其他排序方式 for (uint8_t i = 0; i < len - 1; i++) { for (uint8_t j = 0; j < len - 1 - i; j++) { if (buf[j] > buf[j + 1]) { tmp = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = tmp; } } } uint32_t sum = 0; // 去掉一个最大值和一个最小值 for (uint8_t i = 1; i < len - 1; i++) sum += buf[i]; return (uint16_t)(sum / (len - 2)); }

滤波的代价是响应变慢。比如一秒采一次,做10次滤波,输出刷新就要10秒。所以要根据系统动态要求调整采样频率和滤波窗口。高频采集场景可以用滑动平均,每来一个新值就更新输出,不要求等满N次。这里还有一个工程小技巧:滤波窗口最好选2的幂次方,比如8、16、32,求平均时用右移代替除法,更省时间。对STM32F103这种主频不高的芯片,在中断里反复做浮点除法不是好习惯,能用移位就用移位。

3.4 两点标定:把芯片个体差异“拉回来”

即使参考电压稳定、采样时间足够,不同芯片、不同温度下的ADC仍会有偏移和增益误差。举个例子:同一块板子上两个ADC通道,一个在0V输入时读出8到10个LSB,另一个读出0,这就是偏移误差;在满量程附近,一个读到4093,另一个读到4085,又存在增益误差。用两点标定可以一次性把这两种误差都校正掉。

做法很简单。准备一个可调精密电源或者高精度万用表,给待测引脚施加两个已知电压V1、V2,记录对应的ADC原始值raw1、raw2。然后用直线方程V = K * raw + B,其中K = (V2 - V1)/(raw2 - raw1),B = V1 - K * raw1。之后把K和B存下来,每次测量都套这个公式。V1和V2最好分别选择量程的20%和80%附近,不要一个选0V一个选3.3V,否则标定结果对中间区域覆盖不太均匀。

// 假设标定得到:V1=1.000V时raw1=1240,V2=3.000V时raw2=3720 double K = (3.000 - 1.000) / (3720 - 1240); // 约0.00080645 double B = 1.000 - K * 1240; // 约0.0 double voltage = K * (double)adc_raw + B;

这里要提醒一句:如果输入前端有分压电阻,标定的是分压之后的电压,要换算回真实电压,还需要除以分压比。更好的做法是标定整个链路,也就是从电池正极到ADC读数的完整路径,这样分压电阻的误差也能一起补偿。前提是分压电阻温漂不能太大,最好用1%甚至0.1%精度电阻。标定数据建议在出厂时写进Flash,运行过程中再配合内部基准周期校验。

4. 从现象到根因:常见问题排查实录

4.1 根据现象快速定位

现象最可能原因诊断方法解决方向
输入固定时读数随机跳电源/参考噪声示波器看VDDA纹波,输入接稳定电池加强滤波、加基准、检查地线
读数整体偏低且有规律采样时间不足或源阻抗过高调大采样周期后是否好转加大采样时间、降低分压电阻、加跟随器
读数偏高但比例固定参考电压或增益误差万用表量VREF+,做两点标定修正常数、换基准
0V输入时读数不为0偏移误差将引脚直接接地看原始码软件减offset、校准
某一路读数串到另一路DMA缓冲区错位打印各组数字,对比先后顺序检查转换顺序和DMA数组
所有通道都为0引脚模式/时钟未开查GPIO配置和ADC使能配置为模拟输入、开启ADC时钟
读数忽大忽小,接不接信号源都变引脚悬空万用表量引脚电压固定输入、接下拉或传感器

这张表基本覆盖了我工作中遇到的大多数ADC问题。排查的时候建议先看“0V输入读数是否为0”,再看“固定电压读数是否稳定”,最后看“比例是否准确”。把问题拆成偏移、噪声、增益三个维度,很多奇怪现象就清晰了。一定要从原始码入手,不要一开始就看换算后的电压,否则容易把问题和参考电压混在一起。

4.2 一个实际案例:锂电池电压采集

项目里经常要测两节锂电池串联的电压,最高8.4V,STM32 ADC引脚一般只能承受0到3.3V,所以必须用电阻分压。我的电路选择R1=20k、R2=10k,最高分压到2.8V,留有裕量。下臂并联一个100nF电容,滤除高频噪声。R1和R2都用1%精度,温度系数选低一点的,否则标定意义不大。这里的总源阻抗是R1和R2并联,约6.7k,对ADC来说已经非常友好。

CubeMX里ADC1配置为单通道,采样时间239.5周期。代码流程是:上电延时稳定后,连续采样11次,去除最大最小,对中间9次求平均,得到raw;再用之前两点标定得到的K、B算出分压电压,最后乘上分压比(R1+R2)/R2,就得到电池电压。实测下来,和万用表相比误差能控制在±20mV以内,在12位ADC的量程上已经算不错了。

这里有一个容易忽视的细节:电阻分压网络会持续从电池取电,我用的是20k加10k,在8.4V下损耗约2.35mW,对一般锂电来说可忽略。但如果你把电阻加大到1M,虽然功耗更低,却会带来ADC采样不准的问题。所以设计时要在功耗和精度之间权衡。如果电池需要长时间待机,可以考虑用MOS管或单片机引脚在测量时才给分压网络供电,测完断开。这个方案我后来用在低功耗产品上,待机电流降了几个数量级,同时ADC精度没有损失。

4.3 排查工具和流程

推荐准备一个带ADC功能的万用表,或者直接用一个可调直流源。排查步骤从简单到复杂:第一,把ADC引脚接地,看原始码是否接近0;第二,把ADC引脚接一个已知电压,比如1.5V电池或精密源,看原始码是否接近理论值;第三,串口打印原始值,连续观察10分钟,看波动范围。这一步不要换算成电压,只看原始码,更容易隔离问题。

如果原始码波动大,用示波器量一下VDDA和ADC引脚的波形。很多时候你会发现,VDDA上有几十mV的开关纹波,这就是罪魁祸首。把示波器探头放到ADC引脚,通常能看到叠加在直流上的随机噪声。万用表是量不出这种噪声的,因为万用表响应慢。所以有条件还是借个示波器,排查效率会高很多。没有示波器的话,可以临时用一个低噪声LDO给VDDA单独供电,或者把ADC引脚接一颗大电解电容,看读数是否变稳,这也能间接判断噪声来源。

最后再分享一个我自己的习惯:无论项目大小,我都会在ADC测量链路里保留一个固定分压的参考通道,哪怕只是把VREF+通过电阻分压后接到一个空闲ADC引脚。这样系统运行时可以实时监测参考电压是否变化,一旦软件判断参考漂移超过阈值,就触发重新校准或报警。这个做法帮我在不少现场问题中快速排除了ADC的嫌疑。ADC读数不准,九成不是芯片本身的问题,而是电源、阻抗、时间和软件配合的问题,按顺序排查,基本都能解决。

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

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

立即咨询