☰
ADC电压采集校准实战:从误差分析到标定拟合的完整方案
2026/10/2 5:17:09 网站建设 项目流程

ADC这活儿,说不难是真不难,ADC外设配置好,读取寄存器就是个电压值。但做硬件时间长了你会发现,真正让ADC从“能出数”到“数得准”,中间隔着一条叫“标定”的河。尤其是做电池电压监测、传感器采集、精密仪器这类场景,ADC的原始转换结果直接拿来用,十有八九会出问题。这期“硬件基础”系列就集中聊清楚一件事:ADC电压采集的软件校准,也就是标定和拟合具体怎么做,为什么非做不可,以及怎么做才能不过拟合、不白折腾。

1. 先搞清楚ADC的误差从哪里来

很多刚接触ADC的朋友容易有一个错觉,觉得ADC是12位、16位,分辨率够高,读出来的数字就一定能精确反映真实电压。实际上ADC转换结果和真实物理量之间,永远存在系统性偏差。不把这个偏差模型想明白,软件校准就是无源之水。

1.1 增益误差与失调误差两个大坑

ADC的误差模型,绝大多数情况下可以简化为一条直线:

真实电压 = 增益系数 × ADC读数 + 失调量

这就是标定和拟合的理论基础。其中增益系数对应的是“增益误差”,失调量对应的是“失调误差”。

增益误差,通俗点说就是ADC把1V看成0.98V、把2V看成2.03V,电压越高偏差越大。它主要来自ADC内部参考电压的不准确,也可以来自外部采样电路里分压电阻的容差。

失调误差则是ADC在输入电压为0的时候,转换结果并不是0,而是带了一个固定偏置。很多芯片内部的放大器存在输入失调电压,十几毫伏到几十毫伏都很常见。这个偏置是固定的,电压越高它也在,电压为零时它也在。

所以做软件校准的核心任务就两个:把这个直线的斜率纠正过来,把这个直线的截距挪回原位。这两个系数确定了,大部分误差就消化掉了。

1.2 非线性、温漂等隐藏误差源

增益和失调是最主要的误差来源,但工程实践中还有几个隐藏的“敌人”需要心里有数。

非线性误差是比较难缠的问题。ADC的转换曲线并不是绝对直线,尤其接近满量程和接近零点的两端,线性度通常较差。入门级的MCU内置ADC,INL(积分非线性)做到正负几个LSB已经算不错了。如果追求高精度,单纯做两点校准就不够,需要做多点分段甚至多项式拟合。

温漂是最容易被忽略的。放大器的失调电压会随温度漂移,参考电压源也有温度系数。这意味着在25摄氏度下校准好的系数,到60摄氏度的工作环境中可能又偏了。对于宽温工作场景,校准系数需要按温度点分组存储,或者用包含温度项的补偿模型。

还有电源噪声和参考电压纹波。ADC的参考电压如果有纹波,相当于测量基准本身在抖动,这部分误差不管怎么做软件校准都无法消除。所以硬件上给VREF加足够的去耦电容,甚至用专门的基准源芯片,是软件校准能奏效的前提。

1.3 为什么不能只靠「更高级的ADC」糊弄过去

不少人有这种想法:既然内置ADC精度不行,那我换一颗16位甚至24位的外部ADC不就行了吗?这个思路在某些场景确实有用,但问题在于成本、面积、功耗和开发复杂度的增加。

况且外部ADC也有自己的增益误差、失调误差和温漂。哪怕用了24位的Σ-Δ ADC芯片,数据手册里的初始精度通常也只有“典型值”,真正保证精度还是得靠系统级校准。反倒是把一片普通的12位MCU内置ADC用好、校准好,在很多场景下完全够用,能省不少成本。

我在实际项目中见过不少板子,MCU几乎只用了内置ADC,通过合理的分压电路加软件两点校准,把电压测量精度做到了0.1%以内。这已经是相当实用的精度水平了。

2. 常见校准方案分析与选择

软件校准根据标定点的数量和拟合方式,通常可以分成三个层次:单点校准、两点校准、多点拟合。每一层都有它的适用场景,也有它明显的局限。

2.1 单点校准:怎么用,又在什么时候翻车

单点校准的逻辑很简单:认为ADC的失调为零,只校准增益。做法是给ADC输入一个已知电压,测出ADC读数值,然后用已知电压除以读数,得到校准系数。

