ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ADC采样精度提升实战:从原理到滤波与误差校准

ADC采样精度提升实战:从原理到滤波与误差校准

ADC 这个模块,几乎是每个做嵌入式、硬件、信号采集的人绕不开的一道坎。你可能会遇到这样的场景:代码里读出来的 ADC 值跳得厉害,明明输入是稳定的直流,采样结果却在几十个 LSB 之间来回蹦;或者你换了通道,发现前一通道的残留电压把后一通道的读数带偏了;又或者你算出来的有效位数只有 8 位,但芯片手册上写着 12 位。这些问题背后,其实都指向同一件事——你对 ADC 的采样原理、取平均策略和精度评估还没有形成一套完整的认知框架。

这篇内容就是围绕 ADC 转换这件事,把采样原理、取平均方法、精度评估、前端电路设计、通道切换、触发机制这些核心问题串起来讲清楚。不管你是刚接触 STM32 的初学者,还是已经在做雷达、超声波、电力监测这类对精度有要求的项目,都能从中找到可以直接落地的操作方法和排查思路。我会尽量把每个参数背后的“为什么”讲透,而不是只丢给你一段代码。

1. ADC 采样原理:从模拟电压到数字码值到底发生了什么

1.1 逐次逼近型 ADC 的工作过程拆解

大部分 MCU 内置的 ADC 都是逐次逼近型(SAR ADC)。它的核心结构包括采样保持电路、比较器、DAC 和逐次逼近寄存器。整个转换过程分两个阶段:采样阶段和转换阶段。

采样阶段,内部采样开关闭合,外部模拟电压通过前端电路给采样电容充电。这个充电过程需要时间,也就是采样时间。采样时间不够,电容上的电压还没跟上输入电压,转换结果就会偏低或偏高。转换阶段,采样开关断开,保持电容上的电压,然后 SAR 逻辑从最高位开始逐位比较,最终输出一个数字码值。

这里有一个关键点:采样电容的充电时间常数由前端等效电阻和采样电容共同决定。如果你在 ADC 输入前面串了一个大电阻,比如 100kΩ,而采样电容是 8pF,那么时间常数就是 800ns。要充到 0.5 LSB 的精度,大概需要 7 到 8 个时间常数,也就是 6μs 左右。如果你的采样时间只设了 1.5 个 ADC 时钟周期,在 12MHz ADC 时钟下才 125ns,那采样结果必然不准。

提示:采样时间的选择不是随便填一个值就行,它和你的信号源内阻直接相关。信号源内阻越大,需要的采样时间越长。

1.2 采样定理在实际项目中的边界条件

奈奎斯特采样定理说采样率要大于信号最高频率的两倍。但在实际项目里,这条定理只是最低门槛。如果你采样一个 1kHz 的正弦波,采样率设成 2.1kHz,理论上能恢复,但实际上对采样时刻的抖动极其敏感,稍微有一点时钟抖动,重建出来的波形就面目全非。

更实际的做法是过采样。比如你要测一个 50Hz 的工频信号,采样率设到 10kHz 甚至更高,这样不仅波形重建更完整,后续做数字滤波、取平均、FFT 分析都有足够的余量。过采样还有一个好处:每提升 4 倍采样率,理论上可以换来 1 位有效分辨率的提升,前提是信号里叠加了足够白噪声。

但过采样也不是没有代价。采样率越高,ADC 功耗越大,数据吞吐量越大,对 CPU 中断处理能力的要求也越高。在 STM32 上,如果你用定时器触发 ADC 并以 1MHz 的速率采样,每次转换完成都进中断,CPU 基本上就不用干别的了。这时候必须配合 DMA 搬运,让数据自动存到内存里。

1.3 采样周期与转换时间的配置逻辑

以 STM32 为例,ADC 的总转换时间等于采样时间加上逐次逼近的固定周期。12 位分辨率下,逐次逼近需要 12 个 ADC 时钟周期。如果采样时间设为 84 个周期,总转换时间就是 96 个周期。在 ADC 时钟为 12MHz 时,单次转换耗时 8μs,对应最高采样率约 125kHz。

很多人配置的时候只看总转换时间,忽略了采样时间对精度的直接影响。我的经验是:先用较长的采样时间跑通,确认读数稳定后,再逐步缩短采样时间,观察读数什么时候开始明显偏差。这样你能找到当前硬件条件下的最短可靠采样时间,既不浪费转换速度,也不牺牲精度。

另外要注意,STM32 不同系列的 ADC 时钟上限不同。F1 系列 ADC 时钟最高 14MHz,F4 系列可以到 36MHz,H7 系列更高。配置时一定要查对应型号的数据手册,超频使用 ADC 时钟会导致转换结果不可预测。

