嵌入式ADC从原理到实战:采样、滤波与校准的完整信号链
2026/9/12 7:10:08 网站建设 项目流程

做嵌入式这些年,我越来越觉得ADC是个很微妙的外设。它不像GPIO那样一拉就完事,也不像UART那样发几个字节就收工。ADC卡在模拟世界和数字世界的交界处,一边是连续变化的电压,一边是CPU认识的0和1,任何一端出了点小问题,你读到的数据就开始"表演"。

有一次我在调试一块STM32F407的板子,ADC采集一个电位器的分压,万用表测出来稳稳的1.65V,可单片机里读到的数值却一直在2680到2742之间跳。我以为是采样时间太短,把采样周期调到最大,又加了滑动滤波,情况只能说好转,但依然不干净。最后查了半天,发现是VREF+引脚旁那粒0.1uF去耦电容被我焊掉了。就少一个电容,ADC数据就能飘成这样。

这事给我的启发是:ADC这玩意,知识面横跨电路、信号处理、嵌入式外设配置、软件算法,任何一个环节有盲区,都会在最终的数据上露出马脚。这篇文章我想顺着这条线把ADC从原理到实操完整捋一遍,适合刚接触嵌入式的学生、从纯软件转硬件的开发者,以及那些"能读到数但总觉得哪里不对"的工程师。读完你至少能搞明白三件事:模拟信号到底怎么变成数字的,STM32这类MCU里面的ADC外设是怎么工作的,以及遇到采样值不准、漂移、跳变时该往哪个方向查。

1. 一次"读数乱跳"的排查,暴露了ADC背后的系统性工程

先把这个故事讲完。那块板子是一个简易的电压采集节点,电位器分压后直接接到STM32F407的PA1引脚,配置成模拟输入。采样周期我一开始用的是默认的84周期,ADC时钟21MHz,算下来转换时间大约4.57us。从理论上看,采一个缓变的直流电位器信号,这个配置绰绰有余。

但是读数就是不稳。后来我拿示波器去点PA1引脚,发现波形相当干净,没有明显纹波。这就奇怪了——输入信号是干净的,ADC配置看起来也没问题,为什么数据在跳?

我逐个排查了几个方向:

  • 电源纹波:用示波器看3.3V,有大约30mV的高频毛刺,来自板上的DC-DC。
  • VREF+供电质量:VREF+直接连到了3.3V,而那30mV毛刺一点没衰减地串了进来。
  • 去耦电容:检查原理图时发现,设计上VDDA和VREF+附近各有一粒0.1uF电容,但板子上这两粒都虚焊了。

把电容补上之后,读数立刻稳定下来。这个事让我意识到,ADC采样值跳动时,很多人第一反应就是改软件、加滤波,很少有人先去怀疑硬件链路。但ADC的精度天花板从信号进入引脚那一刻就定下来了,软件只能在这个天花板之下做文章。

如果你也遇到类似问题,我建议先用排除法锁定问题域。看输入信号本身干不干净,看参考电压干不干净,看电源纹波有多大,最后才去动软件配置。硬件上没问题,软件滤波才有意义。顺序反了,你就是在用一个漂亮算法去掩盖一个硬件缺陷。

2. 模拟信号与数字信号:为什么ADC是绕不开的"翻译官"

要搞懂ADC,先得清楚它到底在翻译什么。模拟信号和数字信号的区别,本质上就是"连续"和"离散"的区别。

模拟信号的典型特征是:在时间和幅度上都是连续的。比如一个温度传感器输出的电压,随着温度从25度慢慢升到26度,它可能经过0.625V、0.6251V、0.6252V……任何一个中间值。你没法说它"只取某些固定值"。声音、压力、光照强度,这些物理量经过传感器转换后,输出基本都是模拟信号。

数字信号则相反,它在时间上是离散的,幅度上也是离散的。MCU的GPIO引脚上只有高电平和低电平两种状态,对应的就是逻辑1和逻辑0。而在MCU内部,一切数据最终都表示为一定位数的二进制数,比如一个uint16_t变量,它的取值范围只能是0到65535之间的整数,不可能出现65535.5这种值。

人的感官是模拟的,计算机的运算是数字的,所以两者之间必须有个"翻译官"。ADC(Analog-to-Digital Converter)干的活就是从模拟信号里取出一个个样本,再把每个样本的幅度量化成二进制码。