在某些环境里这种做法是能用的,比如被测电压本身比较稳定、工作在固定电压附近,而且ADC的失调误差可以忽略。但“失调误差可以忽略”这个前提,在实际芯片上经常不成立。只要失调不为零,单点校准就会在偏离校准点的地方产生明显的偏差。

我举个例子。假设某芯片的ADC在输入为0V时读数为50个LSB,在校准点1.2V处读数为2405个LSB。单点校准计算出的增益系数是1.2V除以2405约等于0.000499V/LSB。但真实情况下,输入为0.6V时ADC读数大约是1205 LSB,按这个系数换算出来的电压是0.601V,看着还行。可如果把输入电压拉到接近满量程,比如3.0V,误差就可能被放大到接近失调电压的满量程比例倍数,这部分误差直接没法接受。

结论是:单点校准只能在精度要求不高、测量范围窄、失调可接受时使用。在通用电压采集场景里,我建议至少做到两点校准。

2.2 两点校准:性价比最高的主流方案

两点校准的做法是:选两个已知电压点,分别记录ADC读数,然后用这两个点确定一条直线,反推增益和失调。

具体计算很简单。设两个标定点电压为V1和V2,对应的ADC读数为D1和D2,则:

增益系数 G = (V2 - V1) / (D2 - D1)

失调系数 B = V1 - G × D1

后续任何ADC读数D,都可以用校准后的电压值 V = G × D + B 来还原。

两点校准能够同时消除增益误差和失调误差,对标定点的要求也不高,只要标准电压源精度足够,几分钟就能完成校准。实际工程中我会选一个接近测量范围低端的点和一个接近高端的点,尽量覆盖实际工作区间。

2.3 多点拟合与分段查表:高精度场景的进阶手段

当ADC的非线性误差不可忽略时,两点校准校正完剩下的残差可能还在几个LSB甚至十几个LSB的量级。这时候就需要多点拟合了。

多点拟合最朴实耐用的是分段线性法。把测量范围分成N段,每段分别做线性校准,段与段之间形成一张查表。这种方式不需要高阶数学,每一段内部的线性度足够好,误差就能被压制得很低,实现起来也简单。

另外一种是多项式拟合,比如最小二乘法拟合一条二次或三次曲线。好处是模型平滑、没有分段跳变,缺点是系数计算复杂度高,而且如果选取的拟合阶次过高,很容易出现过拟合问题,在校准点之间的区域内反而产生更大的误差。这点后面单独展开讲。

3. 实操:标定过程、代码实现与参数验证

理论讲清楚了,接下来的重点就是如何在真实硬件上跑通全套标定流程,得到可用的校准系数,并写进固件里。

3.1 标定前的硬件准备与检查

做软件标定前,先花五分钟确认硬件状态,否则后面校出来的系数也没有参考价值。

先用万用表实际测量ADC引脚的电压,而不是完全相信信号源的显示值。信号发生器的显示值未必准确,专业一点的还会先拿六位半万用表去读基准电压。

确认参考电压稳定。VREF脚上的退耦电容必须靠近引脚摆放,如果有条件用LDO单独供电,比直接接在MCU的电源上要干净得多。参考电压的纹波直接影响ADC读数的跳动,软件滤波可以降低噪声但无法修正系统性偏差。

接下来开始采集数据。以我现在最常用的STM32F103内置ADC为例,标准流程是:

  • 配置ADC为单通道、软件触发模式,12位分辨率
  • 外部输入一个已知电压(比如用高精度稳压源输出0.5V)
  • 连续采样64次,取平均,作为该电压点对应的ADC读数
  • 依次采集若干个标定点,记录成一张表

采集中需要注意的一点是,每次切换输入电压后,要等电压稳定1到2秒再开始采样。尤其前端有RC滤波网络时,电容充放电需要时间,一上来就读数往往偏低或偏高。

3.2 最小二乘法线性拟合的代码实现

两点校准只需要一个初中生都会的直线方程,但多点拟合求最佳直线,就要用到最小二乘法了。核心思想是:找到一条直线,使得所有标定点到这条直线的误差平方和最小。它能把测量中随机噪声的影响均摊掉,避免两点校准被某一个噪声点带偏。

假设有N个标定点,每个点记为(D_i, V_i),最小二乘法给出的增益系数和失调系数计算公式为:

斜率 G = (N × Σ(D_i×V_i) - ΣD_i × ΣV_i) / (N × ΣD_i^2 - (ΣD_i)^2)

截距 B = (ΣV_i - G × ΣD_i) / N