2. 取平均的几种做法与各自的适用场景

2.1 简单算术平均:最直接但也有陷阱

算术平均是最容易想到的方法:连续采 N 次,求和后除以 N。在噪声近似白噪声、且信号本身变化缓慢的场景下,算术平均能有效压低随机噪声。理论上,N 次平均可以把噪声标准差降低到原来的 1/√N。也就是说,16 次平均能把噪声降到 1/4。

但算术平均有两个陷阱。第一,如果信号本身在变化,比如你在测一个动态变化的电压,平均会把变化也一起抹平,导致响应滞后。第二,如果噪声不是白噪声,而是有规律的周期性干扰,比如工频串扰,简单平均不一定能压下去,甚至可能因为采样时刻和干扰同相而完全无效。

我一般会先看 ADC 原始数据的分布。如果数据围绕某个中心值上下随机跳动,算术平均有效。如果数据呈现明显的周期性波动,那就得先找干扰源,而不是靠平均硬压。

2.2 滑动平均与 EMA:动态场景下的取舍

滑动平均维护一个长度为 N 的窗口,每次新数据进来,踢掉最老的数据,加入新数据,重新求平均。相比算术平均,滑动平均的响应更快,因为它不需要等 N 次采样全部完成才输出结果。但滑动平均需要额外的内存来存窗口数据,N 越大,内存开销越大。

EMA(指数移动平均)则更进一步,它不需要存历史数据,只需要一个系数 α。公式很简单:新值 = α × 当前采样 + (1-α) × 旧值。α 越小,平滑效果越强,但响应越慢;α 越大,响应越快,但平滑效果越弱。

在嵌入式里,EMA 非常实用,因为它只需要一个变量,计算量极小。我经常用 EMA 做 ADC 的实时滤波,α 取 0.1 到 0.3 之间,具体看信号变化速度。如果信号变化很慢,比如温度采集,α 可以取 0.05;如果是音频包络检测,α 要取大一些,比如 0.5。

用 numpy 验证 EMA 的时候,可以用np.convolve()来模拟,但要注意np.convolve()做的是卷积,不是递推,它对应的是 FIR 形式的加权平均,和真正的 EMA 递推在边界处理上有差异。如果你只是用来观察趋势,问题不大;如果要精确复现嵌入式里的 EMA 行为,还是老老实实写循环。

2.3 中值滤波与去极值平均:对抗脉冲干扰

有些场景下,ADC 读数里会混入偶发的尖峰脉冲,比如继电器动作、电机启停时。这种脉冲用算术平均压不住,因为它会把平均值整体拉偏。这时候中值滤波就派上用场了。

中值滤波的做法是连续采 N 次(N 通常取奇数),排序后取中间值。脉冲干扰最多影响排序中的一两个位置,对中值的影响很小。N 取 5 或 7 就能有很好的效果。

去极值平均则是结合了平均和中值的思路:采 N 次,去掉最大和最小的几个值,剩下的求平均。这样既能压随机噪声,又能抗脉冲干扰。我在电力监测项目里经常用这个方法,N 取 16,去掉最大最小各 2 个,剩下的 12 个求平均,效果比单纯算术平均稳得多。

2.4 过采样与抽取滤波:用速度换精度的经典套路

过采样加抽取是提升 ADC 有效分辨率的经典方法。原理是:以远高于奈奎斯特频率的速率采样,然后做低通滤波和降采样。每 4 倍过采样理论上带来 1 位分辨率提升,16 倍过采样带来 2 位,64 倍带来 3 位。

但这里有一个前提:输入信号里必须存在一定幅度的白噪声,也就是抖动。如果输入信号非常干净,过采样后所有采样值都一样,那平均下来还是那个值,分辨率并没有真正提升。实际项目中,ADC 本身的量化噪声和热噪声通常就够充当抖动源了。

在 STM32 上实现过采样,可以用硬件过采样功能(部分系列支持),也可以用软件在 DMA 缓冲区里做累加和移位。硬件过采样的好处是不占 CPU,配置好寄存器后自动完成。软件过采样更灵活,但要注意累加时的数据类型,16 位 ADC 累加 256 次,结果要用 32 位变量来存,否则会溢出。

3. 精度这件事:有效位数、信噪比与误差来源

3.1 分辨率不等于精度:ENOB 才是真实指标

很多人把 12 位 ADC 理解成精度就是 1/4096,这其实混淆了分辨率和精度。分辨率是 ADC 能区分的最小电压变化,精度是转换结果和真实值之间的接近程度。一个 12 位 ADC 如果噪声很大,有效位数(ENOB)可能只有 9 位甚至更低。