这里有个常见误区:有人觉得,那我不需要ADC,直接用比较器把电压跟某个阈值比一下,大于就是1,小于就是0,不就把模拟信号变成数字信号了吗?

我跟你讲,这差了十万八千里。比较器只能告诉你"输入电压比阈值高还是低",输出只有1个bit的信息。而一个12位的ADC能区分4096个电压等级——同样是3.3V满量程,12位ADC能分辨约0.806mV的变化。你要采集一个变化范围很宽的传感器信号,比较器根本没法还原出"信号到底多大"。

打个比方:模拟信号像一杯水的水位,是连续变化的。数字信号像刻度尺上的读数。如果你只有一把只能判断"水满没满过中间线"的尺子,那你能得到的唯一信息就是"高于中间线或者低于中间线"。ADC则相当于一把有4096条刻度的尺子,能精确说出水位在哪条刻度附近。这个比喻虽然简单,但它揭示了核心:ADC做的事情就是"测量+编码"。

为什么MCU内部非要ADC不可?因为CPU本质是一个做逻辑运算的数字器件,它无法直接理解电压的"大小",只能理解"是0还是1"。想让它知道外部电压是0.625V还是0.630V,就得把这些电压映射成数字。系统架构上,传感器→信号调理→ADC→MCU→执行器,这一整条链路就是嵌入式系统感知物理世界的基本骨架。

3. 采样、量化、编码:从连续波形到一串二进制数的三步

ADC的工作过程可以拆成三个步骤:采样、量化、编码。这三个词看起来学术味重,其实每一环都有非常具体的工程讲究。

采样是按一定时间间隔去读取连续信号的瞬时值。比如每0.5毫秒读一次,得到的是一串在时间上离散的电压快照。采样的频率决定了你能还原多快的信号。这里有一条绕不开的铁律:奈奎斯特采样定理。它说采样率至少要是信号最高频率的两倍,才能无失真地还原信号。两倍是理论下限,实际工程里我一般至少给5到10倍余量。举个例子,你要采集一个频率为1kHz的正弦波,每秒采2000次只是理论门槛,实际我会采到10kSPS以上,好让波形还原得更真实。

混叠是采样时最需要防的一个坑。如果采样率不够,高频信号会"伪装"成低频信号混进你的数据里,而且一旦混进来就再也分不开了。网上有个经典视频:车轮在摄像机下看起来向后反转,那就是时间上的混叠。做嵌入式采样时,如果信号里混着高频噪声,而你采样率又不够,噪声会被折叠到低频段,变成你看到的那种缓慢飘动的假信号。

量化是把连续的电压值映射到有限个离散电平上。一个12位ADC把满量程电压分成4096档,每一档对应的电压叫一个LSB(最低有效位)。满量程3.3V时,一个LSB大约0.806mV。量化过程中必然产生量化误差,因为真实电压几乎不可能刚好落在某个量化台阶上,最多会有正负半个LSB的误差。这个误差是ADC天生带来的,不能消除,只能靠增加位数来减小。

编码是把量化后的档位转换成二进制数。如果你用的是12位ADC,那么编码输出就是0到4095之间的整数。这个整数丢给CPU后,CPU再根据参考电压反向算出实际电压。

这三步是任何ADC都逃不掉的基本逻辑。区别在于不同架构用不同的方式来执行这三步,速度和精度差别很大。

4. 逐次逼近型与Sigma-Delta:两种主流ADC架构的工作逻辑

MCU内置ADC绝大多数是逐次逼近型(SAR, Successive Approximation Register),而高精度测量场景里Sigma-Delta架构则更常见。搞清楚这两种架构,你在选型时就不容易选错。

SAR ADC的原理可以拿天平称重来类比。假设你要称一个物体的重量,手头有一堆砝码,规格是8kg、4kg、2kg、1kg、0.5kg……你从最大的开始试:放上8kg,物体还重,说明不够8kg,拿走;放4kg,超了,拿去;放2kg,不够,留着;放1kg,不够,留着;放0.5kg,超了,拿去。一路试下来,你把留下的砝码加起来,就是物体的近似重量。

SAR ADC的内核就是一个比较器、一个内部DAC(数字模拟转换器)和一串逼近逻辑。它先把输入电压跟参考电压的一半比较,决定最高位是1还是0;再根据这个结果,把比较电压调整到参考电压的1/4或3/4,继续决定下一位。12位分辨率就意味着要比较12次,用12个ADC时钟周期完成一次转换。这就是"逐次逼近"名字的由来。