这个公式在代码里实现起来非常直接。这里给一个在C环境里求最小二乘线性拟合系数的函数:

#include <stdint.h> typedef struct { float slope; // 增益系数 G float offset; // 失调系数 B } calib_coef_t; // data[] 为ADC读数,voltage[]为对应的真实电压,count为标定点数量 calib_coef_t linear_fit(const uint32_t *data, const float *voltage, uint8_t count) { float sum_d = 0.0f, sum_v = 0.0f; float sum_dv = 0.0f, sum_d2 = 0.0f; for (uint8_t i = 0; i < count; i++) { float d = (float)data[i]; float v = voltage[i]; sum_d += d; sum_v += v; sum_dv += d * v; sum_d2 += d * d; } calib_coef_t coef; float denominator = count * sum_d2 - sum_d * sum_d; if (denominator == 0.0f) { coef.slope = 0.0f; coef.offset = 0.0f; return coef; } coef.slope = (count * sum_dv - sum_d * sum_v) / denominator; coef.offset = (sum_v - coef.slope * sum_d) / count; return coef; }

校准系数求出来之后,MCU运行时只需要做一次乘法和一次加法。考虑到嵌入式平台上浮点运算的开销,有些场景会用定点数把G和B放大成整数运算,但对现代Cortex-M系列MCU来说,浮点乘法也就是一条指令的事,直接用float没有性能压力。

3.3 多项式拟合与过拟合的边界控制

线性拟合能解决大部分问题,但碰到ADC线性度不够好、尤其是高精度应用的时候,就需要考虑二次或三次多项式拟合了。一个二次多项式模型长这样:

V = a × D^2 + b × D + c

其中a、b、c三个系数的求解,一般用最小二乘的矩阵形式完成,也就是解一个三元一次方程组。很多PC端的工具(Excel、MATLAB、Python的numpy)都能直接算出系数,但在单片机上直接实现矩阵求逆并不划算。比较务实的做法是:用PC算出系数,把浮点系数作为常量存进固件,运行时直接代入计算。

这里我必须提醒一句:过拟合是多项式拟合的大坑。

所谓过拟合,简单理解就是拟合曲线完美地穿过了所有标定点,但标定点之间的行为完全不可控。高阶多项式在端点附近经常出现剧烈震荡,这个现象叫龙格现象。比如你用9次多项式去拟合10个点,曲线在首尾两端可能会突然往上翘或往下坠,校出来的结果反而更差。

所以在工程实际中,我的经验是:

  • 标定点个数在5到10个之间时,最高用三次多项式已经足够
  • 如果标定点接近20个,优先考虑分段线性拟合,而不是强行提高多项式阶次
  • 每增加一个拟合阶次,都要用预留的验证点去检查拟合效果的残差

3.4 实际项目中的校准数据记录

为了让大家对校准效果有个直观的概念,我整理了一份在某Cortex-M0+芯片上做3.3V系统电压采集的实测记录。前端是两颗1%精度电阻分压,分压比3:1,ADC参考电压接在MCU的VDD上。

标准电压 (V)ADC原始读数 (LSB)线性拟合后计算值 (V)绝对误差 (mV)
0.00050.0011
0.5006220.499-1
1.00012390.998-2
1.50018571.497-3
2.00024741.996-4
2.50030922.497-3
3.00037082.998-2

这里用的是两点校准的数据(0.5V和2.5V两点),其余点作为验证点。可以看到在校准点之间的误差控制在正负4mV以内,这个精度对于电池电压监测、电源状态检测这类应用已经绰绰有余。

4. 常见校准问题与排查技巧

这部分分享一下我在调试ADC校准功能时经常碰到的问题、排查思路和一些比较有用的经验。有些坑不踩一次,确实很难从书本上学会。

4.1 校准后仍有偏差的排查方向

问题一:校准完低压段准、高压段不准,或者反过来。这个现象通常是增益误差没有被完全消除。排查方向是检查分压电阻的精度和温漂、参考电压在不同负载下的稳定性。电阻的温漂系数如果是100ppm/℃,板子从常温升到60℃时,电阻值会漂移0.35%左右,在校准系数上就会留下明显痕迹。

问题二:校准完零点附近还是有一个固定偏置。如果以0V输入时ADC读数不是0,说明失调校准做得不够彻底。可能是校准源在校准零点时本身输出不为零,也可能是PCB漏电或者输入引脚附近的焊接残留影响了微弱信号。还有一个常见原因,是多路复用采样时相邻通道的电荷注入串扰,尤其扫描模式下切换通道后立即读取,读数会带上残余电荷。

