
做嵌入式这些年我越来越觉得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这种值。人的感官是模拟的计算机的运算是数字的所以两者之间必须有个翻译官。ADCAnalog-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固定12ADC时钟21MHz那么一次转换耗时(8412)/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时钟21MHz12位分辨率采样周期84。转换周期 84采样 12固定 96个ADC时钟周期转换时间 96/21MHz ≈ 4.57us理论最高采样率约219kSPS。如果你要采40kHz的信号这个采样率绰绰有余。反过来如果你需要200kSPS采样率就得把采样周期缩到48得到(4812)/21MHz ≈ 2.86us约350kSPS看起来够了但此时采样电容充电时间变短源阻抗必须足够低。这就是参数联动改了采样率输入阻抗要求就变了。7. 输入信号进ADC之前调理电路、电源噪声和PCB布局的实战要点ADC的精度是前端决定上限后端决定下限。信号进入ADC引脚之前经历什么基本决定了你最终能拿到多少有效位数。这里分享几个最实用的硬件设计要点。第一RC低通滤波要会设计。ADC引脚前加一个RC低通一方面滤掉高频噪声另一方面起到抗混叠的作用。截止频率按1/(2πRC)算。比如信号带宽100kHz采样率500kHzRC截止频率取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慢慢升到满量程记录每个电压下的采样值画出一条输入输出曲线。这条曲线比任何仿真都能说明问题。偏了、折了、台阶了一眼就看出来。很多玄学问题画出来就不玄了。