很多嵌入式初学者都有个共同的困惑:学了 GPIO、学了串口、学了定时器,寄存器操作都熟了,可一提到 ADC,总觉得它就是“读一个电压值”而已。后来又做了一些实际项目,才意识到 ADC 这个外设远没有想象中那么简单——模拟信号怎么变成数字信号、采样周期和转换时间到底怎么算、为什么有人做出来的采集值稳定得像直线而有人做出来的数据漂得像心电图、为什么同一个 PCB 上 ADC 精度差了十几个 LSB……这些问题如果一开始就有人讲透,能省下大量调试时间。
这篇文章想做的,就是把 ADC 这条链路从头到尾拆开揉碎了讲一遍:从模拟信号和数字信号的本质区别,到 SAR、Sigma-Delta 这些常见架构的工作原理,再到外设寄存器逻辑、DMA 多通道采样、PCB 布局、软件滤波和校准这些实际工程中绕不开的细节。不管你是刚入手 STM32、GD32、S32K312 这类 MCU 的新手,还是已经写了几年嵌入式代码但没深究过 ADC 细节的工程师,这篇文章都适合放在手边当参考。
1. 先搞清楚 ADC 到底在“数”什么:一条从电压到二进制数字的完整链路
ADC,全称 Analog-to-Digital Converter,中文叫模数转换器。它在嵌入式系统里的角色,就是一个“翻译官”,负责把连续的电压信号翻译成 CPU 认识的 0 和 1。但真正理解 ADC,不能只停在“把电压变成数字”这句话上,得把模拟信号、采样、量化和编码这条完整链路拆开看。
1.1 模拟信号不是“连续的电压”那么简单
模拟信号的本质特征是连续。在时间和幅值两个维度上,它都是连续变化的。比如一个 NTC 热敏电阻分压后输出的电压,会随着温度变化平滑地上升或下降,中间任何一个时刻都有对应的电压值,电压值可以是任意小数——1.234567V、1.234568V 都可以。
数字信号则完全不同,它在时间和幅值上都是离散的。时间上,只在某些特定的采样时刻有意义;幅值上,只能表示有限个等级。这就是为什么我们说 ADC 是一个“离散化”的过程:不顾模拟信号在采样时刻之间的变化,也不理睬采样时刻那些无穷多的小数电压,只认有限个离散电平。
这里就引出一个很多新手忽略的点:模拟信号是客观存在的物理量,数字信号是我们对物理量的近似描述。你永远无法用数字系统完整保存一个模拟信号的“全部信息”,ADC 能做的只是在满足一定条件下,让数字表示尽可能接近原始信号,或者更准确地说,让你关心的信息不丢失。这个“条件”就是我们后面要讲的采样定理。
1.2 从采样到编码,一次 ADC 转换到底做了几件事
一次完整的 ADC 转换,内部经历了采样、保持、量化和编码四个阶段。
采样——在某个时刻“抓住”输入的电压值。理想情况下就是瞬间完成,但实际电路里采样开关有导通电阻,采样电容需要充电时间,所以必须保证采样时间足够长,让采样电容上的电压充分逼近外部输入电压。这就是数据手册里SAMPLING TIME(采样时间)存在的意义。如果采样时间不足,采到的电压会偏低,尤其是信号源内阻大的时候,误差会非常明显。
保持——把采到的电压“冻结”起来,在转换期间内保持不变。采样电容(通常叫CSH)就是干这个的。保持阶段要求电容的漏电足够小,否则电荷泄漏会导致保持电压漂移。你会看到高性能 ADC 前面往往加一个运放做缓冲,目的就是给采样电容提供足够低的阻抗,让充电更快更准。
量化——把保持的模拟电压和内部基准电压(VREF)进行比较,看它落在哪个电压区间里。这里有个关键概念叫量化误差。对于一个 12 位 ADC,输入范围 0~3.3V,那么它把 3.3V 分成 4096 份,每份约 0.806mV。如果真实电压是 1.0004V,量化后就只能落在 1.000V 或 1.001V 的某个档位上,这个“对不齐”的偏差就是量化误差,理论上最大为 ±0.5 LSB。
编码——把量化出来的档位编号换算成二进制数。比如 12 位 ADC 输出 0x1F4,就代表电压落在第 500 个档位上,对应的电压约 0.4028V。这一步在 MCU 内部由硬件自动完成,你从数据寄存器里读到的就是编码后的结果。
1.3 为什么分压电阻和阻抗匹配这么重要
讲完四个阶段,就该明白一个现实问题:ADC 的“采样”环节会对你被测的电路产生影响。想象一下,采样电容在采样瞬间相当于把外部信号源短路了一下,如果信号源内阻很大,电容充电就会很慢,你可能需要分配很长的采样时间才能采准。
举个例子,一个 100kΩ 内阻的信号源,直接接到 STM32 的 ADC 引脚上,如果采样周期配置得太短,采样电容还没来得及充到最终值,采样就结束了,读到的数值会比实际低。这在电池电量检测、电阻分压采样这类场景中特别常见——分压电阻动辄几十 kΩ,不加缓冲直接进 ADC,就会出现“电压读数偏低且不稳定”的怪现象。
解决办法有三个方向:
- 在信号源和 ADC 之间加电压跟随器(运放),用低阻抗输出驱动 ADC。
- 加一个 0.1μF 左右的电容到地,利用电容的电荷储备效应减小采样瞬间的电压跌落,但要注意这会形成低通滤波,影响信号带宽。
- 如果条件允许,增大 ADC 的采样周期,给采样电容更长的充电时间。
这一节的核心就是想让你建立一个认知:ADC 不是“接上去就能测准”的黑盒子,它的整个工作过程牵涉到源阻抗、采样电容、充电时间和基准电压这些物理层面的东西。搞懂这条链路,后面所有问题都好理解。
2. SAR、积分型、Sigma-Delta 怎么选:三种主流 ADC 架构的适用场景
ADC 的“翻译”方法不止一种,不同的内部工作机制决定了不同的性能表现和适用场景。嵌入式领域最常见的是 SAR 型,高精度测量领域常见 Sigma-Delta 型,积分型虽然慢但现在少见了,Flash 型和流水线型更多出现在高速领域。选型之前先把它们的脾气摸清楚。
2.1 SAR:嵌入式 MCU 里最常见的选择,也是入门第一个要搞懂的架构
逐次逼近型(SAR, Successive Approximation Register)ADC 的工作原理,可以想象成一次“猜数字”游戏:裁判心里想了一个电压值(输入的模拟电压),你每次报一个猜测值,裁判告诉你猜大了还是猜小了,你根据反馈不断调整,直到猜中为止。
具体过程是这样的:SAR ADC 内部有一个比较器、一个逐次逼近寄存器、一个 DAC(数模转换器)。转换开始时,寄存器先把最高位置 1,DAC 把这个“猜测值”转成对应的模拟电压,和输入电压比较;如果输入电压比猜测值大,最高位保留为 1,否则清 0。接着对次高位重复同样操作。对一个 N 位 ADC,这种二分法比较需要 N 次,所以 SAR 转换时间正比于分辨率。12 位 ADC 一次转换大约需要 12 个比较周期,加上采样时间,典型转换时间在微秒量级。
SAR 的优点是架构简单、功耗低、转换速度快,而且没有延迟管线,适合大多数 MCU 内置场景。STM32、GD32、S32K312 这类主流车规级 MCU 内部的 ADC,基本都是 SAR 架构。它的缺点是精度受限于内部 DAC 和比较器的匹配程度,要做到 16 位以上很难,所以高精度应用一般不选 SAR。
2.2 Sigma-Delta:凭什么能做到高精度低成本
过采样 Σ-Δ ADC 的思路和 SAR 完全不同。它跑得很快,但用的是一种“噪声整形 + 数字滤波”的组合策略。
它内部有一个积分器、一个 1 位比较器(或者多位)和一个 1 位 DAC。调制器以远高于奈奎斯特频率的速率对输入进行采样,输出一串 0 和 1 的比特流。这串比特流里,信号成分集中在低频,而量化噪声被“整形”推到高频,这就是噪声整形(Noise Shaping)。后面再跟一个数字低通滤波器,把高频噪声滤掉,最后输出高分辨率的结果。
因为量化噪声大部分被推到高频并被滤除,Σ-Δ ADC 在低频范围内能达到非常高的有效位数。工业上大量使用 24 位 Σ-Δ ADC 做称重传感器、热电偶采集、电流检测这类应用,价格还不贵。它的缺点是转换速度慢,而且存在明显的延迟,不适合高速信号采集,也不太适合做多通道高速轮询。很多 MCU 内部集成的也只是 SAR,Sigma-Delta 通常作为外部独立芯片挂在 SPI/I2C 上。
2.3 Flash、流水线和积分型:知道是什么就够了
Flash ADC 是速度最快的架构,它用 2^N-1 个比较器并联,一次比较直接输出 N 位结果。缺点是资源消耗巨大,12 位 Flash ADC 需要 4095 个比较器,基本不可能集成到普通 MCU 里,主要用于射频基站、示波器前端这种需要 GHz 级采样率的地方。
流水线型(Pipeline)ADC 则是把多个低精度级串起来,每一级完成一部分位数转换,整体达到高速度中等精度,常用于视频采集、通信中频数字化。
积分型(比如双斜率 ADC)如今在 MCU 里已经很少见,它的核心思想是先用固定时间对输入电压积分,再对基准电压反向积分,通过测量反向积分时间来得到输入电压。它精度高、抗工频干扰强,但转换速度极慢,老式数字万用表里很常见。
在实际项目里,你只需要记住两句话:MCU 内置就是 SAR,高精度就是 Sigma-Delta,其他架构了解原理即可,遇到时知道怎么查手册就行。
3. 数据手册背后的真实含义:分辨率、采样周期、信噪比这些参数怎么用
拿到一颗 ADC 芯片或 MCU,打开参考手册看到一串参数:12 位分辨率、最大采样率 1Msps、信噪比 71dB、采样周期可配 1.5~239.5 周期。每个字都认识,但串在一起该怎么用,很多人是含糊的。这一节把最重要的几个参数逐一讲清楚。
3.1 分辨率决定灵敏度,但精度不等于分辨率
分辨率是指 ADC 输出的二进制位数。12 位 ADC 把满量程分成 4096 份,16 位分成 65536 份。分辨率越高,理论上对输入信号微小变化的感知就越灵敏,也就是“最小可分辨电压”越小。
但分辨率高不等于精度高。精度是“量得准不准”的问题,分辨率是“量得细不细”的问题。一颗 16 位的 ADC,如果参考电压噪声很大,或者内部线性度不好,实际精度可能还不如一颗精心设计的 12 位 ADC。衡量这个差距的指标叫有效位数(ENOB),公式是:
ENOB = (SINAD - 1.76) / 6.02SINAD 是信纳比,包含信号、噪声和失真的关系。当芯片手册标称精度时,要看 ENOB 而不是看位数。有些便宜的 16 位 ADC,ENOB 可能只有 14 位甚至 12 位,实际用起来和 12 位 ADC 没什么区别,只是分辨率显得高。
3.2 采样周期与转换时间:一条必须自己动手算的公式
这是入门最容易卡住的地方。以 STM32 为例,ADC 的总转换时间公式是:
Tconv = 采样时间 + 12.5 个 ADC 时钟周期其中 12.5 个周期是 SAR 比较过程固定的开销,采样时间由寄存器配置。假设 ADC 时钟为 14MHz,配置采样时间为 1.5 个周期,总时间就是:
(1.5 + 12.5) / 14MHz ≈ 1μs这对应约 1Msps 的采样率,是很多 STM32 的极限值。如果信号源内阻大,需要把采样时间调长到 7.5、28.5 甚至 239.5 个周期,转换速度就相应下降。注意这里采样周期说的是 ADC 时钟周期,不是 CPU 主频周期。ADC 时钟往往由 PCLK2 分频得到,分频系数在 RCC 配置里设置。有些人代码写完了,发现 ADC 读值总是跳动,排查半天发现是 ADC 时钟没配好,超过了最大允许频率,工作就不正常。
3.3 信噪比、有效位数和最大采样率的实际意义
信噪比(SNR)表示信号功率与噪声功率的比值,单位 dB。理想 N 位 ADC 的理论 SNR 公式是 6.02N + 1.76dB,所以 12 位理想 SNR 是 74dB,16 位是 98dB。实际器件达不到理论值,芯片手册会给出典型测试条件下的实测 SNR,比如 71dB,说明实际有效位数大约是:
(71 - 1.76) / 6.02 ≈ 11.5 位最大采样率决定了你能采集的信号带宽。根据奈奎斯特采样定理,采样率必须大于信号最高频率的两倍,采样之后的数字序列才能无失真地还原原始信号。实际工程一般留 5~10 倍裕量。比如要采集 10kHz 的音频信号,20kHz 采样率只是理论底线,实际上用 100kHz 以上的采样率效果才够好。
还有一点需要提醒:数据手册上的最大采样率通常是“常温、理想电源、特定测试条件”下的数值,产品设计时必须留足裕量。比如手册标 1Msps,你要是真的按 1Msps 跑,还得同时保证信号源阻抗很低、ADC 时钟完全符合要求,否则精度会明显下降。工业上往往只跑 60%~80% 的标称采样率。
3.4 一个容易被忽略的“采样周期”热搜误区
网上有个热搜词“adc采样周期”,很多新手误以为它等于 ADC 转换时间,其实两者不是一回事。采样周期只是总转换时间的前半段,负责给采样电容充电,充电越充分,后面对比结果越准确。如果你在项目里碰到“数值偏低”的问题,第一反应除了检查分压电阻、电源基准,还要想想是不是采样时间太短了。
我遇到过一个温度采集的工程案例:用一个 10kΩ NTC 和 10kΩ 电阻分压,NTC 输出直接接 MCU 的 ADC,采样时间配置成 1.5 个周期,结果读到的温度整体偏低 3℃ 左右。原因就是 NTC 分压网络等效内阻太高,采样电容充不满。后来把采样时间调到 239.5 个周期,温度读数立刻正常了。这个案例让我养成了一个习惯:只要信号源阻抗高于 10kΩ,采样时间就不要低于 28.5 个周期。
4. 作为一个外设,ADC 是怎么被“用起来”的:从时钟到寄存器到 DMA
上面讨论的是 ADC 内部原理,这一节回到嵌入式开发的日常:写代码的时候,ADC 这个外设到底是怎么和 MCU 的其他部分协同工作的?为什么上电之后不能直接读数据?为什么每个外设都要先开时钟?理清楚这些,你才算真正“掌握”了这个外设,而不是只会照抄例程。
4.1 上电后外设逻辑:时钟、引脚模式、电源和参考电压缺一不可
大多数 MCU 外设(串口、ADC、定时器)上电后不是马上就能用的。核心原因是低功耗设计:如果不用的外设也一直通电工作,待机电流早就超标了。所以 MCU 的电源管理架构给每个外设都设计了独立的时钟门控开关,你得先在 RCC 里把 ADC 外设的时钟打开,它的寄存器才能读写,内部模拟电路才开始工作。
以 STM32 为例,ADC 的启用流程通常是这样的:
- 使能 GPIO 时钟,把采样引脚配置为模拟输入模式。这一步很多人会漏,直接把引脚配成浮空输入,结果 ADC 读值总是在几个固定数之间跳。模拟输入模式下的施密特触发器是关闭的,引脚直接连接 ADC 内部采样结构,而数字输入模式的引脚结构不适合直接接模拟信号。
- 使能 ADC 时钟,设置 ADC 时钟分频系数,检查 ADC 时钟频率是否在芯片允许范围内。
- 校准。STM32 系列一般建议上电后做一次校准,内部会自动计算校准因子,写入 ADC 的校准寄存器。跳过校准不是不能用,但初始偏移误差可能比较大。
- 配置通道、采样时间、触发方式、对齐方式,然后使能 ADC。
还有电源和参考电压。ADC 的模拟部分需要独立的电源引脚(如 VDDA)和参考电压引脚(如 VREF+)。在高精度场景下,VDDA 和 VREF 必须经过滤波,不能用数字电源直接供电。很多入门开发板直接把 VDDA 和 VCC 短接,VREF 悬空,低精度应用没问题,但要做 16 位高精度采集,就必须认真处理参考电压。
4.2 多通道采样与 DMA:为什么不用中断方式逐个读
实际采集系统很少只采一路信号,多通道轮询是常态。最容易踩坑的是“多通道时读数据乱序”问题,网上经常有人搜“adc四通道使用dma”“gd32e230 adc dma数据紊乱”,根源基本都在于对规则通道和注入通道的机制没有理解清楚。
规则通道是 ADC 常规转换的通道序列。你可以配置一个序列,比如顺序是通道 0、通道 1、通道 2、通道 3,每次触发就按这个顺序依次转换。转换完成后结果放在同一个数据寄存器ADC_DR里,如果不用 DMA,你得在每次转换完成后立刻把数据读走,否则下一次转换结果会覆盖上一次。用中断方式处理时,如果中断响应不及时,数据就丢了或者错位了。用 DMA 的方式,一次触发后 DMA 按顺序把每次转换结果搬运到内存数组,流程是:
- 配置 DMA 请求为循环模式,数据宽度半字(16 位,匹配 12 位 ADC 输出)。
- 配置内存地址为数组首地址,数组长度和转换通道数一致。
- 使能 DMA 的循环模式,这样一次 DMA 传输完成后自动重载,CPU 只需要在数据处理时访问数组。
多通道 DMA 出现“数据紊乱”还有一个常见原因:DMA 循环模式和 ADC 的 DMA 请求信号配合不佳,导致搬运顺序错位一位。解决办法之一是在 DMA 中断里判断当前缓冲区索引,或者把数据长度设为通道数加 1,转换完多个序列后从期望的通道开始解析。另一个有效做法是使用定时器触发 ADC 转换,保证每次触发周期固定,而不是用软件连续触发,这样数据节奏是稳定的,配合 DMA 搬运会容易得多。
4.3 一个典型例程:单通道电压采集加串口打印的完整思路
用一个最小例程串联上面所有知识点。拿 STM32F103 为例,采集 PA1 上的电压,通过串口打印到终端,代码如下:
// 假设已经初始化好串口1,波特率115200 // 1. 配置ADC引脚 GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); gpio.GPIO_Pin = GPIO_Pin_1; gpio.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入 GPIO_Init(GPIOA, &gpio); // 2. 配置ADC时钟分频,PCLK2 72MHz,分频6 -> 12MHz ADC时钟 RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 3. ADC初始化 ADC_InitTypeDef adc; adc.ADC_Mode = ADC_Mode_Independent; adc.ADC_ScanConvMode = DISABLE; // 单通道,不使能扫描 adc.ADC_ContinuousConvMode = ENABLE; // 连续转换 adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发 adc.ADC_DataAlign = ADC_DataAlign_Right; adc.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &adc); // 4. 配置通道1的采样时间,这里给55.5个周期,适合中等阻抗信号源 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); // 5. 校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); // 6. 使能并开始转换 ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(1) { if(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) { uint16_t val = ADC_GetConversionValue(ADC1); float voltage = val * 3.3f / 4096.0f; char buf[64]; sprintf(buf, "ADC value: %d, voltage: %.3f V\r\n", val, voltage); uart_send_string(buf); } delay_ms(500); }这个例程里,关键点都在注释里了:引脚模式、ADC 时钟分频、采样时间、校准、使能顺序。很多新手把这个流程当成“八股文”背诵,却不知道每一步意味着什么。等你踩过几次坑,就会明白这些步骤没有一步是多余的。
5. 硬件板级问题:时钟抖动、电源噪声和 PCB 布局这三道坎
嵌入式 ADC 项目的精度并不只由芯片参数决定。一个能跑 12 位理论精度的 ADC,如果 PCB 布局不合理、电源纹波大、参考电压不干净,实际做到 10 位精度都算好的。网上有一个热搜词是“adc/dac 电路设计:规避时钟抖动与电源噪声的3个pcb布局要点”,这确实是很多硬件工程师反复强调的内容。这一节就讲讲板级问题。
5.1 时钟抖动如何影响采样精度
时钟抖动(Clock Jitter)指的是 ADC 采样时钟沿的微小时间波动。对于低频信号,时钟抖动的影响可以忽略,但采集高频信号时,一个很小的时钟抖动就能导致明显的电压误差。原因很简单:抖动导致采样点的时间位置不确定,而在高频信号上,电压变化率很大,时间上差一点,电压就差很多。
举例来说,采集一个 1MHz、幅值 3.3V 的正弦波,时钟抖动 1ns,信号每秒变化率最大约为 2π × 1MHz × 3.3V ≈ 20.7V/μs,1ns 抖动带来的电压误差约为 20.7mV,这已经超过 12 位 ADC(LSB 约 0.806mV)的好几个 LSB 了。
因此,高速 ADC 应用里,采样时钟质量非常重要。MCU 内部 ADC 的时钟通常来自 PLL 分频,往往会带有一定的抖动,做低速采集没问题,做高速高质量采集时就需要考虑外部精密时钟源。
5.2 电源噪声:影响参考电压等于直接污染测量结果
ADC 的测量本质是把输入电压和参考电压做比例比较。如果参考电压本身不稳定,测量结果必然不稳定。这也是为什么 ADC 芯片手册里 VREF 引脚周围总要求加滤波电容。电源噪声对 ADC 最直接的影响就是导致转换结果跳变。
这里有个实操原则:ADC 的参考电压和模拟电源要独立滤波。常见的做法是加磁珠或者小电阻(10Ω 左右)将模拟电源和数字电源隔离,然后接 10μF 钽电容和 100nF 陶瓷电容去耦。参考电压引脚要单独用高质量电容滤波。不要图省事接个 100nF 就完事,低频纹波需要大电容,高频噪声需要小电容,大小搭配才有效。
5.3 PCB 布局的 3 个具体要点
- 模拟地和数字地要单点连接。不要大面积铺铜后直接连成一片。如果 ADC 是 MCU 内部的,就在 MCU 底部的地焊盘位置把模拟地数字地单点连起来,这个连接点要靠近 ADC 电源引脚。
- 模拟信号线要远离数字信号线,尤其是 PWM、SPI、I2C 这类高频翻转的信号线。如果 PCB 空间有限无法避免交叉,用平面地层做屏蔽,在模拟信号线两侧加接地走线。
- ADC 输入滤波电容要尽量靠近 MCU 引脚。这个电容的作用是给采样电容提供瞬态电荷,距离越远,回路电感就越大,采样瞬间的电压跌落也越严重。
说到 PCB 布局,我还想强调一个热词相关的问题:“(∑-δ)adc前端rc滤波设计”。Sigma-Delta ADC 前端通常需要 RC 低通滤波,用来抑制带外噪声和混叠。这个 RC 的截止频率不是随便设置的,太高了滤不掉噪声,太低了会衰减有用信号和导致建立时间过长。一般设计时,先确定信号带宽,再把滤波截止频率设置为信号带宽的 5~10 倍,同时考虑输入阻抗的影响,必要时用运放做有源滤波。
6. 软件层面的坑和经验:从数据漂移到多点校准
硬件做好之后,ADC 读数还是会出各种问题。数据漂移、跳变、非线性、DMA 乱序……这些都是在实际项目中反复出现的问题。这一节记录一些常见问题的排查思路和解决方案,都是我在实际工程中踩过又填上的坑。
6.1 数据漂移最常见的三个根因
数据漂移是所有做 ADC 采集的人都会遇到的问题。所谓漂移,就是相同的物理量下,ADC 读值随时间缓慢变化,或者每次开机读值都不一样。常见原因有三个:
第一个是温度漂移,内部参考电压或器件本身的温漂导致测量偏移随温度变化。这个没办法完全消除,只能通过校准减小影响。
第二个是参考电压不稳定。如果你用 MCU 的 VDDA 做参考电压,而 VDDA 同时给其他电路供电,一旦负载变化,参考电压就会波动,读值自然漂。解决办法是使用独立的参考电压芯片,或者至少在 VDDA 前加 LDO 隔离。
第三个是采样电容老化和污染。这是硬件层面的问题,比如 PCB 清洗不彻底、潮湿环境导致漏电流增大。出现这种问题往往需要怀疑板子本身。
排查漂移问题时,最有效的方法是用一个高精度电压源输入固定电压,然后长时间记录 ADC 读数,观察漂移曲线,大致判断是随机跳变(噪声)还是趋势性变化(温漂或参考源漂移)。
6.2 软件滤波:简单有效的滑动平均和中值滤波
如果硬件已经尽量优化,读数仍然有随机噪声,软件滤波就是最后一道防线。常见的方法有以下几种:
- 限幅滤波:相邻两次采样值之差超过阈值,就认为是一次干扰,丢弃或限制变化速率。适合剔除偶发毛刺。
- 中值滤波:连续采 N 个值,排序去中间值。对脉冲式干扰效果好,适合慢变信号,但对快速变化信号会失真。N 一般取奇数,如 5 或 7。
- 滑动平均滤波:维护一个长度为 N 的队列,每次取平均值。能有效抑制周期性噪声,但会引入滞后。N 越大越平滑,响应也越慢。
- 一阶低通滤波:
y[n] = α * x[n] + (1-α) * y[n-1],实现简单,运算量小,适合嵌入式。α 越大,响应越快,滤波效果越弱。
实际项目中,我经常把限幅滤波和一阶低通滤波组合使用,既消除毛刺,又平滑曲线,计算开销很小。只看效果选一个中值或者滑动平均也够用,但要知道各自的滞后特性。
6.3 校准:从线性校准到“adc三点校准”
ADC 校准的本质是找到实际转换曲线和理想曲线之间的偏差,然后补偿。最简单的是单点校准,也叫零偏校准:输入 0V(或 GND),记录读数偏差作为偏移量,在软件里减去这个偏移。这个方法只能校准偏移误差,不能校准增益误差。
更常用的是两点校准:输入两个已知且稳定的电压(比如 10% 满量程和 90% 满量程),记录对应的 ADC 读数,得到一条直线:
实际电压 = (ADC读数 - ADC零点) * K + 实际零点电压其中 K 是斜率校正系数。两点校准同时校正了偏移误差和增益误差,适合大多数线性的 SAR ADC。
对于非线性较明显的器件,或者需要更高精度的测量,就需要做“三点校准”甚至多点校准。实现方式是把量程分成多段,每一段用两点校准算一条直线,查表插值。比如网上常搜到“外部adc三点校准”,一般是在 0%、50%、100% 三个点各采一次参考电压,把曲线分成两段做分段线性校正。分段越多,精度越高,但校准工作量也越大,工业批量生产时通常需要为此设计专门校准工装。
6.4 一个容易被忽略的坑:读取 ADC 结果寄存器时,可能读到尚未应用校准的值
有些 MCU 的 ADC 模块会有校准补偿功能,比如对结果做 offset trim。数据手册上常会出现一句很不起眼的话:当你读取 ADC 结果寄存器时,可能读到的是尚未应用 offset trim 的原始值,或者读到已经应用 trim 的值,取决于读时机或寄存器配置。
这类问题的典型表现是:校准之后读数还是有一个固定偏移,或者每次读取偶发出现一个固定的错误值。遇到这种情况,不能靠软件做二次处理,应该去查数据手册里关于结果寄存器、校准寄存器和触发关系的描述。比如有的 MCU(型号不能点名,但 S32K3 系列就有类似情况)在 DMA 读取结果时,如果 DMA 请求刚好触发在校准值更新之前,就会搬到旧值。解决办法一般是等待某个状态位确认,或者用 MUX 选择“只读校准后结果”的寄存器。
这一类问题特别容易藏在“八股文”之外,平时不看手册真发现不了。我的习惯是:用到新芯片的 ADC 时,先把数据手册里 ADC 章节从头到尾读一遍,尤其是 Result Register 和 DMA Interaction 部分,能省下后面大量调试时间。
7. 从入门到跑通完整链路:给新手的学习路线和面试自查清单
写到这里,ADC 的基本原理、架构、参数、外设逻辑、硬件布局、软件处理都已经覆盖到了。最后这个章节,说说如何把这些知识系统化地串起来,形成一个能应对学习和面试的知识框架。
7.1 结合网上常搜的“嵌入式学习路线”和“嵌入式开源项目”怎么安排自己
嵌入式学习路线的话题每次讨论都很热闹。ADC 部分,我建议的顺序是这样的:
- 用开发板跑一遍单通道 ADC 采集,配合串口或者 OLED 显示,把采样、转换、读寄存器的基本流程跑通。
- 做多通道采集,加上 DMA,理解规则通道序列和缓冲区的关系。
- 测量一个真实物理量,比如 NTC 温度传感器或者光敏电阻,做一个简单的环境监控或者温度采集系统,这能逼你把分压电阻、参考电压、采样时间这些细节全部串起来。
- 往高端走一点,可以做基于 STM32F4 的 FFT 频谱分析仪。这个项目在开源社区很常见,核心就是 ADC 高速采样加上 FFT 运算,涉及采样率设置、缓冲区管理、窗口函数选择,对理解 ADC 的频域行为帮助极大。
- 还可以研究一下 BM C 通过 ADC 读取电压的做法。服务器主板上 BMC 常用 ADC 读取电压轨,核心思路是电阻分压 + 多通道扫描 + 校准,原理和 MCU 是一致的。
7.2 嵌入式面试中 ADC 相关的高频问题
面试官喜欢问的 ADC 问题,其实就围绕着这节课里讲的几个点:
- 什么是分辨率、精度、采样率?它们之间有什么区别?
- 简述 SAR ADC 的原理。
- 奈奎斯特采样定理是什么?如果采样率不够会发生什么?
- 为什么 ADC 读数不稳定?怎么解决?
- 多通道扫描时数据为什么会乱序?DMA 怎么配合?
- 如何测量一个高内阻的信号源?
- 硬件上如何提高 ADC 采样精度?
这些问题没有一个能靠背八股文应付过去的,面试官追问几句就能看出是真懂还是背下来的。真正理解并动手做过,回答起来才会有底气。
最后分享一个实操小技巧
如果你正在调一个 ADC 读数不准的板子,手头又没有高精度电压源,我推荐一个土办法:用一节全新的碱性电池当参考源。碱性电池的开路电压相当稳定,一般在 1.6V 左右,用万用表测出精确值,然后接入 ADC 引脚。由于电池内阻很低,不用考虑阻抗匹配问题。对比 ADC 读数和万用表读数,可以快速评估板子的整体误差水平。这个方法帮我排查过好几个项目的 ADC 问题,成本几乎为零,效果却非常直观。
ADC 没有想象中那么神秘,但它确实不是一个“接上就读”的傻瓜外设。从模拟信号的理解,到内部架构的选型,再到外设的启用逻辑和硬件布局,每一步都有值得深挖的细节。把这篇文章里的知识吃透,再去动手做一两个自己的小项目,相信你会对嵌入式系统里“读一个电压值”这件小事,建立起完全不一样的认知。