SAR ADC的优势是转换速度快、功耗低、结构简单,非常适合做中等精度、中等速度的通用采样。STM32、GD32这些MCU内部集成的ADC基本都是这个架构。

Sigma-Delta ADC的玩法完全不同。它用很高的采样率(远高于奈奎斯特频率)去采样,内部做噪声整形,把量化噪声推到高频段,再用数字滤波器把高频噪声滤掉。这样能用比较低的硬件精度换来很高的有效分辨率。常见的24位Sigma-Delta ADC能轻松做到20位以上的有效分辨率,但它们转换速度慢,一次转换可能需要几毫秒甚至更久。

所以这两种架构的分工很清楚:SAR适合做实时性要求高的采样,比如电机控制里的相电流、电压检测;Sigma-Delta适合做精度优先的测量,比如称重传感器、热电偶测温。我见过不少新人拿MCU内置12位ADC去称重,精度怎么都不够,后来换成外置24位Sigma-Delta芯片,问题立刻解决——这不是软件算法的问题,是架构就选错了。

下表可以帮你快速判断该用哪种:

对比项SAR ADCSigma-Delta ADC
转换速度快(几百kSPS到几MSPS)慢(几SPS到几百kSPS)
分辨率常见8~16位常见16~24位
功耗较低相对较高
适用场景实时控制、波形采集传感器精密测量、音频
典型形态MCU内置、外置SPI芯片外置高精度芯片

5. 嵌入式ADC外设的完整链路:从引脚电平到内存数据

搞懂了架构原理,我们再回到嵌入式MCU本身,看看ADC外设的实际工作链路。以STM32为例,一条完整的ADC数据通路大概长这样:模拟输入引脚 → 模拟多路选择器 → 采样保持电路 → 逐次逼近转换内核 → 数据寄存器 →(可选DMA)→ 内存。

信号从引脚进来的第一站是多路选择器。ADC内核本身只有一套转换电路,但MCU通常有多达十几个模拟输入通道,所以通过多路选择器在不同时刻切到不同通道上去转换。这就是为什么你可以在扫描模式下一次配置多个通道,它们轮流被转换。

接着是采样保持电路。这是很容易被忽视但极其重要的一环。STM32的ADC内部有一个采样电容,采样时开关闭合,电容被充电到输入电压;开关断开后,电容上保持的电压就是转换时的实际输入值。如果采样时间太短,电容没充满电,转换出来的值就会比实际电压低。如果你输入源阻抗很高(比如接了一个10kΩ的传感器直接进ADC),充电就更慢,对采样时间的要求就更高。

转换完成后,结果被锁存到数据寄存器。单通道情况下,你读这个寄存器的值就行。多通道扫描模式下,如果不用DMA,你每转换完一个通道都要及时把结果读走,否则下一个通道的结果会覆盖掉它。用DMA的话,硬件会自动把每次转换结果搬运到你自己定义的内存缓冲区里,CPU全程不参与,效率高很多。

触发方式也值得一提。ADC可以软件触发,就是一调用启动函数就开始转换,但这种方式在实时控制场景里不够精准。更专业的做法是用定时器触发,比如配置TIM1的输出比较事件触发ADC,这样PWM和采样能做到严格同步。电机控制里电流采样的时机非常讲究,必须在PWM的特定时刻进行,这时候软件触发根本无法保证时序,必须靠硬件触发。

还有一点我想特别提醒:ADC的时钟是有上限的。STM32F4系列通常由APB2总线时钟分频得到ADC时钟,APB2是84MHz,一般分4得到21MHz。采样周期数是可以配置的,比如3到480个周期。转换时间 = (采样周期数 + 12个固定周期) × ADC时钟周期。举个例子,采样周期配84,固定12,ADC时钟21MHz,那么一次转换耗时(84+12)/21MHz ≈ 4.57us,对应采样率大约219kSPS。很多新手想当然地把采样周期拉到最大以获得"最准确的采样",结果发现采样率根本不够用,这就是没理解这两者的联动关系。

6. 关键参数怎么配:分辨率、参考电压、采样周期与转换时间的联动关系