问题三:校准完的数值跳动很大。这就是随机噪声问题了。软件侧先做均值滤波,连续采16次取平均,通常能把跳动压低不少。硬件侧检查VREF和电源引脚的去耦电容容量够不够,采样引脚上的RC滤波参数合不合理。有些MCU内置ADC支持硬件过采样,比如把4次转换累加再右移2位,也能等效提升分辨率。

问题四:换了一片芯片后校准系数就失效了。这完全正常,每个芯片的失调和增益都有差异,出厂校准就是用来吸收这个差异的。批量生产时,需要每片板子都跑一次标定流程,把系数写进EEPROM或Flash的独立区域。

4.2 标定过程中容易被忽略的三个细节

细节一:标定电压点的顺序会影响结果。建议从低到高依次施加电压,每次改变输入后等待稳定再进行采样记录。从高往低走时,如果前端有大电容,放电时间会明显长于充电时间,容易在数据表里产生虚假的高点。

细节二:标定点不要选得太靠近ADC的量程两端。ADC在接近满量程时线性度通常会略差一些,如果你把标定点放在满量程的2%和98%处,两点连线的斜率可能会被端点区域的非线性带偏。实际操作中把标定点放在量程的10%到90%区间内,得到的线性拟合系数在中段的表现更好。

细节三:校准系数必须带校验。校准系数在量产烧录时如果把Flash写坏了,或者EEPROM里读回来的是0xFF,运行时的电压就会变成乱七八糟的值。我在这一类系统里通常会在存储区尾部放一个CRC或者简单的和校验,初始化时校验失败就直接报错,不进入工作状态。

4.3 把标定做成量产自动化流程

如果产品需要批量生产,手工标定是很消耗时间的。比较好的做法是在产测阶段编写一个简单的自动化脚本,由PC上位机控制可编程电源输出预定电压,同时通过串口或SWD接口读取ADC读数,自动计算校准系数并写回芯片。

我经历过一个项目,手工标定一片板子大概需要3到5分钟,后来改成了自动化流程,产测时间压到了40秒以内,效率提升非常明显。自动化标定过程中还会顺带验证每个标定点的读数是否在合理范围内,如果某一片板子的ADC读数明显偏离预期,直接判定为硬件故障,节省后续排查成本。

4.4 校准需要多久做一次

固定安装的设备,如果环境温度和供电电压都比较稳定,出厂校准一次就够了。但对于便携设备或者存在明显发热的产品,建议在每次上电初始化时做一次零点校准,也就是在有条件的情况下测一次零输入,修正失调系数。零点校准只需要一个IO口控制MOS管或模拟开关,把ADC输入短接到地,成本很低,却能显著改善温漂带来的误差。

如果硬件上连一个可控的接地通路都没有,那就只能在固件里保留自动校准功能,定期在特定条件下获取已知电压值并更新系数,这样能让长期运行的设备始终保持不错的测量精度。

5. 软件滤波与校准配合使用的经验

标定解决了系统性误差,软件滤波解决的则是随机噪声。两者是互补关系,很多项目里把这两件事分开考虑是完全错误的。校准前如果不做任何滤波处理,采集到的标定点本身就带噪声,拟合出来的直线也会被带偏。

我常用的做法是:原始数据先经过滑动平均或中值滤波,再把滤波后的读数用于校准计算和日常转换。滑动平均窗长取16或32就够,太大会让响应变慢,系统的实时性会受影响。中值滤波对付尖峰脉冲干扰很有效,比如电机启停产生的电磁干扰噪声,窗长取5时能去掉最高和最低值,保留中间值。

ADC的采样周期也需要同步考虑。采样周期太短,采样电容没有充分充电,读数会偏低;采样周期太长,又容易混入低频噪声。对高阻源来说,把采样周期设置为最大值通常能有效减小误差,代价是转换速率下降。实际选择时根据信号源阻抗和所需采样率权衡即可。STM32的ADC采样周期可配置为1.5到239.5个ADC时钟周期,面对几十kΩ级别的信号源内阻,建议直接选55.5周期以上的档位。

另外,如果信号本身变化不快,比如电池电压、温度传感器输出,我会在应用层再做一次一阶低通滤波:

filtered_value = filtered_value * 0.9f + raw_value * 0.1f;

这个写法非常简单,系数0.9和0.1决定滤波强度,现场调试时很好调整。滤波后再套用校准公式即可。