ENOB 的计算公式是:ENOB = (SNR - 1.76) / 6.02。其中 SNR 是信噪比,单位 dB。比如你测出来 SNR 是 68dB,那 ENOB 就是 (68-1.76)/6.02 ≈ 11 位。也就是说,虽然 ADC 标称 12 位,但实际有效精度只有 11 位。

要测 ENOB,最直接的方法是对一个稳定的直流电压连续采样,计算标准差,然后换算成有效位数。标准差越小,ENOB 越高。我通常会在项目初期做这个测试,作为评估前端电路和采样配置是否合理的基准。

3.2 参考电压噪声与电源纹波对精度的影响

ADC 的转换结果本质上是输入电压和参考电压的比值。参考电压不稳,转换结果必然不稳。很多 MCU 的参考电压直接取自电源,如果电源纹波有 50mV,在 3.3V 参考下就相当于 1.5% 的误差,12 位 ADC 里就是 60 多个 LSB 的波动。

所以对精度有要求的项目,一定要用独立的参考电压芯片,比如 REF3033、LM4040 这类。同时参考电压引脚要加去耦电容,通常 0.1μF 和 10μF 并联,靠近引脚放置。

电源纹波的影响也不容忽视。ADC 的模拟电源引脚要和数字电源分开,中间用磁珠或电感隔离。模拟地和数字地也要分开走线,最后在一点汇合。这些措施看起来是老生常谈,但实际项目中因为电源处理不当导致精度不达标的情况太常见了。

3.3 温漂、失调与增益误差的校准思路

ADC 的误差分两类:失调误差和增益误差。失调误差是输入为零时输出不为零,增益误差是输入满量程时输出偏离满量程。这两类误差都可以通过两点校准来修正。

校准方法是:先输入一个已知的低电压 V1,读取 ADC 值 D1;再输入一个已知的高电压 V2,读取 ADC 值 D2。然后用线性拟合算出实际斜率和截距,后续采样值用这个拟合公式反算真实电压。

温漂则是另一回事。失调和增益会随温度变化,如果项目工作温度范围宽,比如工业级 -40 到 85℃,那校准最好在不同温度点做,或者选用低温漂的参考电压和前端器件。我在做户外设备时,通常会在常温校准一次,然后在高温和低温各测一组数据,看看漂移是否在可接受范围内。

4. 前端电路与通道切换:那些容易被忽略的细节

4.1 输入阻抗匹配与RC滤波的设计取舍

ADC 输入端的等效电路可以看作一个采样电容加一个开关。在采样瞬间,采样电容需要从外部电路吸取电荷。如果外部电路的输出阻抗太高,电荷补充不及时,采样电容上的电压就达不到输入电压。

所以前端电路的设计要保证在采样时间内,采样电容能充到足够精度。一般建议信号源内阻控制在几 kΩ 以内。如果信号源内阻确实很高,比如传感器输出阻抗几十 kΩ,那就需要在 ADC 前面加一个运放做缓冲。

RC 滤波是 ADC 前端常见的配置。R 和 C 的取值要兼顾滤波效果和采样时间。R 太大影响采样,C 太大影响响应速度。我通常取 R 在 1kΩ 以内,C 在 1nF 到 100nF 之间。截止频率要高于你关心的信号频率,但低于噪声频率。

4.2 多通道切换时的串扰与建立时间问题

多通道 ADC 在切换通道时,采样保持电容上还残留着上一个通道的电压。如果新通道的源阻抗较高,残留电荷来不及被新通道电压替换,就会产生串扰。表现就是通道 A 的读数会受到通道 B 的影响。

解决方法是:在通道切换后,先做一次丢弃采样,把第一次转换结果扔掉,从第二次开始取。或者在切换通道后增加采样时间,让采样电容有足够时间建立到新电压。STM32 的 ADC 可以通过设置不同的采样时间来优化这个问题,对高阻抗通道用更长的采样时间。

另外,如果两个通道的电压差异很大,比如一个测 0.1V,一个测 3.0V,串扰会更明显。这时候可以在两个通道之间插入一个接地通道做隔离,或者用外部多路复用器配合独立的缓冲运放。

4.3 定时器触发与 DMA 搬运的配合方式

用定时器触发 ADC 是实现精确等间隔采样的标准做法。定时器产生触发信号,ADC 收到触发后启动转换,转换完成后通过 DMA 把数据搬到内存。整个过程不需要 CPU 干预,采样间隔由定时器决定,精度远高于软件循环。

