
做单片机音频采集项目的人多少都遇到过这种怪事明明麦克风型号选对了放大电路也按手册搭了程序里的ADC配置一段一行检查过可DMA搬回来的数据就是不对劲——波形幅度对不上、小信号发飘高频部分像是蒙了一层纱。我之前做一个语音唤醒节点时就被这个问题折腾了两天最后一路排查问题居然出在ADC最前端那个不起眼的采样保持电路SHA上。把整条链路拆开看之后才发现从麦克风到单片机真正决定采集质量的不是你代码里写了多少次平均滤波而是模拟信号链和ADC内部SHA之间怎么配合。这篇文章就把声音采集模块的完整链路摊开讲重点说清楚SHA到底在干嘛、它的参数怎么影响录音结果以及实战中怎么配置采样才能真正发挥单片机内置ADC的潜力。适合正在做音频采集、传感器精密测量的单片机开发者和电子爱好者参考。1. 信号进ADC之前先过这几道关很多人拿起单片机就往ADC引脚怼信号以为ADC读到的就是真实电压。实际上从麦克风振膜到转换结果之间隔着一整条模拟链路任何一环没处理好后面谈SHA都是白搭。我先按信号流向把这条链路理一遍。1.1 换能器麦克风自身就是个高阻源麦克风是整个系统的源头。常见的有驻极体麦克风和MEMS麦克风消费级模块里基本都是这两种。驻极体麦克风里面有一个带永久电荷的振膜声音让振膜振动改变与背极板之间的距离从而改变电容再把电容变化转成电压变化。它本身是个电容式器件需要外部给一个偏置电压才能工作通常通过一个2.2kΩ到10kΩ的电阻从电源取电。这个电阻同时决定了它的输出阻抗——你可以粗略认为信号源的输出阻抗就是这个偏置电阻量级也就是几千欧姆。MEMS麦克风内部集成了一颗专用ASIC输出阻抗要低得多通常只有几百欧姆信号质量更好但价格也更高。这里有个关键点ADC的采样保持电路是需要从信号源吸取电荷的信号源阻抗越高给采样电容充电就越慢后面会详细算。麦克风作为第一级它的输出阻抗直接决定了你后面要不要加缓冲器。1.2 前置放大与电平规划为什么不能直接怼ADC收音的模拟信号通常只有毫伏级别。以典型驻极体麦克风为例灵敏度大约-40dBV/Pa也就是说在1Pa声压约94dB SPL下输出大约10mV。人正常说话到麦克风10cm远的地方声压大约0.1Pa到1Pa对应输出1mV到10mV。这个幅度直接进单片机ADC在小信号输入时通常只有几十到几百LSB量化噪声占比一下就上来了信号细节基本被吃掉。所以必须加一级前置放大把信号放大到接近ADC满量程才能充分利用ADC的12位甚至16位分辨率。放大完之后还有一个电平匹配问题。单片机ADC通常只能采0到VREF的正电压而麦克风的输出是交流信号正负都有。这时候就要把信号抬一个直流偏置到VREF的一半。最简单的方法是用两级运算放大器第一级做同相放大第二级做偏置叠加或者用一颗支持虚拟地的运放配合分压电阻来设置静态工作点。目标是把信号中心放在1.65V如果VREF是3.3V幅度峰峰值控制在1V到2V之间留出余量防止削波。1.3 抗混叠滤波奈奎斯特不是开玩笑麦克风出来的信号不是只有人声环境中还有各种高频噪声、数字电路的辐射干扰。如果这些高频分量超过采样率的一半也就是奈奎斯特频率就会被ADC折叠回低频带和真实信号混在一起没法分开。这就是混叠现象。我在一个项目里用8kHz采样率采集语音结果频谱里总是出现一个莫名其妙的低频成分查了很久才发现是附近一个开关电源的高频噪声被混叠进来的。后来在运放后面加了一级RC低通滤波器把截止频率放在3.4kHz左右语音带宽上限问题就消失了。所以采样之前先想清楚信号的最高频率是多少然后决定滤波器参数这一步一定不能省。2. SHA到底在干嘛把流动的电压冻结下来前面讲的都是模拟信号链真正的核心问题来了ADC内部的采样保持电路SHASample-and-Hold Amplifier也常写成T/HTrack-and-Hold到底做了什么一句话概括它负责在ADC开始转换的那一瞬间把正在连续变化的模拟电压冻结成一个稳定的值让后面的量化器有时间慢慢测量。2.1 一个模拟开关加一个电容就是SHA的核心SHA的结构看起来特别简单就是一个开关串联一个电容开关另一端接输入信号。工作分两个阶段采样阶段也叫跟踪阶段开关闭合电容电压跟随输入信号变化。这时候电容相当于信号源的负载信号源通过自身的内阻给电容充电。保持阶段开关断开电容上的电压就停在了断开前那一瞬间的值。后面的比较器、DAC逐次逼近逻辑全靠这个冻结的电压来完成转换。可以类比成拍照你按快门前取景器里画面一直在动你没法测量它的细节按下快门的瞬间画面被定格在一张照片上之后你可以慢慢研究照片里每一个像素的亮度。SHA就是ADC的那只快门。2.2 为什么ADC内部离不了SHA对于逐次逼近型ADCSAR ADC它转换一个N位值需要进行N次比较。每一次比较都需要把输入电压和一个参考电压做差然后决定当前位是0还是1。如果输入电压在比较过程中一直在变会出什么问题比如你正要把输入和1/2 VREF比较结果输入从0.8V漂到了0.9V刚才刚判定的最高位可能就错了连带后面所有位全错。也就是说SAR ADC的整个逐次逼近过程要求输入电压在转换期间基本保持不变。SHA的作用就是在转换开始前把输入定格保证整个转换过程中比较器看到的电压都是同一个值。没有SHASAR ADC基本没法正常工作。对于流水线ADC、积分型ADC等其他架构SHA同样是核心模块只是放的位置和工作时序略有差别。可以说SHA是所有依靠电压进行量化的ADC都绕不开的前端电路。2.3 从分立元件到电荷再分配现代SAR ADC的SHA长什么样早期的SHA确实就是一颗外置的运放加开关和电容体积大、精度还受分立元件匹配度限制。现在芯片内置的SAR ADC几乎都采用电荷再分配结构SHA和数模转换用的电容阵列合二为一。在采样阶段电容阵列全部接地AQMD?写的时候换成开关即可输入电压接到电容阵列上极板给整个阵列充上对应的电荷。在转换阶段把所有电容下极板从输入信号切换到一个公共参考电压这样上极板电压就等于输入电压和参考电压的某个加权组合每次比较一位电容阵列里的电荷会根据比较结果重新分配。整个过程里SHA功能和DAC功能共用同一组电容节省面积匹配度也比分立方案高得多。但请记住一点不管内部结构多先进它对外表现出的特性仍然可以抽象成一个开关接一个电容。所以下面要讲的阻抗匹配、采样时间、电荷注入等问题对各种架构的ADC都成立。3. 决定声音采集质量的SHA参数不是精度位数的锅经常有人问为什么我的12位ADC采音频音频效果还不如别人的10位DAC播放出来的好这锅多半不在分辨率位数上而在于你没有理解SHA的这几个参数。这几个参数几乎决定了信号能多准确地送到量化器嘴边。3.1 采集时间不够电容没充满就开始转换采样电容C_sample通常只有几皮法到十几皮法看起来很小但驱动它需要时间充电。充电过程是一个典型的RC充电时间常数τ R_source × C_sample。T表示采样保持电路在这个时间内能够把电容电压充到与输入电压的误差小于某个范围。工程上一般希望给到至少5个τ到10个τ的时间才能让采样电容上的电压足够接近输入电压。如果你的信号源阻抗R_source是10kΩC_sample是8pF那么一个τ就是80ns5个τ就是400ns。听起来好像很短但别忘了你用的单片机ADC时钟可能只有几兆赫兹到十几兆赫兹一个ADC时钟周期可能就是50ns到100ns。如果采样时间配置成1.5个ADC时钟周期总采样时间可能只有75ns远远不够。STM32的ADC采样时间是通过寄存器配置的可以选1.5、7.5、28.5、41.5甚至更多个ADC时钟周期。采集音频这种高阻源信号时老老实实把采样时间拉长这是性价比最高的改动之一。3.2 信号源阻抗是SHA的死敌这里要特别强调信号源阻抗的问题。单片机ADC的数据手册通常会给出一个最大推荐源阻抗值比如STM32F103的ADC在12位模式下要求信号源阻抗不超过10kΩ左右才能保证精度。这是根据采样电容、采样时间和目标精度反推出来的。如果信号源阻抗太高采样时间不够电容充足电ADC读出来的值会比实际电压低而且信号频率越高衰减越严重。更麻烦的是这个误差和信号幅度不是线性关系没法用一个固定系数去校准。麦克风的输出阻抗正好落在偏高的区域所以实战中不要直接把它接到单片机ADC引脚上中间至少要加一个运放组成的电压跟随器利用运放的低输出阻抗通常小于100Ω去驱动ADC的采样电容。这一步在很多入门教程里被跳过了结果就是数据乱飘还以为是单片机坏了。3.3 孔径抖动、电荷注入与droop三个家伙在背后捣乱除了采样时间SHA还有三个隐形误差源音频采集时可能感受不明显但对精度要求高的传感器测量场景会非常明显。第一个是孔径抖动aperture jitter。它指的是采样开关实际断开时刻的随机波动量。理想情况下开关应该在精确的时刻断开但实际上每一次断开的时刻都会有一点点随机偏差。如果输入信号变化很快那么断开时刻的偏差就会导致采到错误的电压。对于音频信号最高几十kHz孔径抖动的影响通常微乎其微但如果单片机旁边有高频数字电路抖动和干扰耦合进来就会表现为采样噪声恶化。第二个是电荷注入。模拟开关断开时控制信号会通过寄生电容把一部分电荷注入到保持电容上导致保持电压出现一个微小的偏移。这个偏移通常和输入电压有非线性关系没法简单校准掉。在低电压、小信号场景下电荷注入可能是主要的误差来源。第三个是droop也就是保持电容的漏电。保持模式下电容上储存的电荷会通过寄生电阻慢慢漏掉电压随之下降。SAR ADC转换时间通常在微秒级不太明显但如果你用外部SHA给一个慢速ADC供数据转换时间长达毫秒级漏电影响就必须考虑。4. 从麦克风到单片机的完整采样设计以语音唤醒节点为例理论讲了这么多还是用一个具体项目串一遍。我之前做一个语音唤醒节点要求实时采集语音信号进行简单的关键字识别。主控选了一颗带12位SAR ADC的STM32G4麦克风是驻极体模块。整个设计可以分成三步参数估算、前端电路、MCU配置。4.1 项目需求与参数推算先定采样率。语音唤醒只需要识别窄带语音最高频率取4kHz按奈奎斯特定理采样率至少8kHz。为了留出滤波余量实际用16kHz采样。再定增益。选一颗灵敏度约-40dBV/Pa的驻极体麦克风正常说话时输出约5mV峰值。ADC参考电压是3.3V分辨率12位满量程对应LSB大约0.806mV3.3V/4096。要让5mV信号在ADC输出端有可辨识的幅度至少要放大100倍放大后变成500mV峰值占满量程约15%。考虑到人说话音量波动很大增益选50倍更稳妥给大声呐喊留出裕量。最后估算采样时间长度。ADC时钟选用PCLK的1/4STM32G4的PCLK可以到170MHz分频后ADC时钟约42.5MHz但实际建议把ADC时钟控制在17.5MHz左右以保证精度。在17.5MHz下一个ADC时钟周期约57ns。配置采样时间47.5个时钟周期采样时间约2.71µs再加上12.5个周期的转换时间单次总耗时约3.43µs。16kHz采样率下每个采样间隔是62.5µs完全来得及。4.2 实际前置电路与PCB走线前置电路我用了两片单运放。第一级是反相放大把麦克风信号放大约50倍反相结构的好处是可以把麦克风偏置电压隔在直流外只用交流信号做反馈。第二级是一个加法器把放大后的信号抬到1.65V偏置同时做一级低通滤波。关键元件取值输入耦合电容C1 1µF与第一级输入电阻R1 10kΩ形成16Hz的高通滤波防止低频隆隆声。反馈电阻R2 500kΩ与R1的比例决定增益50倍同时并联一个22pF电容截止频率约15kHz把超声频段压掉。第二级放大器同相输入端用两个10kΩ电阻分压出1.65V反相输入端接隔直电容后的信号和反馈网络。PCB走线也要注意麦克风偏置电阻要靠近麦克风放置运放供电引脚就近加0.1µF去耦电容ADC输入引脚和运放输出之间走线尽可能短。这些细节直接影响ADC采出来的底噪。4.3 MCU内置ADC的配置代码与DMA搬运配置时用到了STM32的HAL库核心是把采样时间拉到最长并开启ADC自校准然后通过DMA把结果搬到内存。关键代码如下ADC_HandleTypeDef hadc1; hadc1.Instance ADC1; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; hadc1.Init.LowPowerAutoWait DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.NbrOfConversion 1; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; HAL_ADC_Init(hadc1); ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLINGTIME_47CYCLES_5; // 关键拉长采样时间 sConfig.SingleDiff ADC_SINGLE_ENDED; HAL_ADC_ConfigChannel(hadc1, sConfig);每次采集前记得先做校准HAL_ADCEx_Calibration_Start(hadc1, ADC_SINGLE_ENDED);启动DMA传输后数据存在缓冲区里主循环里用双缓冲做半满/全满中断交替处理数据。DMA配置本身不复杂真正需要盯住的是ADC时钟分频和采样周期的匹配关系。很多数据异常直接改采样周期就能解决。5. 实测中的三个坑排查思路比结论更重要配置完成后实测阶段照样会踩坑。下面这几个问题我都实际遇到并排过最关键的是把排查思路捋清楚不要一看到数据不对就直接怀疑芯片。5.1 坑一直接接麦克风采集数据抖动到崩溃有一次我为了图省事跳过前置放大把驻极体麦克风模块的输出直接接到了ADC引脚。结果DMA采集到的数据在几百个LSB范围内乱跳完全看不出声音波形。当时第一反应是代码配置问题反复改了采样周期、开启了软件平均效果都不明显。后来用示波器量ADC引脚发现信号本身就有很大的衰减和噪声叠加。原因说白了就是前面讲的阻抗匹配问题。驻极体麦克风模块内阻好几千欧姆而ADC采样电容充电需要时间source电阻太大采样阶段电容根本充不满再加上引线拾取噪声数据自然乱飞。解决办法是在麦克风和ADC之间加了一级运放跟随器输出阻抗压到几十欧姆问题立刻消失。排查这类问题最快的方式是用示波器直接看ADC引脚的波形对比信号源输出和ADC采样点位置的差异。如果ADC引脚波形和源波形形状一致才需要考虑代码侧的问题否则就先解决模拟链路。5.2 坑二切换ADC通道后第一个数据点总是错的另一个项目里我用ADC轮流采集两路信号结果每次切换通道后的第一个数据都和后面明显不同像是一个跳变点。仔细看时序发现ADC内部只有一个采样保持电路通道切换后保持电容上还残留着上一个通道的电荷。虽然下一次采样会重新充电但如果采样时间不足残留电荷没有完全放完就会导致第一个转换结果不准。解决办法有两个一是切换通道后丢弃第一次转换结果用第二次的数据二是在切换通道后插入一个延时给内部模拟电路充分稳定时间。我实际用了丢弃第一个转换结果的方案额外开销几乎为零数据就正常了。5.3 坑三高频段噪声突然抬升FFT底噪变高做完FFT频谱分析后发现高频端底噪整体抬升了几个dB看起来像是SHA的孔径抖动造成的。排查过程是先用信号发生器给一个干净的1kHz正弦波FFT后看信噪比发现高频底噪偏高。然后我把输入信号短接只量ADC自身噪声发现底噪正常。问题锁定在信号路径上而不是ADC本身。进一步测试发现放大器输出端的RC低通滤波被我为了省元件去掉了高频噪声直接进了ADC发生了混叠。加上一个10kΩ和1nF组成的低通滤波器截至频率约16kHz高频底噪立刻降了下来。这个坑提醒我孔径抖动等ADC自身误差通常只有几ps的抖动引发的噪声远不如一个简单的抗混叠滤波缺失来得大排查时应先处理外部因素。后来我总结了一条经验凡是做音频采集前置的低通滤波一定不能省千万别以为ADC采样率选高一点就万事大吉。对外部噪声的抑制能力其实是由你硬件电路的滤波特性决定的不是由采样率决定的。再分享一个小技巧。如果ADC支持过采样对静态或低频信号可以开过采样加平均来提升有效分辨率。但音频信号是变化较快的动态信号过采样平均反而会模糊波形细节不建议这么做。测麦克风这类动态信号时老老实实靠模拟链路保证前端信号质量比在数字域里做任何补救都有效。