尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32三重ADC交错采样与同步采样:从原理到CubeMX配置

STM32三重ADC交错采样与同步采样:从原理到CubeMX配置 很多人第一次听到“三重ADC交错采样”时心里第一个反应是STM32的12位ADC还能玩出花来尤其是做电机控制、数字电源或者数据采集的朋友面对自带ADC那12bit分辨率总觉得捉襟见肘要么外挂一颗几百块的Σ-Δ ADC要么咬着牙忍受噪声和量化误差。其实STM32内部的三套ADC如果用好很多场景根本不用外扩。这篇文章我以最常见的F103高容量型号为例把“stm32的三重模式adc转换是什么意思”这个小问题完整拆开从硬件原理、CubeMX配置、HAL库代码到实测数据和踩坑记录一次性说透。适合刚学会基础ADC采样、又想把自带资源榨干的工程师和竞赛党。1. 先把概念捋清楚三重模式、同步采样、交错采样到底指什么1.1 “三重模式”不是三个独立ADC各自干活而是一组联动开关STM32F103高容量型号内部有ADC1、ADC2、ADC3三个独立的逐次逼近型ADC它们共享一组“通用寄存器块”里面有个关键寄存器叫ADC_CCR它的DUALMOD[3:0]字段就是整个多ADC系统的总开关。把这个字段配置成对应值三个ADC就会进入“双重模式”或“三重模式”的联动状态。我见过很多新手把“三重模式”理解成“三个ADC并联谁闲着谁干活”这是不对的。更准确的说法是三个ADC被硬件绑定成一个小组由同一个触发信号驱动各自完成各自的转换任务然后通过硬件逻辑把结果按特定规则打包好。组成员之间的启动时刻要么完全相同同步要么严格错开交替/交错这取决于你选的子模式。F1系列三重模式下常见子模式有同步规则转换、同步注入转换、交替注入转换。其中同步规则转换三个ADC同时对一个或多个规则通道采样结果走规则数据寄存器适合配合DMA。同步注入转换三个ADC同时触发注入通道采样结果走注入数据寄存器适合单次精确读取。交替注入转换三个ADC轮流对注入通道采样等效提高注入组的转换频率。这里要泼一盆冷水F1的硬件“三重模式”并没有提供让你一键开启的“三ADC交替规则采样”功能。网上不少教程标题写着“三重ADC交错采样”实际做的是两件事中的一件——要么是三个ADC同步采样后再软件平均要么是用定时器人为制造相位差模拟出交错采样的效果。这两种思路对应完全不同的应用目标后面我会分开讲。1.2 同步和交错一个求“同一时刻”一个求“错开时刻”同步采样解决的是“同一时刻我只有一只手”的问题。三个ADC同时按下快门分别去抓三个不同通道的瞬时电压这样你拿到的是严格同一物理时刻的三相电流或三路线电压做Clark/Park变换时相位才是对的。三个ADC都怼到同一个输入上时同步采样给出的三份读数还能用来做平均降噪。交错采样解决的是“快门速度不够快”的问题。假设单个ADC最高采样率1Msps你嫌快门的反应速度不够那就在一个PWM周期内让ADC1在t0采样ADC2在tT/3采样ADC3在t2T/3采样。三个ADC轮流开工落在同一根信号线上的采样点密度就变成了原来的三倍等效采样率直接乘3。代价是你拿到的是三个不同时刻的快照信号如果在这三个时刻之间有变化你得多做一步插值或者把它当成一个高速样本流来处理。数字上很好记同步是“并行拿多路”交错是“串行错开拿一路”。两者都叫“三重ADC”但设计目标南辕北辙配置方法也完全不同。1.3 选型问题不是所有F103都能玩三重采样这是最容易被忽略的坑。STM32F103C8T6这种中容量芯片上只有ADC1和ADC2根本没有ADC3你在CubeMX里翻破天也找不到第三个ADC外设。真正带三个ADC的是高容量型号比如F103ZET6、F103ZGT6、F103VET6等后缀里的Z/V代表引脚数和封装Flash容量大于256KB的型号才有三套ADC。如果你手里的板子只是蓝色Pill那种C8T6最小系统板三重ADC方案直接不成立。这时候要么换ZET6核心板要么退一步做双重ADC同步采样——原理和本文几乎一样只是少了ADC3那路数据。另外如果你正在选型做新产品我更建议优先考虑F303、F334或者G4系列这几款对多重ADC交错采样的硬件支持更完整甚至带了硬件过采样器做高分辨率采集省心得多。2. 三个ADC采同一个信号分辨率提升背后的数学账2.1 平均降噪不是玄学N次平均噪声降√N倍要理解“12位ADC怎么越采越准”核心数学只有一句话如果每次采样混入的随机噪声互不相关那么N次采样的平均值其噪声标准差会降到原来的1/√N。假设单次采样的噪声能量方差是σ²N次平均后方差变成σ²/N。换算成信噪比SNR提升10log₁₀(N) dB。有效位数ENOB的提升公式是ΔENOB 0.5 × log₂(N)。代进去算一下三个ADC同步采同一信号后平均N3ΔENOB ≈ 0.79位也就是12位底子能摸到接近13位。如果三个ADC同时工作你再让它们连续触发16次凑出48个样本平均等效N48ΔENOB ≈ 2.79位理论上逼近14.8位。别高兴太早这个提升的前提是“噪声随机且互不相关”。ADC内部量化噪声、外部热噪声通常满足这个条件但参考电压纹波、电源工频干扰这类相关性强的噪声平均多少次都去不掉。2.2 信号上最好有一点点噪声否则平均救不了你这是个反直觉的点想让过采样平均效应生效输入信号里反而要有一点“噪声”。原因是12位ADC的量化误差在没有噪声时是确定的你重复采一个纯净直流电压每次拿到的都是同一个数字无论平均多少次量化误差纹丝不动。实际测试时我会故意在信号源上叠加一个幅度几个mV到十几mV的宽带噪声或者让信号源本身带一点输出噪声。这样每次采样的量化误差才会在多个量化台阶之间随机跳动平均才能把低于1个LSB的小信号“挖”出来。这个操作在数字信号处理里叫抖动注入dithering是过采样系统里最常见也最容易被忽略的技巧。2.3 数据打包规则CDR寄存器和JDR寄存器的身份热词里出现的“通用规则数据寄存器adc_cdr规则数据寄存器”指的就是三重模式同步规则转换时硬件把ADC1和ADC2的结果打包进同一个32位寄存器ADC_CDR。具体布局是低16位放ADC1的转换结果高16位放ADC2的转换结果两个12位数据各占用低12位高4位读出来是0。ADC3的结果不参与打包而是存放在它自己的ADC3_DR寄存器里。所以同步规则模式下一次同步转换会产生两个数据搬运请求一个由ADC1触发把32位的ADC_CDR读走另一个由ADC3触发把16位的ADC3_DR读走。这也是为什么CubeMX配置里ADC1和ADC3都要开DMA而ADC2不需要单独配置DMA通道的原因它的数据已经被ADC1那个DMA请求顺带捎出来了。注入模式下的数据组织则是另一种玩法三重注入同步转换时三个ADC的结果会出现在ADC1的注入数据寄存器序列中也就是JDR1、JDR2、JDR3依次对应ADC1、ADC2、ADC3的结果。具体到某个批次芯片上可能略有出入我的建议是实际把三路输入接成不同电压读一遍寄存器确认落位再写死解析逻辑不要盲信所有资料上的说法。3. 方法一同步规则模式双DMA入门最稳的三重采集3.1 CubeMX完整配置步骤我用STM32F103ZET6和HAL库做例打开CubeMX后按下面顺序配置。时钟树部分没什么特殊系统时钟给到72MHzAPB2保持72MHz然后到ADC配置界面把ADC时钟分频设为PCLK2/6这样ADCCLK是12MHz满足F1“ADC时钟不能超过14MHz”的硬约束。三个ADC的每个都要单独配置打开ADC1、ADC2、ADC3使能。三个ADC都添加一个Rank通道选Channel0也就是PA0引脚。采样时间在入门阶段建议选239.5周期。这个参数越长采样保持电容充电越充分内部采样误差越小代价是单次转换时间变长。按12MHz ADCCLK算239.5周期采样加12.5周期转换一次大约21微秒触发频率不能超过47kHz。关闭连续转换模式打开DMA连续请求。外部触发源选择Timer 3 Trigger Out event触发沿选上升沿。扫描模式保持默认或关闭因为每个ADC只转换一个通道。DMA部分是两个关键点。ADC1的DMA Settings里添加DMA请求方向PeripheralToMemory模式Circular数据宽度Word。ADC3同样添加DMA方向、模式、宽度都一样。ADC2不需要添加DMA。注意数据宽度必须跟你目标缓冲区类型匹配ADC_CDR是32位寄存器所以ADC1的DMA搬运宽度选WordADC3_DR虽然低12位有效但为了省事也选Word后面解析时再截断。TIM3配置为内部时钟预分频72-1得到1MHz计数频率自动重载值1000-1就得到1kHz更新事件再把Trigger Output设为Update Event。这样TIM3每毫秒产生一个TRGO脉冲三个ADC同时启动一轮转换。3.2 HAL库代码骨架启动顺序是第一个坑配置生成代码后主要逻辑长这样#define ADC_BUF_LEN 64 uint32_t adc_cdr_buf[ADC_BUF_LEN]; uint32_t adc3_buf[ADC_BUF_LEN]; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_ADC2_Init(); MX_ADC3_Init(); MX_TIM3_Init(); // 校准顺序放这里最稳 HAL_ADCEx_Calibration_Start(hadc1); HAL_ADCEx_Calibration_Start(hadc2); HAL_ADCEx_Calibration_Start(hadc3); // 先启动ADC再启动触发定时器 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_cdr_buf, ADC_BUF_LEN); HAL_ADC_Start_DMA(hadc3, (uint32_t *)adc3_buf, ADC_BUF_LEN); HAL_ADC_Start(hadc2); // ADC2已由ADC1的DMA搬运CDR但ADON必须使能 HAL_TIM_Base_Start(htim3); while (1) { // 主循环可以干别的数据在回调里处理 } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { // 说明一组ADC_CDR已经被DMA搬完 } }启动顺序里有个容易翻车的地方必须先把三个ADC启动好再启动TIM3。如果定时器先跑起来第一个触发脉冲到达时ADC1和ADC3的DMA还没就位这组数据直接丢掉DMA缓冲区里的第一个数据往往是错位的。每次HAL_ADC_Start_DMA之后最好加个短延时等内部状态稳定再HAL_TIM_Base_Start。另外有人问ADC2到底要不要单独调用HAL_ADC_Start。我测试下来如果只启动ADC1和ADC3ADC2的ADON位有时候没被真正拉起来结果就是CDR高16位一直是旧值或者0。保险起见对hadc2也调一次HAL_ADC_Start它不需要DMA也不会产生额外搬运动作。3.3 数据解析从两个缓冲区里拆出三个样本DMA把数据搬满64组后adc_cdr_buf里每个元素是32位打包数据adc3_buf里每个元素是ADC3的原始结果。解析代码很直接for (int i 0; i ADC_BUF_LEN; i) { uint16_t adc1_val adc_cdr_buf[i] 0xFFFF; uint16_t adc2_val (adc_cdr_buf[i] 16) 0xFFFF; uint16_t adc3_val adc3_buf[i] 0xFFFF; uint32_t sum adc1_val adc2_val adc3_val; uint16_t avg sum / 3; // 这里就可以把avg送串口、送显示或送控制环 }如果你一开始不确定CDR的位布局最稳妥的验证方法是PA0接1.0VPA1接2.0VPA2接3.0V三个ADC分别采不同电压打印出解析后的三个数值看哪个通道对应哪个数字。确认无误后再把三路输入并到一起做平均实验。这一步多花五分钟能省掉后面几小时的排查时间。4. 方法二TIM1三通道产生120°相移跑出真正的交错采样4.1 F1没有“三ADC交替规则模式”那只能自己造节奏前面说了F1三重模式的硬件子模式里没有“三ADC交替规则转换”这个档位。但STM32的设计妙在每个ADC的外部触发源可以独立选择而且触发源里恰好都包含了TIM1的CC1、CC2、CC3事件。利用这一点完全可以把三个ADC的采样时刻人为错开实现真正的交错采样。思路是这样的TIM1一个计数周期内CH1比较匹配事件、CH2比较匹配事件、CH3比较匹配事件分别出现在不同的计数值位置。例如周期是P我把三个通道的比较值分别设为P/3、2P/3和P-1那么一个周期内CNT依次撞上三个比较值产生三个错开的触发脉冲。ADC1吃CH1的脉冲ADC2吃CH2的脉冲ADC3吃CH3的脉冲三个ADC就在每个PWM周期内各采样一次且采样时刻均匀间隔等效采样率变成3倍PWM频率。4.2 相移触发原理和关键约束用表格看更清楚假设TIM1周期P3000计数频率1MHz通道比较值CCR触发事件时刻触发的ADC说明CH11000t1msADC1第一拍CH22000t2msADC2第二拍CH32999t2.999msADC3第三拍三个采样点在一个周期内均匀散开等效采样周期是单ADC采样周期的三分之一等效采样率就是三倍。这里有个硬约束必须注意相邻两个触发脉冲的时间间隔必须大于单个ADC完成一次转换所需的时间。如果ADC还在转换中新的触发来了会被忽略数据不会自动排队。计算一下最极端的两种情况。ADCCLK12MHz时一个ADC时钟周期约83.3ns。如果采样时间选1.5周期总转换时间(1.512.5)周期14周期≈1.17us。那三个触发脉冲之间的间隔只要大于1.17us就行对应等效采样率能做到几百kHz。如果采样时间还用239.5周期总转换时间约21us三个脉冲间隔必须大于21us等效采样率就直接被压到47kHz以下。所以做交错采样采样时间必须往短了选1.5周期或7.5周期是常见选择这个取舍直接影响最终效果。4.3 配置要点和验证方法CubeMX里做两件事。TIM1配置三个输出比较通道模式选PWM Generation CH1/CH2/CH3Prescaler设为72-1Period按你的目标频率设置三个通道的Pulse分别为Period/3、2×Period/3和Period-1。三个ADC的外部触发源分别改成Timer 1 Capture Compare 1、Timer 1 Capture Compare 2、Timer 1 Capture Compare 3。代码上启动顺序依然遵循“先ADC后定时器”TIM_OC_InitTypeDef sConfigOC {0}; htim1.Instance TIM1; htim1.Init.Prescaler 72 - 1; htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.Period 3000 - 1; HAL_TIM_OC_Init(htim1); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 1000; HAL_TIM_OC_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); sConfigOC.Pulse 2000; HAL_TIM_OC_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_2); sConfigOC.Pulse 2999; HAL_TIM_OC_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_3); HAL_TIM_OC_Start(htim1, TIM_CHANNEL_1); HAL_TIM_OC_Start(htim1, TIM_CHANNEL_2); HAL_TIM_OC_Start(htim1, TIM_CHANNEL_3);三个ADC外部触发模式下每次都只转换一次结果依然可以被DMA搬走。数据解析时把三个缓冲区按时间顺序拼接起来就是一条等效三倍采样率的连续样本流。验证方法我觉得最直观信号发生器输出一个1kHz正弦波用1kHz同步采样去采集波形会严重混叠甚至完全看不出正弦改用1kHz定时器周期加三ADC交错采样等效3kHz采样率虽然1kHz信号还是低于奈奎斯特频率但有明显改善。如果把TIM1频率提到10kHz等效30kHz采样率再采1kHz正弦就画得很漂亮了。5. 实测数据对比单ADC和三重ADC采集同一信号差在哪5.1 测试环境和数据采集方式我拿F103ZET6核心板做了两组对比。输入信号用信号发生器输出1kHz正弦波幅值3V峰峰值叠加1.65V直流偏置再打开信号源的噪声输出叠加约10mV宽带噪声。采样率统一设成1kHz一组用单个ADC采集64个点另一组用三重ADC同步采样每次触发得到三个样本后求平均同样攒64个平均结果。数据从串口打印到PC用串口助手导出CSV再用脚本算统计量。因为输入信号本身是周期信号直接看波形不够客观我选择对每组数据的噪声方差做比较。具体做法是先用正弦拟合得到理想值再用实际采样值减理想值得到残差残差的方差就是噪声能量的估计。5.2 时域和统计结果实测下来单ADC在12位量化下残差标准差大概在2~3个LSB而三重同步平均后残差标准差掉到1.1~1.5个LSB方差比值在2.5~3.2倍之间波动。理论预测是3倍实测略低原因是ADC内部参考噪声和量化噪声不是完全独立的还有三个ADC各自自身的offset误差没校准干净。从有效位数角度换算ΔENOB0.5×log₂(σ单²/σ三²)。如果方差比正好是3对应提升约0.79位实测取方差比2.8提升约0.74位。也就是说三重同步平均带来的提升就是“12位半到13位”这个量级。我还用FFT看了噪底。对同样时长的采样数据做FFT单ADC的噪声底大约在-65dBFS附近三重平均后大约是-70dBFS差了约5dB和10log₁₀(3)4.77dB的理论值高度吻合。这个结果对电磁环境不好的板子来说已经是实打实的提升。5.3 关于“把12位变成14位”的宣传话术如果你在网上看到“STM32三重ADC采样精度可达14位”的帖子要分清两种情况。一种是做了大量的过采样平均比如三个ADC采16轮共48个样本再平均理论上确实能逼近14位但代价是有效带宽大幅下降输入信号必须接近直流或极低频。另一种就是把FFT里无数个噪声谱线都算上得到一个“有效分辨率”的理论上限工程上拿不到这么理想的结果。我个人的建议是把三重同步平均当成“白送的0.7~0.8位”来用不会吃亏。至于更高分辨率需求要么老老实实做16次过采样平均要么换带硬件过采样器的芯片要么上外置高精度ADC别指望纯靠三个内置ADC逆天改命。6. 踩坑记录、选型建议和下一步优化方向6.1 我实测中踩过的几个坑第一个大坑是连续转换模式和外部触发叠加。CubeMX里如果习惯性点开了Continuous ConversionDMA会不断搬运数据定时器触发信号又源源不断进来两个事件互相打架结果就是DMA缓冲区里数据错位、HAL_ADC_ConvCpltCallback回调频率完全对不上。解决办法就是关掉连续转换让每次转换都由定时器脉冲驱动整个数据流才是一个萝卜一个坑。第二个坑是只启动了ADC1和ADC3的DMA没管ADC2。三重同步模式下ADC2虽然不直接挂DMA但它的ADON位必须被置位否则CDR高16位永远是默认值。我见过有人排查半天采集到的ADC2值恒定不变最后发现是启动函数漏了hadc2。第三个坑是触发频率太快。F1的ADC没有FIFO转换过程中收到新触发信号会直接忽略。如果你用DMA buffer采集看起来数据一直在更新其实很多值都是上一轮转换的旧数据波形会出现肉眼可见的“阶梯状变形”。排查方法很简单把采样时间代入公式算出单次转换时间保证触发周期大于这个值。第四个坑就是选型。拿C8T6做三重ADC折腾一晚上发现根本没有ADC3外设这种事我真见过不少。做之前先翻数据手册确认芯片带三个ADC再开工。6.2 不同需求下怎么选方案我根据自己的实践做了一个选型表目标推荐方案备注三路同步采集三相电流等三重同步注入模式三个ADC同时采样三个通道相位一致性好单通道精度提升0.5~0.8位三重同步规则模式平均配置简单代码量少单通道极高精度14位三重同步多轮平均或外部ADC注意带宽损失输入限直流/低频单通道采样率提升3倍定时器三通道相移触发采样时间必须调短否则触发间隔不够多通道且高采样率F3/G4硬件多重交错比F1强太多新项目优先考虑F1这套方案里我最常用的其实是同步规则模式DMA。因为代码稳定、数据流清晰做控制环比如电压电流双闭环时三个ADC可以同时抓三路反馈量相位误差几乎为零对环路稳定性帮助很大。6.3 后续可以优化的方向如果你想把这套东西做成产品级几个方向值得深入。一是三ADC通道间失配校准三个ADC的offset和增益不可能完全一致可以写一段自检程序输入已知电压生成每个ADC的offset/gain校准表平均前先按校准表修正精度还能再挤出来一点。二是把“每N点平均”改成真正的数字低通滤波加抽取对交错采样的高速样本流做sinc滤波比简单平均更平滑频响也更可控。三是利用DMA的半传输中断实现双缓冲流水CPU在后台处理上一批数据DMA同时搬运下一批这样采样率可以压得更满。最后说点我自己的体会。三重ADC这套玩法本质上是把芯片里已经付过费的资源用到位不加一分钱硬件成本却能换来0.7位左右的精度提升或者三倍的采样率提升。它的上限也确实摆在那不是能把12位变24位的魔法但对很多要求不夸张的嵌入式项目来说够用且好用。我建议你拿到板子后先按方法一跑通同步平均再用方法二跑一下交错模式两个方向都实际体会一遍以后再看到花里胡哨的“多重ADC”方案就能一眼看出它到底在玩哪种套路了。
返回列表