6. 校准系数的存储与运行机制

校准系数算出来之后,怎么存、怎么加载、怎么在运行时生效,也是工程上必须考虑清楚的环节。

6.1 系数存储分区与备份机制

我习惯给校准系数单独划分一段Flash或EEPROM区域,和应用程序代码分开。这样升级固件时不会把校准数据覆盖掉。如果MCU的Flash支持按扇区擦除,选一个独立扇区用于存放校准参数。

存储结构建议包含:

  • 魔数(比如0xA5A5A5A5),用于识别该区域是否已写入有效校准数据
  • 校准系数G和B,以float或定点的形式保存
  • 校验值,可以是CRC16或者简单和校验
  • 校准日期、批次号、版本号这类附加信息

系统启动时先读取这段数据,做校验。校验通过就直接使用,校验失败则启用默认系数,同时点亮异常指示灯或者上报错误。这套机制能有效防止用户在不知情的情况下带着错误校准数据运行设备。

6.2 运行时如何快速套用校准公式

校准系数加载到内存之后,每次ADC转换完成,只需要一行代码就能得到校准后的电压值,不同场景下的代码逻辑基本一致:

float adc_get_calibrated_voltage(uint32_t raw_value) { return calib_coef.slope * raw_value + calib_coef.offset; }

如果使用定点数避免浮点开销,可以把系数放大到int32或Q15格式。本项目里MCU是带FPU的Cortex-M4,直接用float没有任何问题。对于没有FPU的Cortex-M0,定点化才有实际意义。

6.3 上位机辅助标定的优势

如果产品有上位机通信接口,强烈建议把标定做成上位机引导的半自动流程。上位机负责控制标准电压源、下发指令、接收ADC读数、计算系数、回写芯片。这套流程不仅适用于产线,也方便研发阶段反复调整标定策略。

我在研发阶段会把标定过程做成Python脚本,配合串口和可编程电源,一条命令就能自动跑完整个标定流程并直接画出拟合曲线。看到曲线就能直观判断线性度是否满足预期,还能快速比较不同拟合方案的残差大小,比纯手工记录效率高太多。

7. 几个容易忽视的配套问题

最后把几个容易被忽视但影响很大的配套问题集中梳理一下。

7.1 参考电压的选择

MCU内置ADC通常支持内部参考电压和外部参考电压两种模式。内部参考电压精度一般不高,温漂系数在50ppm/℃左右已经算不错的了。外部参考电压,比如TL431、REF3030这类基准芯片,能做到低温漂、低噪声。对测量精度要求高的项目,外部基准基本是刚需。

考虑成本时注意一个平衡点:软件校准可以吸收固定偏移和固定增益误差,但吸收不了参考电压的随机抖动和温漂带来的动态误差。校准其实救不了不稳定的基准源。

7.2 PCB布局的三个核心要点

ADC相关的PCB布局直接决定采样信号的干净程度。有三个要点值得记在笔记本上:

参考电压的退耦电容要尽量靠近芯片引脚,走线短粗直。任何串在VREF路径上的寄生电感,都会在采样瞬间造成电压跌落。

模拟采样线和数字信号线保持距离,尤其是PWM输出线、SPI时钟线、电机驱动线这类快速翻转的信号,对ADC输入的影响非常大。实在避不开时,在ADC输入引脚前加RC低通滤波,截止频率根据信号频率来定,功耗和响应速度之间做个取舍。

ADC输入引脚尽量不打过孔,少走外层,如果有铺地区域,上下左右包地处理。ADC输入是高阻输入,哪怕只有几皮法的寄生耦合,都可能把数字噪声带进采样结果。

7.3 ADC数据漂移的软件监测

写代码的时候,可以加一个简单的漂移监测:周期性给ADC输入一个已知的稳定电压(比如内部参考电压通道或者某个精密分压网络),对比当前读数和预期值的偏差。这个偏差超出阈值时,主动上报传感器异常或者自动触发重新校准流程。

这种方法本质上是一种软件自检机制,对长期无人值守的设备尤其有用。比如在野外工作的数据采集设备,温度变化剧烈,校准系数随时间缓慢漂移,如果没有自检机制,等发现问题时数据可能已经错了很长时间。

从我自己的项目经验来看,ADC校准并不是一个“调一次就一劳永逸”的工作。它和硬件设计、软件架构、产线流程都耦合在一起,需要系统性地考虑。摆正这个思路,后面再去做高精度采集项目,就能少走不少弯路了。

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

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

立即咨询