1. 从“能读到数”到“读得准”:STM32电压测量的核心痛点
很多人第一次用STM32做ADC采样,代码跑通那一刻特别兴奋——串口打印出一串数字,觉得大功告成。但把万用表往板子上一搭,心就凉了半截:万用表显示1.500V,STM32算出来是1.42V,差了80mV。再换个电压,误差又变了。这不是代码写错了,而是从“能读到数”到“读得准”之间,隔着一整套信号链设计、参考源选择、校准策略和软件滤波的功夫。
这篇内容就是围绕“精确测量电压”这个目标,把STM32的ADC从配置到校准、从硬件到软件、从理论到实测的完整链路拆开讲清楚。适合已经会点灯、会串口、会基本ADC配置,但测量结果总是不尽如人意的嵌入式开发者。我会用常见的STM32F103和STM32F4系列作为参考平台,因为这两个系列在工业测量和DIY项目中用量极大,资料也最丰富。核心不是教你调库函数,而是让你理解每一个配置项背后的物理意义,知道误差从哪里来,以及怎么把它压下去。
先给一个反直觉的结论:STM32的12位ADC,在默认配置下,有效位数往往只有9到10位。也就是说,你读到的4096个码值里,真正有意义的只有512到1024个。剩下的低位全是噪声和误差。精确测量的第一步,不是写更复杂的滤波算法,而是承认这个现实,然后从参考电压、采样时间、硬件布局三个方向去挤掉水分。
2. 参考电压:精度链条上最容易被忽视的一环
2.1 VDDA和VREF+到底该怎么接
STM32的ADC以VREF+为满量程参考。对于100脚以上的封装,VREF+是独立引脚;对于64脚及以下的封装,VREF+在内部连接到VDDA。很多开发板为了省事,直接把VDDA接到3.3V稳压芯片的输出,而那个3.3V又是给整个板子供电的,上面挂着MCU、显示屏、WiFi模块,纹波和负载跳变全耦合进来了。
我实测过一块常见的F103最小系统板,用示波器交流耦合看VDDA引脚,纹波峰峰值大约30mV。3.3V参考下,30mV对应约37个ADC码值。也就是说,你的测量结果天然就在±18个码值之间跳动,换算成电压就是±15mV。这还没算ADC本身的量化误差和非线性。
正确的做法是:VREF+单独用一个低噪声LDO供电,比如输出3.0V或2.5V的高精度基准源。如果板子空间有限,至少要在VDDA和VSSA之间加一个10uF钽电容并联100nF陶瓷电容,并且这两个电容要尽量靠近引脚。更讲究一点,可以在VREF+和VDDA之间串一个10欧姆电阻,再在VREF+侧加对地电容,构成RC低通,把高频噪声挡在参考引脚外面。
注意:STM32F4系列的数据手册明确要求,当VDDA与VREF+之间的电压差超过一定值时,ADC精度会显著下降。如果VREF+独立供电,务必保证VREF+不超过VDDA,且上电顺序要合理。
2.2 内部参考电压VREFINT的妙用
STM32内部有一个绝对参考电压源VREFINT,典型值1.20V左右,但每颗芯片的具体值不同。出厂时,厂商会在特定条件下测量这个电压对应的ADC码值,存在系统存储区里。你可以通过读取这个出厂校准值,反推出当前VREF+的实际电压。
具体操作是:先读VREFINT对应的ADC通道,得到一个码值,再用出厂校准值去算。公式很简单:VREF+实际值 = 1.20V × 出厂校准码值 / 当前VREFINT码值。这样你就能在不增加任何外部元件的情况下,知道VREF+到底偏了多少。如果偏得厉害,可以在软件里做补偿。
这个方法我经常用在成本敏感的场合,省掉外部基准源,靠软件校准把精度拉回可接受范围。实测下来,用VREFINT补偿后,不同板子之间的测量一致性可以从±3%提升到±0.5%以内。当然,温漂还是存在的,VREFINT本身也有温漂,但比外部3.3V稳压器的温漂小得多。
2.3 参考电压的噪声抑制
除了选好基准源,布局也很关键。VREF+的走线要尽量短而粗,远离时钟线、数据线和电源开关节点。如果板子有DC-DC开关电源,VREF+的走线绝对不能从电感或二极管下方穿过。我见过一个案例,VREF+走线从DC-DC电感旁边经过,ADC读数每隔几十毫秒就跳一下,正好对应开关电源的开关频率。后来把走线绕开,问题立刻消失。
另外,ADC采样时,内部采样电容会从VREF+抽取瞬间电流。如果VREF+的驱动能力不足,采样瞬间电压会被拉低,导致转换结果偏小。所以VREF+的旁路电容不能省,而且要用低ESR的陶瓷电容。数据手册里推荐的1uF加10nF组合,是有道理的,不是随便写的。
3. 采样时间与信号源阻抗:为什么你的读数总是偏小
3.1 采样保持电路的等效模型
STM32的ADC前端可以等效为一个开关、一个采样电阻和一个采样电容。采样阶段,开关闭合,外部信号通过采样电阻给采样电容充电。如果外部信号源的内阻太大,或者采样时间太短,电容充不到信号的真实电压,转换结果就会偏小。
这个模型解释了一个常见现象:用高阻值分压电阻测电压时,读数总是比万用表低。比如用两个100k电阻分压测5V,理论分压2.5V,但STM32读出来可能只有2.2V。原因就是100k电阻和采样电容构成了RC充电回路,时间常数太大,采样时间内充不满。
STM32F103的采样电容大约是8pF,采样电阻在几k欧姆量级。如果外部源阻抗是100k,时间常数就是100k乘以8pF,等于800ns。而默认采样时间可能只有1.5个ADC时钟周期,在14MHz ADC时钟下约107ns。远远不够。所以读数必然偏小。
3.2 采样时间的计算与选择
采样时间应该满足:采样时间 > 5倍RC时间常数。以100k源阻抗和8pF采样电容为例,RC等于800ns,5倍就是4us。STM32F103的ADC时钟最快14MHz,一个周期约71ns。4us对应约56个周期。所以采样时间至少要选55.5个周期或239.5个周期。
实际配置时,我一般会留足余量。如果源阻抗在10k以下,选55.5周期就很稳;如果源阻抗在100k量级,直接选239.5周期。代价是转换速度变慢,但对于电压测量这种低速应用,完全不是问题。
提示:STM32F4系列的采样时间配置更灵活,但原理相同。不要盲目追求高采样率,先保证采样电容充到位。
3.3 信号源阻抗的降低方法
如果不想牺牲采样时间,就得降低源阻抗。最直接的办法是在分压电阻后面加一个电压跟随器,用运放做缓冲。运放的输出阻抗极低,通常小于1欧姆,采样电容瞬间就能充满。这样采样时间可以设得很短,转换速度也上去了。
选运放时要注意几点:输入偏置电流要小,否则会在高阻分压电阻上产生额外误差;输出要能轨到轨,否则接近地或电源轨的电压测不准;带宽不用太高,但压摆率要够,保证建立时间短。我常用的是低功耗的CMOS运放,偏置电流在pA级,对分压电阻几乎没影响。
如果不想加运放,也可以降低分压电阻的阻值。比如把100k换成10k,源阻抗降到5k,采样时间选55.5周期就够了。代价是分压电阻上消耗的电流大了10倍,从50uA变成500uA。对于电池供电的设备,这个代价可能不能接受。所以要在功耗、速度和精度之间做权衡。
4. 校准:把出厂误差和系统误差一起干掉
4.1 出厂校准值的读取与使用
STM32在出厂时会对ADC做一次校准,把校准值存在系统存储区的特定地址。对于F103系列,这个地址是0x1FFFF7BA,里面存的是在VDDA=3.3V、温度25度条件下,VREFINT对应的ADC码值。F4系列的地址不同,具体要查对应型号的参考手册。
读取这个值很简单,用指针访问即可。但要注意,这个值是在特定条件下测的,实际使用时VDDA和温度可能不同,所以不能直接拿来当绝对参考,而是用来做相对补偿。具体做法是:在当前条件下读VREFINT通道,得到码值A;出厂校准值是B;那么当前VREF+的实际电压约等于3.3V乘以B除以A。这个结果再用来换算其他通道的电压。
我实测过十几颗F103芯片,用这个方法算出的VREF+,和用高精度万用表测VDDA的结果,误差在±10mV以内。对于大多数应用,这个精度已经足够了。
4.2 两点校准法消除增益和偏移误差
出厂校准只能补偿参考电压的偏差,不能消除ADC本身的增益误差和偏移误差。要进一步提高精度,需要做两点校准。具体操作是:用高精度电压源给ADC输入两个已知电压,比如0.5V和2.5V,记录ADC码值,然后拟合出直线方程。
假设输入0.5V时码值是620,输入2.5V时码值是3100。那么增益等于(3100-620)/(2.5-0.5)=1240码值每伏。偏移等于620减去0.5乘以1240,等于0。这样任何码值X对应的电压就是(X-偏移)/增益。
实际芯片的偏移通常不为零,可能在几个码值到几十个码值之间。增益误差一般在±1%以内。两点校准后,测量精度可以做到±0.1%以内,前提是参考电压稳定、噪声控制得好。
注意:两点校准的精度依赖于校准源本身的精度。如果校准源只有1%精度,校准后也只能做到1%。所以校准源至少要比目标精度高一个数量级。
4.3 多点校准与查表法
如果对非线性要求很高,比如要做音频采样或精密传感器读取,两点校准可能不够。这时可以用多点校准,在每个感兴趣的电压点测一组数据,然后做分段线性插值或多项式拟合。STM32的Flash空间足够存几十个校准点,运行时查表加插值,计算量也不大。
我做过一个项目,测量范围0到3V,要求精度0.05%。用五点校准加三次多项式拟合,实测最大误差0.03%。校准点选在0.5V、1.0V、1.5V、2.0V、2.5V,覆盖整个范围。拟合系数存在Flash里,上电时读出来。这个方法比两点校准麻烦,但精度提升明显。
5. 硬件布局与滤波:把噪声挡在ADC外面
5.1 模拟输入端的RC滤波
即使参考电压很干净,信号源本身也可能带噪声。在ADC输入引脚前加一个RC低通滤波,是最简单有效的办法。电阻选1k到10k,电容选100nF到1uF。截止频率等于1/(2πRC)。比如1k和100nF,截止频率约1.6kHz,对于工频干扰和开关噪声都有很好的抑制。
但要注意,这个电阻会增加源阻抗。如果前面没有运放缓冲,电阻和分压电阻串联,源阻抗更大,采样时间要相应加长。所以RC滤波的位置很关键:如果信号源本身阻抗低,RC放在靠近ADC引脚处;如果信号源阻抗高,先用运放缓冲,再放RC。
电容要选C0G或X7R材质的陶瓷电容,容值稳定,温漂小。不要用Y5V,容值随电压和温度变化太大,滤波效果不稳定。
5.2 地平面与星型接地
混合信号PCB布局的核心原则是:模拟地和数字地分开,最后在一点连接。STM32的VSSA和VSS引脚,在芯片内部是分开的,外部也要分别接地,然后在靠近芯片的地方用0欧姆电阻或磁珠连接。
我见过很多板子把VSSA直接连到数字地平面,结果ADC读数上叠加了数字开关噪声。示波器看VSSA引脚,能看到明显的尖峰,和GPIO翻转同步。后来把VSSA单独走线到电源入口的地,噪声立刻小了一个数量级。
星型接地的意思是:所有模拟部分的地线都单独走,最后汇聚到一个参考点,通常是ADC芯片下方的模拟地平面。不要让数字电流流过模拟地线。如果板子有ADC专用地引脚,一定要利用起来。
5.3 软件滤波:均值、中值和滑动平均
硬件做得再好,总还有残余噪声。软件滤波是最后一道防线。最简单的是多次采样取平均。比如连续采16次,去掉最大值和最小值,剩下14次取平均。这样能有效抑制随机噪声,代价是转换时间变长。
对于工频干扰,可以用同步采样法。如果干扰频率是50Hz,采样周期设为20ms的整数倍,比如每20ms采一次,连续采N次取平均。这样工频干扰在平均过程中会被抵消。我试过在电机控制板上用这个方法,ADC读数从跳动±20个码值降到±2个码值。
滑动平均适合实时性要求高的场合。维护一个长度为N的队列,每次新采样进来,踢掉最老的,算平均。N取8或16,计算量小,效果也不错。但要注意,滑动平均会引入相位滞后,对于快速变化的信号不合适。
提示:不要盲目追求复杂的滤波算法。先看噪声来源,如果是周期性的,用同步采样;如果是随机的,用均值或中值;如果是突发的,用限幅滤波。对症下药比堆算法有效。
6. 实测对比:从默认配置到优化配置的完整过程
6.1 测试平台与基准设备
我用一块STM32F103C8T6最小系统板做测试,ADC输入接可调基准源,参考电压用外部3.0V基准。基准源精度0.02%,万用表用六位半台式表。测试点选0.5V、1.0V、1.5V、2.0V、2.5V五个点,每个点采100次,记录均值和标准差。
默认配置:VDDA接3.3V,采样时间1.5周期,无滤波,无校准。优化配置:VREF+接3.0V基准,采样时间55.5周期,输入加1k+100nF RC滤波,软件16次均值,两点校准。
6.2 数据对比与误差分析
默认配置下,0.5V点均值0.482V,误差-3.6%;1.5V点均值1.442V,误差-3.9%;2.5V点均值2.398V,误差-4.1%。误差主要是负的,说明读数偏小,符合采样电容充不满的预期。标准差在15到20个码值之间,噪声很大。
优化配置下,0.5V点均值0.4995V,误差-0.1%;1.5V点均值1.4998V,误差-0.01%;2.5V点均值2.5002V,误差+0.01%。标准差降到2到3个码值。误差从百分之几降到万分之几,提升非常明显。
这个对比说明,精确测量不是靠某一个技巧,而是参考源、采样时间、滤波、校准四个环节共同作用的结果。任何一个环节拖后腿,整体精度就上不去。
6.3 温度漂移的实测观察
我把板子放进温箱,从25度升到60度,观察读数变化。默认配置下,1.5V点读数从1.442V漂到1.428V,漂移约14mV,对应约0.9%。优化配置下,从1.4998V漂到1.4975V,漂移约2.3mV,对应约0.15%。漂移主要来自参考电压的温漂和分压电阻的温漂。
如果应用环境温度变化大,建议选低温漂的基准源和电阻。基准源选5ppm每度的,电阻选25ppm每度的金属膜电阻。这样整体温漂可以控制在0.05%以内。普通厚膜电阻的温漂在100ppm每度以上,不适合精密测量。
7. 常见问题与排查思路
7.1 读数跳动大,但万用表很稳
先看参考电压。用示波器交流耦合测VREF+和VDDA,看纹波。如果纹波超过10mV,先解决电源。再看采样时间,如果源阻抗高,采样时间短,读数会跳。最后看软件滤波,有没有做均值或中值。
还有一个容易被忽略的点:ADC时钟太快。STM32的ADC时钟有上限,F103是14MHz,F4是36MHz。超频会导致转换错误,读数乱跳。检查RCC配置,确保ADC预分频器设置正确。
7.2 低电压测不准,高电压反而准
这是典型的偏移误差。ADC在接近地电位时,内部开关和运放的失调电压影响更大。做两点校准,把低电压点也纳入校准范围。如果还不行,检查输入引脚有没有漏电流,比如相邻GPIO配置成了输出且输出高电平,会通过寄生二极管向输入引脚灌电流。
7.3 不同通道之间互相干扰
这是多通道扫描模式的常见问题。采样一个通道后,采样电容上还残留上一个通道的电压,如果采样时间不够,就会影响下一个通道。解决办法是增加采样时间,或者在通道切换后丢弃第一次转换结果。STM32的ADC确实有这个特性,数据手册里也提到了。
我一般会在扫描序列里,每个通道连续转换两次,取第二次的结果。这样第一次转换相当于“预热”,把采样电容上的残留电荷清掉。代价是转换时间翻倍,但对于低速多通道测量,完全可以接受。
7.4 电池供电时精度下降
电池电压会随着电量下降而降低,如果VDDA直接接电池,参考电压也跟着降,测量结果就不准了。解决办法是用VREFINT补偿,或者用外部基准源。如果必须用电池直接供电,至少要用VREFINT算出实际VDDA,再换算测量结果。
另外,电池内阻会随着老化增大,ADC采样时的瞬间电流会在内阻上产生压降,导致参考电压波动。在VDDA引脚加一个大电容,比如100uF,可以缓解这个问题。
8. 几个让我印象深刻的踩坑经历
第一个坑:曾经用STM32F4做音频采样,采样率设了100kHz,结果读数全是乱的。查了半天,发现ADC时钟配置错了,实际时钟远低于预期,采样时间不够。后来用示波器测ADC时钟引脚,才确认问题。教训是:不要只看代码里的分频系数,要实测时钟频率。
第二个坑:一块板子ADC读数总是偏大,换了芯片就好了。后来对比两颗芯片的出厂校准值,发现坏的那颗校准值明显异常。所以批量生产时,最好在产线上做一次校准,把每颗芯片的校准值写进Flash,运行时读取。不要完全依赖出厂值。
第三个坑:用DMA搬运ADC数据,结果数据错位。原因是DMA传输完成中断里,没有正确清除ADC的EOC标志,导致下一次转换的数据覆盖了上一次。后来改成DMA循环模式,配合半传输和传输完成中断,问题解决。这个坑花了我整整一个下午,教训是:DMA和ADC的配合,时序要仔细看参考手册的流程图。
第四个坑:测量高阻信号时,读数一直偏小,加了运放缓冲就好了。但运放选型不对,输入偏置电流太大,在1M欧姆源阻抗上产生了毫伏级误差。后来换成CMOS输入运放,偏置电流在pA级,问题消失。选运放不能只看带宽和压摆率,偏置电流和失调电压同样重要。
这些经历让我明白,精确测量是一个系统工程,任何一个细节疏忽都会体现在最终读数上。不要迷信某个“神奇”的配置,而是要把整个信号链都理清楚,逐级排查,逐级优化。
9. 从测量到控制:把精确电压用起来
测准电压只是第一步,很多应用还需要根据电压做控制。比如电池管理,需要根据电压判断剩余电量;比如传感器读取,需要把电压换算成物理量。这时候,测量的精度直接决定了控制的精度。
如果做电池电量计,建议用查表法加开路电压法。先测开路电压,查表得到大概电量,再结合库仑计做修正。单纯靠电压判断电量,在电池放电平台期误差很大,因为平台期电压变化很小,ADC的微小误差会导致电量估计大幅波动。
如果做传感器读取,比如压力传感器输出0到100mV,需要先用仪表放大器放大到ADC量程,再做测量。放大器的增益误差和失调误差会直接叠加到测量结果上,所以放大器的选型比ADC本身更关键。我一般选零漂移运放,失调电压在微伏级,温漂在纳伏每度级,这样整个温度范围内都不需要重新校准。
对于需要隔离的场合,比如测量高压侧的电压,可以用隔离放大器或光耦加线性光耦。但隔离器件的线性度和温漂通常比非隔离方案差,需要仔细评估。如果精度要求高,建议用隔离ADC,把ADC放在高压侧,通过数字隔离器传数据。这样模拟信号不跨隔离带,精度损失最小。
10. 写在最后的一些个人体会
做了这么多年嵌入式测量,我最大的体会是:精度是设计出来的,不是校准出来的。校准只能修正系统误差,不能消除噪声和漂移。如果硬件设计有缺陷,再复杂的软件也救不回来。
另一个体会是:数据手册要反复看。很多问题的答案就在数据手册的脚注里,比如采样时间的最小值、参考电压的范围、温度漂移的典型值。我习惯把关键参数抄在笔记本上,设计时随时对照。
最后,不要追求不切实际的精度。12位ADC的理论分辨率是1/4096,约0.024%。但实际有效位数往往只有10位,对应0.1%。如果你的应用需要0.01%的精度,应该选16位或24位的外部ADC,而不是在12位ADC上死磕。选对器件比优化算法重要得多。
测量电压这件事,入门很容易,做好很难。希望这篇内容能帮你少走一些弯路,把STM32的ADC真正用出它应有的水平。