1. 从一次采样翻车说起:为什么ADC远没有想象中简单
刚入行那会儿,我接手过一个电池电压监测的小模块,方案很朴素:一颗12位ADC,参考电压3.3V,直接读分压后的电池电压,换算一下完事。结果样机一上电,读数在3.28V到3.35V之间来回跳,同一块电池、同一个负载,读数能差出70mV。当时我第一反应是"ADC坏了",换了一颗新的,还是跳。后来才明白,问题根本不在芯片,而在我对采样原理、取平均、精度这三件事的理解几乎为零。
这个标题"ADC转换汇总"看起来像是一篇资料整理,但真正做过项目的人都知道,它背后藏着的是嵌入式开发里最容易被低估的一块硬骨头。ADC(模数转换器)是把模拟世界的连续电压翻译成数字世界离散码值的桥梁,几乎所有涉及传感器、电池管理、音频采集、电机电流环的项目都绕不开它。但"能读出一个数"和"能读出一个可信的数"之间,隔着采样定理、参考源噪声、输入阻抗匹配、平均算法、量化误差、有效位数这一整套东西。
这篇内容我打算按一个真实项目推进的顺序来写:先讲清楚ADC到底在干什么、采样原理里哪些坑必须提前知道,再讲取平均这件事为什么不是"多读几次除一下"这么简单,最后落到精度这个最玄乎的话题上,把误差来源一项项拆开,给出可复现的配置和代码。适合刚接触ADC的嵌入式新手,也适合做过几个项目但读数一直"不太稳"的老手对照排查。全文的代码以常见的MCU片上ADC为例,思路对任何平台都通用。
2. ADC采样原理:先把"翻译规则"搞明白
2.1 采样、量化、编码:三步翻译的底层逻辑
ADC的工作可以拆成三步:采样、量化、编码。采样是在时间轴上把连续信号切成一个个瞬间值,量化是把每个瞬间值的电压幅度映射到有限个离散档位,编码是把档位号变成二进制数。很多人只关注最后那个二进制数,却忽略了前两步才是误差的真正来源。
采样这一步的核心约束是采样定理:要无失真地还原一个信号,采样率必须大于信号最高频率分量的两倍。比如你要采一个最高1kHz的音频信号,采样率至少得2kHz以上,工程上一般取5到10倍余量。如果采样率不够,高频成分会"折叠"到低频里,这就是混叠。混叠最坑的地方在于它不可逆——一旦混进来,后面无论怎么滤波、怎么平均都救不回来。我见过一个测振动的项目,传感器带宽到5kHz,采样率却只给了8kHz,结果频谱上全是假的低频峰,排查了两天才定位到是采样率的问题。
量化这一步决定了分辨率。一个N位ADC,把参考电压Vref均分成2^N份,每一份就是一个LSB(最低有效位),大小等于Vref/2^N。12位、3.3V参考,LSB就是3.3/4096≈0.806mV。也就是说,任何小于0.8mV的电压变化,这颗ADC在理想情况下都分辨不出来。这里有个常见误区:很多人以为"12位ADC精度就是3.3V/4096",其实那只是分辨率,不是精度。分辨率是尺子的最小刻度,精度是这把尺子准不准,两者完全不是一回事。
编码环节相对简单,但要注意输出格式:有的ADC输出直接二进制,有的输出补码,有的还带偏移。差分输入的ADC尤其容易在这里翻车,读出来的数看着正常,实际符号反了。配置寄存器时一定要对着手册确认输出编码方式。
2.2 采样保持电路与输入阻抗:被忽视的"充电时间"
SAR型ADC(逐次逼近型,MCU上最常见)内部有一个采样保持电容,采样阶段这个电容要通过外部信号源和内部开关电阻充电,充电到位后才能开始逐次比较。这个充电过程需要时间,就是采样时间(Sample Time)。
问题来了:如果你的信号源内阻很大,比如一个100kΩ的分压电阻网络,采样电容充电就会很慢。采样时间设得太短,电容还没充到真实电压就开始转换,读出来的值必然偏低。这就是为什么高阻信号源必须加长采样时间,或者前面加一级电压跟随器做阻抗缓冲。
我踩过的坑:一个NTC测温电路,分压电阻用了两个100kΩ,采样时间按默认的最短档配,读数比实际温度低了将近8℃。后来把采样时间从1.5个ADC时钟周期加到41.5个周期,读数立刻正常。这个参数在STM32里叫SamplingTime,在别的平台可能叫Acquisition Time,名字不同,本质一样。
提示:信号源内阻超过10kΩ时,务必查手册里的"最大外部阻抗"表格,或者干脆加运放缓冲。别凭感觉设采样时间。
2.3 参考电压:ADC的"标尺"必须比被测对象更稳
ADC的转换结果本质是一个比值:Code = Vin / Vref × 2^N。注意,Vref在分母上。这意味着参考电压的任何抖动都会直接乘到结果上。如果你的Vref是MCU的供电电压,而供电又带着开关电源的纹波,那ADC读数就会跟着纹波一起跳。
这就是为什么精密测量场合一定要用独立基准源,比如2.5V或4.096V的专用基准芯片。4.096V这个值很讨巧,因为4096×1mV=4.096V,12位ADC配4.096V基准,1个LSB正好等于1mV,换算的时候心算就行,省事。
还有个细节:参考电压的噪声要低于1个LSB。12位、3.3V系统,LSB是0.8mV,那Vref的峰峰值噪声最好控制在0.4mV以内。普通LDO的噪声往往在几十微伏到几百微伏RMS,换算成峰峰值可能就超标了。所以高精度场合,基准源后面还要加RC滤波甚至LC滤波。
2.4 采样率、转换时间与吞吐量的关系
很多人把"采样率"和"转换时间"混为一谈。实际上,一次完整的ADC转换包含采样时间+转换时间,两者相加的倒数才是真正的最大采样率。比如采样时间41.5个ADC时钟、转换时间12.5个时钟,ADC时钟14MHz,那一次转换就是(41.5+12.5)/14M≈3.86μs,理论最大采样率约259kSPS。
这里有个权衡:采样时间越长,抗干扰和带载能力越好,但采样率越低。做多通道轮询采集时,如果每个通道都设很长的采样时间,整体刷新率会掉得很厉害。我的经验是,先按信号源内阻确定最小可用采样时间,再留1.5到2倍余量,不要无脑拉满。
3. 取平均:不是"多读几次除一下"这么粗暴
3.1 平均能压掉什么噪声,压不掉什么
取平均的本质是利用噪声的随机性。如果噪声是零均值的高斯白噪声,N次平均后,噪声的标准差会降到原来的1/√N。注意是√N,不是N。也就是说,想让噪声减半,得平均4次;想减到1/10,得平均100次。这个规律决定了平均的收益是递减的,读1000次和读100次,改善并没有想象中那么大。
但平均有个致命前提:噪声必须是随机的、零均值的。如果噪声里混着周期性干扰,比如50Hz工频串扰,简单平均根本压不掉,甚至可能因为采样点和工频相位固定,平均后误差反而被"锁死"。这时候要做的是同步采样或者加窗平均,让采样点均匀覆盖干扰周期。
还有一个更隐蔽的坑:平均压不掉量化误差和失调误差。量化误差在输入不变时是固定的,平均多少次都是那个值;失调误差是系统性的,平均只会让它更稳定地错着。所以平均只能对付随机噪声,别指望它包治百病。
3.2 滑动平均、块平均与中值滤波的取舍
工程上常用的平均方式有三种,各有适用场景:
| 平均方式 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 块平均 | 连续读N次求均值 | 实现简单,噪声抑制明确 | 输出刷新率降为1/N,有延迟 | 慢变信号,如温度、电池电压 |
| 滑动平均 | 维护一个N长度窗口,每来一个新值替换最旧的 | 输出连续,延迟小 | 需要缓存,N大时占RAM | 需要连续输出的场合 |
| 中值滤波 | 取N个值的中位数 | 能剔除脉冲尖峰 | 计算量大,对连续噪声无效 | 有偶发尖峰干扰的场合 |
我的实战组合是:先中值滤波剔除尖峰,再滑动平均压随机噪声。比如读NTC温度,先取5个值去掉最大最小再平均,能同时对付脉冲干扰和随机噪声。这个组合在电池电压、温度、压力这类慢变量上非常好用。
注意:平均的N不是越大越好。N太大,一是响应变慢,二是如果信号本身在变化,平均会把变化"抹平",造成测量滞后。一般N取8到64之间,根据信号变化速率定。
3.3 平均与过采样的区别:一个提精度,一个提分辨率
这里必须澄清一个高频混淆点:平均和过采样不是一回事,虽然它们经常一起用。
过采样是指用远高于信号带宽的采样率去采,然后通过抽取滤波把量化噪声"摊薄"到更宽的频带上,从而提高有效分辨率。理论上,采样率每提高4倍,有效位数增加1位。比如12位ADC过采样256倍,理论上能拿到16位分辨率。但注意,这是分辨率的提升,不是精度的提升——如果ADC本身失调和增益误差很大,过采样后位数上去了,准确度还是那样。
平均更多是压随机噪声,提升的是信噪比,间接改善有效位数。两者数学上有重叠,但出发点不同:过采样关注的是量化噪声的频谱分布,平均关注的是时域上的随机性。
实操上,很多MCU的ADC支持硬件过采样,配置一下就能自动完成累加和移位,比软件平均省CPU。如果平台支持,优先用硬件过采样。
3.4 平均次数怎么定:一个可落地的估算方法
定平均次数N,我一般按这个流程走:
- 先单次采样,记录一段时间内的峰峰值波动ΔVpp。
- 确定你能接受的输出波动ΔVtarget。
- 估算N ≈ (ΔVpp / ΔVtarget)²。
举个例子:单次采样波动峰峰值20mV,你希望输出波动控制在2mV以内,那N≈(20/2)²=100。这个估算偏保守,实际取64或128都能达到接近效果。如果N算出来超过256,说明单靠平均不划算,得从硬件上找原因——要么加滤波电容,要么换基准源,要么改布局。
4. 精度:把误差一项项拆开算
4.1 分辨率不等于精度:一张表看清区别
新手最容易把这两个概念混为一谈。我用一张表说清楚:
| 概念 | 定义 | 决定因素 | 能否靠软件改善 |
|---|---|---|---|
| 分辨率 | 能分辨的最小电压变化 | ADC位数、参考电压 | 过采样可提升 |
| 精度 | 测量值与真实值的接近程度 | 失调、增益、非线性、噪声、基准 | 校准可部分改善 |
| 准确度 | 多次测量的一致程度 | 噪声、温漂 | 平均可改善 |
一颗12位ADC,分辨率是0.8mV,但如果失调误差有5mV,那它的精度可能还不如一颗校准良好的10位ADC。选型时先看精度指标(INL、DNL、失调、增益误差),再看位数。
4.2 误差来源清单:从失调到温漂
ADC的误差来源可以列一个清单,逐项排查:
- 失调误差(Offset Error):输入为0时输出不为0,相当于整条转换曲线平移。可以单点校准。
- 增益误差(Gain Error):转换曲线斜率不对,满量程附近偏差最大。可以两点校准。
- 积分非线性(INL):实际转换曲线与理想直线的最大偏差,反映整体线性度。
- 微分非线性(DNL):相邻码值对应的电压间隔与理想LSB的偏差,DNL超过1LSB可能导致丢码。
- 量化误差:±0.5LSB,无法消除,是理论下限。
- 噪声:热噪声、参考源噪声、电源噪声,靠平均和滤波压制。
- 温漂:失调和增益随温度变化,精密场合要查温漂系数。
- 基准误差:Vref本身的初始精度和温漂,直接乘到结果上。
实际项目里,基准误差和噪声往往是大头,而不是ADC本身。我见过太多人花大价钱买高位数ADC,却用MCU的3.3V供电当基准,最后精度还不如别人用普通ADC配好基准。
4.3 有效位数(ENOB):精度的真实度量
ENOB(Effective Number of Bits)是衡量ADC实际性能最实在的指标,它把噪声和失真都折算成等效位数。公式是:
ENOB = (SINAD - 1.76) / 6.02其中SINAD是信号与噪声失真比。一颗标称12位的ADC,实测ENOB可能只有10.5位,那多出来的1.5位就是"纸面位数"。评估ADC时,ENOB比标称位数靠谱得多。
自己测ENOB也不难:输入一个接近满量程的正弦波,采一批数据做FFT,算出SINAD,代公式即可。如果嫌麻烦,至少可以测噪声自由位数:短接输入,采一批数据算标准差,用log2(满量程/噪声RMS)估算有效位数。
4.4 校准实操:两点校准的完整流程
失调和增益误差可以用两点校准搞定。流程如下:
- 输入一个已知的低电压V1(比如接近0但不在0,避免进入非线性区),读一批取平均,记作C1。
- 输入一个已知的高电压V2(比如接近满量程的90%),读一批取平均,记作C2。
- 计算增益系数:
k = (V2 - V1) / (C2 - C1)。 - 计算失调:
b = V1 - k × C1。 - 实际测量时:
V = k × Code + b。
这个校准能同时修掉失调和增益误差,前提是V1、V2要足够准。校准用的基准电压源精度至少要比目标精度高一个数量级,否则校准本身就把误差引进来了。
提示:校准系数要存在非易失存储里,并且记录校准时的温度。如果工作温区宽,最好做多点温度校准,用查表或分段线性插值。
5. 实操过程:从裸读到稳定读数的完整改造
5.1 硬件侧:布局、滤波与基准的落地细节
硬件上我做了这几件事,效果立竿见影:
- 基准源独立:把Vref从MCU供电改成专用2.5V基准芯片,基准输出加10μF+100nF去耦,再串一个10Ω电阻加1μF电容做RC滤波,截止频率约16kHz。
- 输入RC滤波:ADC输入脚前加1kΩ+100nF,截止频率约1.6kHz,既能滤高频干扰,又不影响采样建立。
- 模拟地与数字地分开:ADC相关的地单独走,最后单点汇接到主地,避免数字回流串进模拟区。
- 采样电容充电余量:信号源内阻控制在5kΩ以内,采样时间设为中等档位。
布局上,基准芯片尽量靠近ADC引脚,滤波电容紧贴引脚放置。这些细节看着琐碎,但每一条都对应着实测能看到的改善。
5.2 软件侧:采样、滤波、校准的代码骨架
软件流程我按这个顺序组织:
// 1. 配置ADC:分辨率、采样时间、参考源 ADC_Init(ADC_RES_12BIT, ADC_SAMPLETIME_MEDIUM, ADC_REF_EXTERNAL); // 2. 单次采样函数 uint16_t adc_read_raw(void) { ADC_StartConversion(); while (!ADC_ConversionDone()); return ADC_GetResult(); } // 3. 中值+平均组合滤波 #define MEDIAN_N 5 #define AVG_N 32 uint16_t adc_read_filtered(void) { uint16_t buf[MEDIAN_N]; uint32_t sum = 0; // 中值滤波:取5个值排序后取中间 for (int i = 0; i < MEDIAN_N; i++) { buf[i] = adc_read_raw(); } sort(buf, MEDIAN_N); uint16_t median = buf[MEDIAN_N / 2]; // 滑动平均:连续采AVG_N次求均值 for (int i = 0; i < AVG_N; i++) { sum += adc_read_raw(); } return (uint16_t)(sum / AVG_N); } // 4. 两点校准换算 float adc_to_voltage(uint16_t code) { return CAL_K * code + CAL_B; }这段代码里,中值滤波负责剔除偶发尖峰,平均负责压随机噪声,校准负责修系统误差。三层叠加,实测读数波动从原来的±35mV降到±1.5mV。
5.3 参数计算:采样时间与平均次数的定量确定
采样时间我按信号源内阻算:外部阻抗5kΩ,加上内部开关电阻约1kΩ,采样电容按8pF估算,RC时间常数约48ns。要充到0.5LSB精度(12位约0.012%),需要约9个时间常数,即约432ns。ADC时钟14MHz,一个周期71ns,所以采样时间至少7个周期。我取了15个周期,留了一倍余量。
平均次数按前面估算方法:单次波动峰峰值约30mV,目标2mV,N≈(30/2)²=225,取256。实测256次平均后波动约1.8mV,符合预期。但256次平均在1kHz采样率下,输出刷新率只有约4Hz,对电池监测够用,对快速信号就不行了。
5.4 实测数据对比:改造前后的读数稳定性
改造前后我各采了1000个点,统计结果如下:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 读数均值 | 3.312V | 3.298V |
| 峰峰值波动 | 70mV | 3mV |
| 标准差 | 18mV | 0.6mV |
| 与万用表偏差 | +14mV | +1mV |
均值从3.312V变到3.298V,说明原来的失调被校准修掉了;波动从70mV降到3mV,是滤波和基准改善的功劳;与万用表偏差从14mV降到1mV,是两点校准的效果。这组数据基本能说明每一层改造各自的贡献。
6. 常见问题与排查技巧实录
6.1 读数跳动大:从噪声源到平均策略的排查顺序
读数跳动是最常见的问题,我一般按这个顺序排查:
- 先看基准:用示波器看Vref的纹波,如果超过1LSB,先解决基准。
- 再看输入:示波器看ADC输入脚的波形,有没有明显干扰或振铃。
- 再看采样时间:信号源内阻大就加长采样时间,看跳动是否改善。
- 最后看平均:前面都排除了,再加大平均次数。
顺序很重要,因为平均只能压随机噪声,如果跳动来自周期性干扰或基准问题,加大平均是白费力气。
6.2 读数偏大或偏小:失调、增益与基准的定位方法
读数系统性偏大或偏小,通常是失调或增益问题。定位方法:
- 短接输入:读到的值就是失调。如果短接后读数不为0,就是失调误差。
- 满量程输入:输入接近Vref的已知电压,看读数偏差。如果低端准、高端偏,是增益误差。
- 换基准验证:用一个已知精度的外部电压源直接接ADC输入,如果读数准了,说明是基准问题。
我遇到过一次读数整体偏小2%,查了半天是基准芯片的初始精度只有±1%,加上分压电阻的±1%误差,凑一起就偏了2%。换了0.1%精度的基准和电阻后正常。
6.3 多通道互相干扰:采样顺序与建立时间的影响
多通道轮询时,前一个通道的残留电荷会影响后一个通道,尤其是通道间电压差大的时候。解决办法:
- 加长采样时间:给采样电容足够的建立时间。
- 插入空采样:切换通道后先丢弃一次转换结果。
- 降低通道切换频率:如果不需要高速轮询,放慢节奏。
我做过一个8通道采集,通道间电压差最大3V,不加空采样时串扰约5LSB,加了空采样后降到1LSB以内。
6.4 温度漂移:校准系数随温度失效的应对
校准系数是在室温下测的,温度一变就失效。应对方法:
- 多点温度校准:在-20℃、25℃、60℃各校准一次,用分段线性插值。
- 选低温漂器件:基准和分压电阻选低温漂型号,从源头减少漂移。
- 软件温度补偿:如果ADC内置温度传感器,可以读温度做一阶补偿。
实测下来,普通厚膜电阻的温漂能到100ppm/℃,从-20℃到60℃变化80℃,阻值变化0.8%,对应读数偏差就有0.8%。换成25ppm/℃的精密电阻后,同样温区偏差降到0.2%。
6.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 读数跳动大 | 基准噪声、电源纹波 | 示波器看Vref和输入 | 加滤波、换基准 |
| 读数系统性偏 | 失调、增益、基准误差 | 短接和满量程测试 | 两点校准 |
| 多通道串扰 | 采样建立不足 | 通道间加空采样 | 加长采样时间 |
| 高温下偏差大 | 器件温漂 | 高低温对比测试 | 换低温漂器件 |
| 读数偶尔跳变 | 脉冲干扰 | 观察跳变时刻 | 中值滤波 |
| 分辨率不够 | 位数或过采样不足 | 算LSB大小 | 过采样或换芯片 |
7. 几个我踩过之后才明白的经验
第一个经验是别迷信位数。我早期选型总盯着"16位""24位",后来发现一颗校准良好的12位ADC配好基准,实际ENOB能到11位以上,比某些标称16位但ENOB只有13位的芯片还稳。选型先看ENOB和噪声指标,位数只是参考。
第二个经验是平均次数要跟着信号走。慢变量可以狠平均,快变量就得权衡响应速度。我做过一个电流环,一开始平均64次,结果环路响应慢得像蜗牛,后来降到8次,配合硬件RC滤波,既稳又快。平均不是越多越好,是要和系统带宽匹配。
第三个经验是校准要趁早,也要定期。产线校准一次就完事的做法,在温区宽或长期运行的场合会翻车。我现在习惯在固件里留一个校准接口,现场能重新校准,省得返厂。
第四个经验是布局和滤波的价值被严重低估。很多人愿意花时间调软件平均,却不愿意花十分钟优化一下地平面和去耦电容。实际上,硬件上省下的每一分噪声,软件上都要花十倍力气去补。先把硬件做干净,软件会轻松很多。
这套东西我在好几个项目上复用,从电池监测到传感器采集,基本都能把读数稳定性做到1mV量级。ADC这东西,入门容易,做精难,但只要把采样、平均、精度这三块拆开理解,剩下的就是耐心调参和反复验证。