配置时要注意几点:定时器的触发输出要连接到 ADC 的外部触发源,不同系列的连接方式不同,查参考手册的触发映射表。DMA 要配置成循环模式,缓冲区大小要够大,避免数据覆盖。如果采样率很高,还要考虑 DMA 带宽和总线仲裁。

我在做音频采样时,用定时器触发 ADC,DMA 搬到双缓冲区,CPU 只处理半满和全满中断。这样即使采样率到 48kHz,CPU 占用率也很低。关键是缓冲区要开得够大,比如 256 或 512 个采样点,给 CPU 足够的处理时间。

5. 实测排查:从跳动读数到稳定波形的完整链路

5.1 先确认是信号问题还是采集问题

拿到一个跳动的 ADC 读数,第一步不是急着加滤波,而是先判断跳动来自信号本身还是采集链路。方法很简单:把 ADC 输入直接短接到地,看读数是否稳定。如果接地后读数仍然跳动,那问题在 ADC 配置或参考电压;如果接地后稳定,接上信号就跳,那问题在信号源或前端电路。

我遇到过好几次,客户说 ADC 不准,结果一查是传感器供电纹波太大,信号本身就在抖。这种情况下再怎么调 ADC 配置也没用,得先解决供电。

还有一种情况是接地方式不对。模拟地和数字地混在一起,数字开关噪声串到模拟部分。用示波器看 ADC 输入引脚,能看到明显的开关噪声尖峰。这时候要检查 PCB 布局,模拟部分和数字部分要分开,地平面要完整。

5.2 用示波器和 CCS 观察 ADC 采集波形

在 CCS 里查看 ADC 采集波形,最方便的方法是用图形化工具把 DMA 缓冲区里的数据画出来。CCS 的 Graph 功能可以实时显示数组内容,设置好采样点数和刷新率,就能看到波形。如果波形有明显的周期性毛刺,那多半是电源或地的问题;如果波形是随机噪声,那可能是前端电阻热噪声或 ADC 本身噪声。

示波器则用来观察 ADC 输入引脚的模拟波形。把探头打到输入引脚上,触发方式设为正常,看波形是否干净。如果示波器上就有噪声,那 ADC 读到的必然也有噪声。注意示波器探头本身会引入电容负载,对高阻抗节点测量时会影响电路,最好用低电容探头或缓冲后再测。

5.3 逐步定位:从参考电压到采样时间的排查顺序

我的排查顺序通常是这样的:先测参考电压,用高精度万用表量参考引脚,看是否稳定在标称值。然后测电源纹波,用示波器交流耦合看模拟电源引脚。接着检查采样时间配置,逐步增加采样时间看读数是否改善。最后检查前端 RC 和信号源阻抗。

这个顺序的逻辑是从最可能出问题、最容易检查的地方开始。参考电压和电源是基础,基础不稳后面都白搭。采样时间是配置问题,改起来最快。前端电路是硬件问题,改起来最麻烦,放最后。

如果以上都排查了还是不行,那就要考虑 ADC 本身是否损坏,或者芯片是否为正品。我确实遇到过用拆机芯片导致 ADC 精度严重下降的情况,换正品后立刻恢复正常。

6. 几个实战中的经验与避坑点

关于 ADC 校准,很多人只在初始化时校准一次,但芯片温度变化后失调会漂移。我的做法是在每次重要测量前都执行一次校准,STM32 的 HAL 库里有HAL_ADCEx_Calibration_Start()函数,调用一次耗时很短,但对精度提升明显。

关于 DMA 缓冲区,一定要用volatile修饰,否则编译器优化后可能读不到最新数据。另外缓冲区的数据类型要和 ADC 分辨率匹配,12 位 ADC 用uint16_t就够了,但如果做软件过采样累加,累加变量要用uint32_t。

关于多通道采样,如果通道之间电压差异大,建议在切换后丢弃第一次采样。STM32 的规则组序列里没法直接标记丢弃,但可以在 DMA 缓冲区里预留位置,软件读取时跳过。或者用注入组配合规则组,注入组做快速采样,规则组做常规采样。

关于精度评估,不要只看数据手册上的 ENOB 典型值,一定要在自己的板子上实测。实测方法很简单:短接输入到地,采 1000 个点,算标准差,换算成 LSB,再算 ENOB。这个数据才是你系统真实能达到的精度上限。

最后说一个关于 EMA 的细节。EMA 的初始值很关键,如果初始值离真实值很远,前几十个采样点都会受影响。我的做法是用第一次采样值直接作为 EMA 的初始值,或者先采 10 个点做算术平均作为初始值,这样收敛更快。

这些经验都是我在实际项目中踩过坑之后总结出来的,希望对你在 ADC 采集这条路上有所帮助。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进