1. 从一次采样翻车说起:ADC转换到底在做什么
前阵子帮一个朋友看他做的小采集板,现象很典型:用万用表量电池电压是4.18V,板子上的ADC读出来却在4.05V到4.31V之间来回跳,偶尔还蹦出个3.9V。他第一反应是"芯片坏了",换了三片ADC,现象一模一样。后来把原始数据抓出来一看,问题根本不在芯片,而在于他对采样原理、取平均和精度这几个环节的理解全是模糊的。
这个场景我见过太多次了。ADC转换这件事,入门门槛看着很低——接个基准、配个通道、读个寄存器就出数了。但真正要把数读准、读稳、读得有意义,中间隔着一整套工程细节:采样保持电路怎么工作、采样时间怎么算、为什么要过采样、平均到底该怎么取、精度和分辨率是不是一回事。这些问题不搞清楚,你拿到的永远是一堆"看起来能用、实际上不敢信"的数字。
这篇内容就是把这套东西从头到尾捋一遍。我会从采样原理讲起,把采样定理、采样保持、采样时间这些容易被跳过的环节说透;然后重点讲取平均这件事——很多人以为平均就是"多读几次除以次数",其实里面门道不少,取错了反而引入新误差;最后落到精度这个最容易被误解的概念上,把分辨率、精度、准确度、有效位数这几个词彻底掰开。适合正在做数据采集、传感器读取、电源监控、音频采样这类工作的朋友,也适合刚接触ADC、被一堆参数表搞晕的新手。看完你至少能做到两件事:知道自己的采样值为什么不准,以及知道该怎么改。
2. 采样原理:为什么你的ADC读数总在跳
2.1 采样定理不是背出来的,是算出来的
奈奎斯特采样定理大家都背过:采样频率要大于信号最高频率的两倍。但实际工程里,这句话只解决了一半问题。它保证的是"信号能被无失真重建",前提是你后面还有理想重建滤波器。而在真实系统里,我们更关心的是"我这次采样能不能代表这一刻的真实值"。
举个具体例子。假设你在采一个50Hz的工频信号,按定理最低要100Hz采样。但如果你真的用100Hz去采,采到的点会落在波形的不同相位上,相邻两次采样值可能差很多,看起来就像在剧烈跳动。这时候你读到的不是噪声,而是信号本身在被欠采样。所以工程上通常会把采样率拉到信号频率的10倍以上,甚至更高,目的不是满足定理,而是让单次采样值本身就足够接近瞬时真值。
还有一个常被忽略的点:采样定理针对的是带宽,不是频率。一个方波看起来基频很低,但它的谐波成分能延伸到很高。你用低采样率去采方波,采出来的形状会完全失真。这就是为什么采开关信号、PWM信号时,采样率要按边沿速度来定,而不是按重复频率来定。
2.2 采样保持电路:那个被忽略的"快门"
绝大多数逐次逼近型ADC(SAR ADC)内部都有一个采样保持电路,结构上就是一个开关加一个电容。工作过程分两个阶段:采样阶段开关闭合,输入信号给电容充电;保持阶段开关断开,电容上的电压被送去量化。
这个电容就是整个ADC精度的命门之一。它要充到和输入电压一致,需要时间,这个时间由输入源阻抗和采样电容共同决定。公式很朴素:
充电时间常数 τ = R_source × C_sample
一般要求采样时间至少是5到10个τ,才能充到接近满值。假设你的信号源内阻是10kΩ,ADC采样电容是10pF,那τ就是100ns,采样时间至少要500ns到1μs。如果你在代码里把采样时间配得太短,电容还没充够就进入保持阶段,读出来的值必然偏低——而且这种偏低是有规律的,信号源内阻越大偏得越多。
这就是为什么很多人在ADC输入端串了一个大电阻做分压,结果读数一直偏小。不是ADC坏了,是采样时间不够。解决办法要么加大采样时间,要么在分压电阻后面加一个电压跟随器做缓冲,把源阻抗降下来。
2.3 采样时间怎么配:一个可复现的计算过程
我拿一个常见的场景走一遍。假设你要采一个电池电压,用两个电阻分压,上电阻100kΩ,下电阻100kΩ,等效源阻抗就是两个电阻并联,50kΩ。ADC采样电容按10pF算。
τ = 50000 × 10e-12 = 500ns
要充到12位精度(约0.024%误差),需要的时间是 τ × ln(1/0.00024) ≈ τ × 8.3 ≈ 4.15μs。
也就是说,采样时间至少要配到4μs以上,留点余量配6到8μs比较稳。如果你用的是STM32这类MCU,采样时间是以ADC时钟周期为单位的,假设ADC时钟是14MHz,一个周期约71ns,那6μs大约对应84个周期,选最接近的档位(比如112周期或144周期)就行。
这里有个实操心得:与其算,不如实测。把采样时间从最小档开始往上调,每调一档读一批数据看均值,当均值不再随采样时间增加而变化时,说明电容已经充够了,当前档位就是安全值。这个方法比纯算更靠谱,因为它把PCB走线电容、焊盘电容这些算不进去的因素也一起覆盖了。
2.4 参考电压:精度的天花板
ADC转换的本质是拿输入电压和参考电压做比值,输出一个比例码。所以参考电压的稳定性直接决定了整个系统的精度上限。你用一个纹波50mV的参考源,后面再怎么平均、再怎么滤波,绝对精度都不可能好过这个纹波。
常见的参考源有三类:电源直接做参考、内部基准、外部专用基准。电源做参考最省事但最差,因为电源本身带负载波动和纹波;内部基准一般精度在1%到2%量级,够用但不精;外部专用基准能做到0.1%甚至更高,代价是成本和板面空间。
这里有个容易踩的坑:很多人以为参考电压只要"准"就行,其实"稳"比"准"更重要。一个标称2.500V但实际2.480V的基准,只要它稳定,你完全可以在软件里做一次校准把偏差补掉。但如果它随温度、随负载漂,那才是真的没救。所以选基准时先看温漂和长期稳定性,再看初始精度。
3. 取平均:不是读几次除以几那么简单
3.1 平均能解决什么,不能解决什么
平均的本质是利用噪声的随机性。如果每次采样的误差是随机的、均值为零的,那么N次平均后,噪声的标准差会降到原来的1/√N。注意这个1/√N,它意味着你想把噪声降一半,要采4次;降一个数量级,要采100次。收益是递减的,这点心里要有数。
但平均有个前提:误差必须是随机的。如果误差是系统性的——比如前面说的采样时间不够导致的固定偏低,或者参考电压偏了——那平均一点用都没有,你平均一万次,得到的还是一个偏的值。这是新手最容易犯的错:读数不稳就狂加平均次数,结果噪声是小了,但值还是错的。
所以正确的顺序是:先排查系统性误差(采样时间、参考、增益、偏置),确认没有固定偏差之后,再用平均去压随机噪声。
3.2 几种平均方式的取舍
平均不是只有一种做法,不同场景要用不同策略。
算术平均是最基础的,N次求和除以N。适合噪声近似高斯分布、没有突发干扰的场景。缺点是它对异常值没有抵抗力,一个野值就能把结果拉偏。
中值平均是先去极值再平均,比如采10次去掉最大最小各1个,剩下8个求平均。它对突发干扰(比如开关动作引起的尖峰)有很好的抑制作用。代价是要排序,计算量比算术平均大,而且采的次数要够多才有意义。
滑动平均是维护一个长度为N的窗口,每来一个新值就挤掉最老的值。它的好处是输出连续、延迟固定,适合实时显示。缺点是窗口长度决定了响应速度,窗口越长越平滑但越迟钝。
中值滤波加滑动平均是我在工业采集里用得最多的组合:先用中值滤掉尖峰,再滑动平均压随机噪声。两级处理下来,数据既稳又不会太迟钝。
下面这张表是我总结的选型参考:
| 平均方式 | 适用场景 | 抗突发干扰 | 计算开销 | 响应延迟 |
|---|---|---|---|---|
| 算术平均 | 噪声随机、无尖峰 | 弱 | 低 | 中 |
| 中值平均 | 有偶发尖峰 | 强 | 中 | 中 |
| 滑动平均 | 实时显示、连续输出 | 弱 | 低 | 固定 |
| 中值+滑动 | 工业采集、要求高 | 强 | 中高 | 固定 |
3.3 平均次数的选择:2的幂不是迷信
很多人习惯用2的幂做平均次数,比如8、16、32、64。这不是迷信,是有实际好处的。在定点运算里,除以2的幂可以直接用右移实现,省掉除法指令;在硬件里,累加器位宽也更好设计。如果你用的是没有硬件除法器的低端MCU,这个习惯能省不少时间。
但次数不是越多越好。前面说了噪声按1/√N下降,采到一定程度后,噪声已经低于ADC本身的量化噪声了,再平均就是浪费。一般来说,平均后的噪声目标定在1个LSB以内就够了。假设你的ADC噪声是3个LSB(RMS),那采9次平均后约1个LSB,采16次就绰绰有余了。
还有一个隐藏问题:平均次数太多会拉长采样周期。如果你要监测的是快速变化的信号,平均窗口太长会把真实的变化也一起抹平。这时候要在平滑度和响应速度之间做权衡,没有标准答案,看你的应用能容忍多大延迟。
3.4 过采样与抽取:用速度换精度的正规做法
前面说的平均是"采N次求平均",而过采样加抽取是它的升级版,有严格的理论支撑。原理是:如果往输入信号里叠加一点白噪声(或者信号本身噪声就够),然后以远高于奈奎斯特的频率采样,再累加抽取,每4倍过采样能换来1位有效分辨率。
具体来说,采4^n次累加再右移n位,理论上能提升n位分辨率。比如12位ADC过采样256次(4^4),理论上能到16位。但这里有几个硬条件:第一,输入信号要有足够的噪声做抖动,否则量化误差是相关的,过采样不涨精度;第二,ADC本身的线性度要够,如果ADC的积分非线性只有10位水平,你过采样到16位也是假的;第三,累加器不能溢出,位宽要留够。
我在实际项目里用过这个方法,12位ADC过采样64次,有效位数大概能到14位多一点,再往上就上不去了,因为ADC的线性度到顶了。所以过采样不是无限提精度的魔法,它受限于ADC的物理特性。
4. 精度:分辨率、准确度、有效位数别再混着用
4.1 分辨率不等于精度
这是最经典的一个误解。一个12位ADC,参考电压3.3V,它的分辨率是3.3V/4096 ≈ 0.8mV。但这只是说它能分辨0.8mV的步进,不代表它测出来的值误差只有0.8mV。
分辨率是"尺子的最小刻度",精度是"你量出来的值离真值有多远"。一把尺子刻度再细,如果尺子本身是弯的,量出来照样不准。ADC也一样,12位分辨率配上糟糕的参考和糟糕的布局,实际精度可能只有8位水平。
4.2 有效位数(ENOB):真正该看的指标
有效位数是把ADC的实际噪声和失真折算回去,看它等效于多少位的理想ADC。计算公式大致是:
ENOB = (SNR - 1.76) / 6.02
比如一个ADC实测SNR是68dB,那ENOB = (68 - 1.76)/6.02 ≈ 11位。也就是说,虽然它标称12位,但实际有效精度只有11位,最低那一位基本是噪声。
ENOB比标称位数更能反映真实水平。选ADC或者评估自己的采集系统时,与其看数据手册上的"12位",不如实测一下SNR,算算ENOB。我一般会在实际工作频率和实际参考电压下测,因为ENOB是随频率和参考变化的,手册上的典型值往往是最理想条件下的。
4.3 误差来源清单与量级
把影响精度的因素列全,心里才有底。下面这张表是我按经验整理的量级参考:
| 误差来源 | 典型量级 | 是否可校准 |
|---|---|---|
| 参考电压初始偏差 | 0.1%~2% | 可校准 |
| 参考电压温漂 | 10~100ppm/℃ | 部分可补偿 |
| 增益误差 | 0.1%~1% | 可校准 |
| 偏置误差 | 1~10 LSB | 可校准 |
| 积分非线性 | 1~4 LSB | 难校准 |
| 微分非线性 | 0.5~2 LSB | 难校准 |
| 量化噪声 | 0.29 LSB RMS | 不可消除 |
| 热噪声 | 随带宽变化 | 不可消除 |
| 采样时间不足 | 可达几十LSB | 可通过配置消除 |
看这张表能得出一个结论:可校准的误差(参考、增益、偏置)先校准掉,不可校准的(非线性、量化、热噪声)只能靠选好器件和过采样去压。很多人一上来就纠结非线性,其实先把参考和增益校准了,精度就能提升一大截。
4.4 校准实操:两点校准法
最实用的校准方法是两点校准。准备两个已知的准确电压,一个靠近量程下限,一个靠近上限,分别读ADC值,然后拟合出增益和偏置。
假设下限电压V1对应读数D1,上限电压V2对应读数D2,那么:
增益 k = (V2 - V1) / (D2 - D1) 偏置 b = V1 - k × D1
之后每次读数D,真实电压就是 k × D + b。
这个方法能同时消掉增益误差和偏置误差,实现简单,效果立竿见影。注意两点要选在量程的10%和90%附近,不要贴着两端,因为ADC在接近满量程和接近零的地方非线性往往更明显。
校准用的已知电压从哪来?最省事的是用一个高精度基准分压出来,或者用一台校准过的台式电源。校准一次之后把k和b存到非易失存储里,上电加载就行。温度变化大的场合,可以在不同温度点各校准一次,做成分段补偿。
5. 实操全流程:从裸数据到可信读数
5.1 硬件侧要确认的几件事
在写代码之前,先把硬件确认清楚,能省掉后面一大半的调试时间。
第一,确认参考电压的来源和实际值。用高精度万用表量一下参考引脚的实际电压,别信标称值。这个实测值要写进代码里参与换算。
第二,确认输入源阻抗。如果分压电阻太大,要么加缓冲,要么加大采样时间。我一般会把源阻抗控制在10kΩ以内,超过就加运放跟随。
第三,确认ADC输入引脚附近有去耦电容,一般0.1μF加1μF组合,靠近引脚放。这个电容能吸收采样瞬间的电荷冲击,对稳定性帮助很大。
第四,确认模拟地和数字地的处理。如果板子上有大的数字电流,模拟部分要单独走线、单点接地,否则数字噪声会串进采样值。
5.2 软件侧的采样配置模板
下面是一段典型的采样配置思路,用伪代码表示,具体寄存器名按你的平台替换:
// 1. 配置ADC时钟,确保在规格范围内 adc_set_clock(ADC_CLK_14MHZ); // 2. 配置采样时间,按前面算的留足余量 adc_set_sample_time(CHANNEL, SAMPLE_TIME_112CYCLES); // 3. 配置参考电压源 adc_set_reference(REF_EXTERNAL); // 4. 单次采样函数 uint16_t adc_read_once(uint8_t ch) { adc_start_conversion(ch); while(!adc_is_done()); return adc_get_result(); } // 5. 带中值滤波的多次采样 uint16_t adc_read_filtered(uint8_t ch, uint8_t n) { uint16_t buf[MAX_N]; for(uint8_t i = 0; i < n; i++) { buf[i] = adc_read_once(ch); } // 简单冒泡排序,n一般不超过16 sort(buf, n); // 去掉最大最小各一个,中间求平均 uint32_t sum = 0; for(uint8_t i = 1; i < n - 1; i++) { sum += buf[i]; } return sum / (n - 2); }这段代码里,采样时间配置和滤波策略是两个关键点。采样时间按前面算的来,滤波次数n一般取8到16,太少压不住噪声,太多拖慢响应。
5.3 从码值到电压:换算与校准的落地
拿到滤波后的码值,换算成电压:
V = code × Vref / (2^bits - 1)
比如12位、Vref实测3.296V、code是2048,那V = 2048 × 3.296 / 4095 ≈ 1.648V。
然后套用两点校准的k和b:
V_calibrated = k × V + b
注意校准要在换算之后做,因为k和b是针对电压值拟合的。如果你在码值域做校准也行,但要把Vref一起考虑进去,容易乱,不如统一在电压域做。
5.4 一次完整的调试记录
我把前面那个朋友的采集板问题完整走一遍,你能看到排查思路。
第一步,抓原始数据。连续采1000次,不做任何滤波,打印出来。发现数据在4.05到4.31之间分布,均值4.18,和万用表一致。说明没有系统性偏差,问题是不稳。
第二步,看分布形状。画了个直方图,近似正态,标准差约0.04V。这个噪声水平对12位、3.3V参考来说大约是50个LSB,偏大。
第三步,查采样时间。发现配置的是最小档,算下来只有0.2μs,而源阻抗50kΩ需要至少4μs。改成112周期后,标准差降到0.008V,约10个LSB。
第四步,加滤波。中值加平均16次后,标准差降到0.002V以内,约2到3个LSB,已经满足他的需求了。
第五步,做两点校准。用台式电源给两个标准电压,拟合出k和b,绝对误差从原来的30mV降到5mV以内。
整个过程没有换任何硬件,全是配置和软件的事。这就是为什么我说,ADC读数不准,先别怀疑芯片。
6. 常见问题与排查速查
6.1 读数一直偏大或偏小
先查参考电压实际值,再查采样时间够不够,最后查增益和偏置是否需要校准。这三步能解决九成的固定偏差问题。如果三步都做了还偏,检查输入分压电阻的精度和温漂,以及ADC输入是否有漏电流。
6.2 读数跳动大
先看跳动是随机的还是有规律的。随机跳动多半是噪声,加滤波和去耦电容;有规律的跳动(比如跟着某个周期走)多半是干扰耦合,查地线布局和信号走线。还有一个常见原因是参考电压不稳,用示波器看参考引脚有没有纹波。
6.3 平均之后反而更差
这种情况一般是平均窗口里混进了异常值,或者信号本身在变化被平均抹平了。改用中值滤波,或者缩短平均窗口。还有一种可能是平均次数太多导致累加器溢出,检查一下变量类型。
6.4 不同通道读数互相影响
这是多通道ADC的经典问题。原因是通道切换后采样电容还残留着上一个通道的电荷。解决办法是增加采样时间,或者在切换通道后先采一次丢弃,再采一次作为有效值。前者治本,后者治标,我一般两个一起用。
6.5 温度变化后精度变差
查参考电压的温漂和分压电阻的温漂。如果这两者都还好,那可能是ADC本身的偏置温漂。做分段温度校准,或者换低温漂的器件。
| 现象 | 首要排查 | 次要排查 | 快速验证 |
|---|---|---|---|
| 固定偏大偏小 | 参考电压 | 采样时间 | 量参考引脚 |
| 随机跳动 | 去耦电容 | 滤波策略 | 加电容看变化 |
| 平均后更差 | 异常值 | 窗口长度 | 换中值滤波 |
| 通道串扰 | 采样时间 | 丢弃首次 | 加长采样时间 |
| 温漂明显 | 参考温漂 | 电阻温漂 | 加热对比 |
6.6 几个我踩过的坑
第一个坑:以为参考电压标称值就是实际值。有次用内部基准,标称1.2V,实际量出来1.18V,差了1.7%,换算出来的电压全偏。后来养成习惯,每块板子都实测参考。
第二个坑:采样时间按手册最小值配。手册的最小值是理想条件下的,实际板子有走线电容、有源阻抗,必须留余量。现在我至少配到算出来值的1.5倍。
第三个坑:平均次数用2的幂但忘了累加器位宽。12位采256次,累加和最大是4095×256,需要20位,用16位变量直接溢出,数据全乱。这个坑很隐蔽,因为溢出后的数据看起来还挺"正常"。
第四个坑:校准只做了一点。只测一个电压点拟合不出增益和偏置,必须两点。而且两点要拉开距离,靠太近拟合出来的斜率不准。
7. 写在最后的一点个人体会
ADC这块东西,越做越觉得"配置"只是表面,"理解"才是根本。采样时间、参考、平均、校准,每一个环节背后都有明确的物理原因,搞懂了原因,遇到新问题就能自己推。我见过太多人拿着别人的配置代码抄来抄去,换个板子就翻车,就是因为不知道那些参数为什么是那个值。
如果只让我给一条建议,那就是:先把原始数据抓出来看。不要一上来就滤波、就平均、就怀疑芯片。原始数据的分布、跳动的规律、和已知真值的偏差,这些信息里藏着所有答案。看懂了原始数据,后面每一步该怎么做就都清楚了。
另外,精度这件事要有合理的预期。12位ADC想要12位有效精度,需要参考、布局、校准全部到位,成本不低。很多应用其实8到10位有效精度就够了,没必要为了最后那两位折腾。先想清楚你的应用到底需要多少精度,再决定投入多少精力,这个判断比任何技术细节都重要。