配置ADC时你会面对一堆参数:分辨率、参考电压、采样周期、转换模式、触发源。这些参数不是孤立存在的,它们互相制约。我把最常见、最容易踩坑的几个捋一遍。

分辨率。STM32F4的12位分辨率给了4096个台阶,大多数工业采集场景够用了。但你要明白,分辨率越高不代表越准——它只是把电压切得更细,如果参考电压本身有噪声、输入信号本身不干净,位数再高也白搭。有时候甚至要故意降低分辨率换速度,比如一些高速波形采集场景,8位分辨率配合高采样率反而更实用。

参考电压。满量程电压由VREF+引脚决定。很多开发板上VREF+直接接3.3V电源,这会导致一个问题:数字电源上的噪声会一路灌进ADC的量化结果。要求高的设计会把VREF+单独接到一个低噪声基准源上,比如REF3030这类3.0V基准芯片,几百微安功耗,输出噪声很低,能显著改善采样稳定性。我的经验是,哪怕只是多加一个0.1uF电容到VREF+引脚,都能感受到数据跳动幅度变小。

采样时间。前面说了,采样时间是给内部采样电容充电的时间。源阻抗越大,需要的采样时间越长。如果采样时间太短,你读到的值会偏低且非线性。这里有个经验值:对于普通信号源(输出阻抗几kΩ以内),采样周期设在几十个ADC时钟周期通常没问题;如果前端接了高阻传感器,要么加运放缓冲,要么把采样周期调到几百个周期。但采样时间长了,转换速度就下来了,所以要在精度和速度之间找平衡。

转换模式。单次转换适合低频点测,连续转换适合周期性监测,扫描模式适合多通道同时采集。项目里我常看到有人把所有通道都开了扫描,但实际只需要一两个,却忘了禁用其他通道,导致无关通道占用转换时间,降低有效采样率。注意不是让你不配置没用到的通道,而是别放在扫描通道列表里。

采样率计算的完整例子。假设ADC时钟21MHz,12位分辨率,采样周期84。转换周期 = 84采样 + 12固定 = 96个ADC时钟周期,转换时间 = 96/21MHz ≈ 4.57us,理论最高采样率约219kSPS。如果你要采40kHz的信号,这个采样率绰绰有余。反过来,如果你需要200kSPS采样率,就得把采样周期缩到48,得到(48+12)/21MHz ≈ 2.86us,约350kSPS,看起来够了,但此时采样电容充电时间变短,源阻抗必须足够低。这就是参数联动:改了采样率,输入阻抗要求就变了。

7. 输入信号进ADC之前:调理电路、电源噪声和PCB布局的实战要点

ADC的精度是"前端决定上限,后端决定下限"。信号进入ADC引脚之前经历什么,基本决定了你最终能拿到多少有效位数。这里分享几个最实用的硬件设计要点。

第一,RC低通滤波要会设计。ADC引脚前加一个RC低通,一方面滤掉高频噪声,另一方面起到抗混叠的作用。截止频率按1/(2πRC)算。比如信号带宽100kHz,采样率500kHz,RC截止频率取150kHz就合适,既保留信号,又衰减带外噪声。但RC还有个隐性作用:它给ADC采样电容一个电荷源。如果RC里的C太小,采样瞬间从电容上抽走的电荷得不到迅速补充,读数就会偏低。我通常会在靠近ADC引脚的地方放一个1nF到10nF的电容,相当于一个小水库。

第二,源阻抗高就加运放跟随。有些传感器输出阻抗高达几十千欧甚至更高,直接接ADC,采样电容充电时间常数(源阻抗×采样电容)会非常大,导致采样值严重偏低。解决方法是加一级电压跟随器(运放接成单位增益),把高阻抗信号转换成低阻抗输出,ADC的采样电容再从运放的输出端取电荷,瞬间就能充满。

第三,电源和参考电压的退耦。这是我在文章开头那个故事里踩的坑。VDDA和VREF+引脚要单独走线,尽量远离数字信号。每个模拟供电引脚旁边放0.1uF和1uF两个电容,紧贴引脚放置。更讲究一点,在模拟电源入口串一个磁珠或小电阻,组成一个低通滤波器,把数字域的高频噪声挡在外面。

第四,时钟抖动不可忽视。SAR ADC的采样时刻如果发生抖动,你采到的电压点就会偏离理想位置,尤其在信号斜率大的时候,微小的时间误差会变成明显的电压误差。所以高精度采样不建议用内部RC振荡器作为系统时钟源,用外部晶振或PLL锁出来的时钟会稳很多。这个提法在MCU系统设计里尤其关键:内部RC振荡器适合跑功能逻辑,不适合做精密ADC转换的时钟基准。

第五,PCB布局的三个要点。一是把模拟信号和数字信号分区域布线,模拟走线远离开关节点(比如PWM输出线、电感下方);二是模拟地要单点连接到数字地,避免数字地的大电流在模拟地上产生压差;三是采样走线尽量短,因为走线越长,等效电感越大,越容易捡到周围的高频干扰。

8. 拿到数据还没完:软件滤波、三点校准与漂移排查的完整思路

硬件链路都处理好了,数据还是会有一点残留噪声。这时候软件就该上了。但我不建议上来就上复杂算法,先分清你的噪声类型再选滤波器。

白噪声用滑动平均。激活ADC采集,数据是稳定的,但有小幅随机跳动,多半是白噪声主导。用滑动平均滤波能有效压低这类噪声。代价是响应变慢,输出数据会有几个采样周期的延迟。下面是一段典型的滑动平均实现:

#define FILTER_N 16 static uint16_t filter_buf[FILTER_N]; static uint8_t filter_index = 0; static uint32_t filter_sum = 0; uint16_t adc_sliding_average(uint16_t new_sample) { filter_sum -= filter_buf[filter_index]; filter_sum += new_sample; filter_buf[filter_index] = new_sample; filter_index = (filter_index + 1) % FILTER_N; return (uint16_t)(filter_sum / FILTER_N); }

偶发毛刺用中值滤波。如果数据偶尔蹦出几个明显的离群值,比如一瞬间跳到满量程,那不是白噪声,而是突发干扰。中值滤波专门对付这种毛刺,取最近N个值的中间值,毛刺就会被剔除。它的缺点是排序比较耗时间,N一般取3到7就够。

一阶低通滤波适合实时性要求高的场景。它每个采样点只做一次乘加运算,开销极小。alpha值越小,滤波越强,但延迟也越大。

float adc_lowpass(float new_sample, float alpha, float prev_out) { return alpha * new_sample + (1.0f - alpha) * prev_out; }

校准这块我多说两句。ADC有两个典型误差:零偏误差(输入0V时输出不为0)和增益误差(实际斜率跟理论斜率不一致)。解决方法是做校准。最简单的单点校准:输入一个已知电压,测出ADC码值,算出偏置。更严谨一点是两点校准:给两个已知电压V1和V2,测出码值D1和D2,然后线性拟合出电压和码值的关系。

三点校准在热词里被反复提到,是因为当ADC前端有非线性失真时,两点拟合成一条直线不够精确。三点校准的做法是:用标准源分别给三个已知电压V1、V2、V3,读回原始码值D1、D2、D3,用最小二乘法拟合出V = k×D + b的k和b。如果ADC非线性更严重,就分段线性拟合,每一段采用不同的k和b。工程里最常见的做法就是查表加插值,把测过的点存进一张表,未测到的点用线性插值补出来。

数据漂移排查的思路。漂移有两种:随机漂移和趋势性漂移。随机漂移大概率是噪声问题,按前面说的硬件排查思路走。趋势性漂移更头疼,通常是参考电压随温度漂移、电源电压缓慢变化、或者信号源本身在漂。我一般这样做:先把ADC输入短路到GND,看读数漂不漂。如果接地都漂,问题在ADC内部或参考电压,不在信号源。然后把输入接一个稳定的独立电源,比如用电池分压,再看读数准不准。如果这样都不漂了,那问题就回到原信号源那边。

如果你做的是高精度测量,还要留意焊接和接触电阻。我曾经排查过一个温漂问题,最后发现是采样线上的接插件氧化了,接触电阻随温度变化,导致输入到ADC的电压跟着漂。这类问题最隐蔽,也最容易让你怀疑这怀疑那,最后发现是物理接触的问题。

最后再分享一个我的切身体会:ADC调通了、数据稳定了之后,强烈建议做一次"全量程扫描验证"——用可调电源从0V慢慢升到满量程,记录每个电压下的采样值,画出一条输入输出曲线。这条曲线比任何仿真都能说明问题。偏了、折了、台阶了,一眼就看出来。很多玄学问题,画出来就不玄了。

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

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

立即咨询