1. 为什么STM32 G4的ADC值得单独拿出来聊STM32G4系列定位很特殊它夹在F4和H7之间主频不算最高但片上模拟外设给得异常大方。以G474为例三个独立的12位ADC最高采样率可以到4Msps12位下如果开启硬件过采样还能把有效位数推到16位左右。这个规格放在电机控制、数字电源、精密传感器采集这些场景里性价比非常突出。但问题也恰恰出在这里。很多人拿到G4之后直接照搬F103时代的ADC配置思路——轮询、单次转换、软件触发结果发现采样值跳动大、噪声压不下去、高速采样时CPU被中断拖死。然后就开始怀疑芯片不行或者怀疑自己的模拟前端有问题。实际上大部分情况下问题出在配置策略上而不是硬件本身。我最近做了一个基于G474的电流采样项目需要在一路电机驱动板上同时采集三相电流和母线电压采样率要求每路至少100kHz精度要求有效位数不低于11位。这个需求用G4的ADC硬件过采样加DMA搬运再配合软件端的滑动平均滤波最终实测有效位数能稳定在12.5位左右。整个过程踩了不少坑也积累了一些在官方文档里不太容易找到的经验这里系统性地整理一下。这篇文章适合正在用STM32G4做模拟采集的工程师也适合从F1/F4平台迁移过来、发现老套路不奏效的朋友。我会从硬件过采样的原理讲起一直讲到软件滤波的选型和DMA配置的细节尽量把每个参数背后的逻辑说清楚。2. 硬件过采样到底在做什么2.1 过采样的数学本质过采样的核心逻辑其实一句话就能说清楚对同一个信号连续采样N次然后把结果累加再右移相应的位数。每4倍过采样理论上能提升1位有效分辨率因为噪声被平均掉了而信号是相干的。具体来说如果过采样倍率是N累加后的结果位宽会增加log2(N)位。比如12位ADC做16倍过采样累加结果是16位右移2位后得到14位的结果。STM32G4的硬件过采样单元支持2x到256x的倍率右移位数可以独立配置这就给了很大的灵活性。但这里有一个容易被忽略的点过采样提升的是信噪比不是绝对精度。如果你的参考电压本身噪声就大或者模拟前端有系统性偏移过采样帮不了你。它只能压制随机噪声对确定性误差无能为力。2.2 G4的过采样硬件单元怎么配G4的ADC过采样配置涉及几个关键寄存器字段在CubeMX里对应的参数是Oversampling Mode、Oversampling Ratio、Oversampling Shift和Oversampling Trigger。过采样倍率的选择要看你的目标。如果只是想把12位做到13到14位4x到16x就够了。如果想推到16位需要256x但这时候采样率会大幅下降。以G474为例ADC时钟如果是60MHz12位转换需要12.5个ADC周期单次转换时间约208ns。256倍过采样意味着每次输出需要256次转换等效输出速率就降到约18.8kHz。这个代价必须提前算清楚。右移位数Shift的设置有个原则右移位数应该等于log2(过采样倍率)这样结果刚好回到12位。但如果你想让结果保持更高的位宽可以少右移几位。比如16x过采样累加结果是16位你可以右移2位得到14位结果也可以右移0位得到16位结果。后者在后续软件处理时动态范围更大但要注意DMA目标缓冲区要用uint16_t。触发模式Oversampling Trigger决定了过采样在什么条件下启动。通常选“Triggered by ADC conversion”就行意思是每次常规触发后自动执行过采样序列。还有一种“Resumed by trigger”模式适合需要精确控制过采样窗口起点的场景。注意开启过采样后ADC的采样时间Sampling Time设置会直接影响过采样的总耗时。采样时间越长每次转换越慢256倍过采样的总时间就越长。建议在满足信号源阻抗要求的前提下尽量选较短的采样时间。2.3 过采样和DMA的配合方式过采样开启后ADC每次“输出”一个结果实际上内部已经完成了N次转换。DMA的搬运单位是过采样后的结果不是每次原始转换。这一点在配置DMA缓冲区长度时要注意——缓冲区长度应该等于你想要的输出样本数而不是原始转换次数。G4的ADC支持DMA循环模式配合过采样可以做到完全无CPU干预的连续采集。配置时把DMA设为Circular模式外设地址设为ADC_DR寄存器内存地址设为你的缓冲区数据宽度根据右移位数选择Half Word或Word。这里有个细节如果右移位数小于log2(倍率)结果可能超过16位这时候DMA必须用Word宽度。但大多数情况下右移到12位或14位用Half Word就够了能省一半内存带宽。3. 软件滤波方案怎么选3.1 滑动平均滤波的适用边界滑动平均是最直观的滤波方式把最近N个采样值求平均。它的本质是一个低通滤波器截止频率和N成反比。N越大输出越平滑但响应越慢。在电机电流采样这种场景里滑动平均有一个致命问题它会把电流的快速变化也平滑掉。如果PWM频率是20kHz电流环带宽要求1kHz以上滑动平均的窗口就不能太大。我实测下来N8在100kHz采样率下截止频率大约在6kHz左右对电流环的影响可以接受。实现上滑动平均可以用环形缓冲区加累加和的方式每次新数据进来减去最老的数据加上最新的数据然后除以N。这样每次只需要一次加法和一次减法计算量极小。在G4的170MHz主频下处理100kHz的三路采样绰绰有余。3.2 中值滤波和滑动平均的组合中值滤波对脉冲噪声特别有效比如MOSFET开关瞬间耦合进来的尖峰。但中值滤波需要排序计算量比滑动平均大得多。如果采样率是100kHz每路每秒钟要排10万次序三路就是30万次即使是G4也会吃掉不少CPU。我的做法是组合使用先用一个长度为3或5的中值滤波去掉尖峰再做滑动平均。中值滤波的窗口不需要太大3点就够因为脉冲噪声通常只影响一两个采样点。排序3个数的计算量很小用几个比较交换就能完成。具体实现时可以用一个长度为3的数组每次新数据进来后做三次比较交换取中间值。这个操作在C语言里可以用宏或者内联函数实现编译器优化后大概只需要几个时钟周期。3.3 一阶低通滤波器的参数计算一阶低通滤波器的公式是 y[n] α * x[n] (1-α) * y[n-1]其中α决定了截止频率。α越大响应越快滤波效果越弱α越小输出越平滑但相位滞后越大。截止频率fc和α的关系是α 1 - exp(-2π * fc / fs)其中fs是采样率。反过来给定α截止频率fc -fs * ln(1-α) / (2π)。举个例子采样率100kHz想要截止频率1kHzα 1 - exp(-2π * 1000 / 100000) ≈ 0.061。这个值在定点运算里可以用Q15格式表示为0.061 * 32768 ≈ 1999。一阶低通的计算量比滑动平均还小只需要一次乘法和一次加法。但它对脉冲噪声的抑制能力不如中值滤波所以通常也是配合中值滤波使用。3.4 滤波方案对比与选型建议滤波方式计算量脉冲噪声抑制相位滞后适用场景滑动平均低一般中等缓变信号如温度、电压中值滤波中强小有尖峰干扰的场合一阶低通极低弱中等实时性要求高的闭环控制中值滑动平均中强中等电机电流、电源采样中值一阶低通低强小高速闭环控制选型的核心原则是先搞清楚噪声的类型再选对应的滤波器。如果是白噪声滑动平均或低通都行如果是脉冲噪声必须上中值滤波如果是周期性干扰可能需要陷波器但那又是另一个话题了。4. 从CubeMX配置到代码落地的完整流程4.1 CubeMX里的关键配置项打开CubeMX选好G474芯片后在Analog标签下找到ADC1。先使能ADC1的IN1和IN2两个通道分别对应三相电流的两相第三相可以用基尔霍夫定律算出来省一个ADC通道。在Parameter Settings里几个关键参数这样设Clock Prescaler选Synchronous clock mode divided by 4。G4的ADC时钟来自系统时钟170MHz除以4是42.5MHz在ADC的最大时钟范围内。Resolution12位。Scan Conversion ModeEnabled因为要扫两个通道。Continuous Conversion ModeDisabled我们用定时器触发。DMA Continuous RequestsEnabled这样DMA可以在每次转换后自动搬运。End Of Conversion SelectionEOC after each conversion每个通道转换完都产生EOC。OverrunOverrun data overwritten避免溢出时卡死。在Oversampling里Mode选EnabledRatio选16xShift选2Trigger选Triggered by ADC conversion。这样每个通道输出的是14位结果。DMA Settings里添加ADC1的DMA请求模式选Circular数据宽度选Half Word内存地址自增。4.2 定时器触发配置用TIM1的TRGO来触发ADC。TIM1配置成PWM模式频率20kHz中央对齐。在Trigger Output里选Update Event这样每个PWM周期产生一次触发。但这里有个问题20kHz的PWM频率意味着ADC触发频率也是20kHz如果过采样16x每次触发需要16次转换总转换时间约3.3μs远小于50μs的PWM周期时间上完全来得及。如果你想要更高的采样率可以把TIM1的TRGO改成比较事件在一个PWM周期内触发多次。比如在计数器的25%和75%位置各触发一次采样率就变成40kHz。但要注意避开MOSFET开关瞬间否则会采到开关噪声。4.3 DMA缓冲区的设计DMA缓冲区的大小要结合滤波窗口来定。假设滑动平均窗口N8中值滤波窗口M3那么DMA缓冲区至少要是NM-110个样本。但实际上为了处理方便我通常设成2的幂次比如16或32。缓冲区用uint16_t数组因为14位结果用16位存刚好。如果是两个通道交替采样缓冲区里数据是交错的CH1, CH2, CH1, CH2... 处理的时候要分开。#define ADC_BUF_SIZE 32 uint16_t adc_dma_buf[ADC_BUF_SIZE]; // 启动ADC和DMA HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_dma_buf, ADC_BUF_SIZE);DMA传输完成一半和全部完成的中断可以用来做双缓冲但在这个场景里因为采样率不高直接在主循环里处理就行不需要中断。4.4 软件滤波的代码实现先定义滤波器的状态变量#define MA_WINDOW 8 #define MED_WINDOW 3 typedef struct { uint16_t ma_buf[MA_WINDOW]; uint32_t ma_sum; uint8_t ma_idx; uint16_t med_buf[MED_WINDOW]; uint8_t med_idx; } FilterState; FilterState filt_ch1, filt_ch2;中值滤波的实现uint16_t median3(uint16_t a, uint16_t b, uint16_t c) { if (a b) { uint16_t t a; a b; b t; } if (b c) { uint16_t t b; b c; c t; } if (a b) { uint16_t t a; a b; b t; } return b; }滑动平均的实现uint16_t moving_average(FilterState *fs, uint16_t new_val) { fs-ma_sum - fs-ma_buf[fs-ma_idx]; fs-ma_buf[fs-ma_idx] new_val; fs-ma_sum new_val; fs-ma_idx (fs-ma_idx 1) % MA_WINDOW; return (uint16_t)(fs-ma_sum / MA_WINDOW); }组合滤波的处理函数uint16_t process_adc(FilterState *fs, uint16_t raw) { // 先做中值滤波 fs-med_buf[fs-med_idx] raw; fs-med_idx (fs-med_idx 1) % MED_WINDOW; uint16_t med median3(fs-med_buf[0], fs-med_buf[1], fs-med_buf[2]); // 再做滑动平均 return moving_average(fs, med); }在主循环里遍历DMA缓冲区把CH1和CH2的数据分别送入各自的滤波器while (1) { for (int i 0; i ADC_BUF_SIZE; i 2) { uint16_t ch1_raw adc_dma_buf[i]; uint16_t ch2_raw adc_dma_buf[i1]; uint16_t ch1_filt process_adc(filt_ch1, ch1_raw); uint16_t ch2_filt process_adc(filt_ch2, ch2_raw); // 后续使用ch1_filt和ch2_filt } HAL_Delay(1); }4.5 参数计算的实际案例假设ADC参考电压是3.3V12位分辨率过采样16x右移2位后得到14位结果。那么每个LSB对应的电压是3.3V / 16384 ≈ 0.201mV。如果采样电阻是10mΩ运放增益是10倍那么实际电流的灵敏度是10mΩ * 10 100mV/A。每个LSB对应的电流是0.201mV / 100mV/A 2.01mA。这个精度对于大多数电机控制应用已经足够了。如果还需要更高精度可以把过采样倍率提到64x右移2位得到16位结果LSB变成0.0503mV对应电流0.503mA。但要注意过采样倍率提高后采样率会下降。64x过采样在42.5MHz ADC时钟下每次转换约208ns64次就是13.3μs等效输出速率约75kHz。如果PWM频率是20kHz每个周期能采3到4次也够用了。5. 实际调试中遇到的坑和排查方法5.1 采样值跳动大的常见原因第一个要排查的是参考电压。G4的VREF可以外部提供也可以用内部的。如果用内部参考噪声通常比外部LDO大。我实测过用内部参考时噪声峰峰值大约在8到10个LSB换成外部REF3033后降到2到3个LSB。第二个是采样时间不够。如果信号源阻抗较高采样电容充电不充分会导致采样值偏低且不稳定。G4的ADC采样电容约4pF如果源阻抗是10kΩ时间常数约40ns采样时间至少要设到160ns以上才能充到99%以上。在CubeMX里采样时间对应的周期数是可选的选24.5个ADC周期比较保险。第三个是地线处理。模拟地和数字地如果没有单点连接或者连接点选得不好开关噪声会串进模拟部分。我的做法是在ADC下方铺一块独立的模拟地通过一个0Ω电阻在芯片下方单点连接到数字地。5.2 DMA传输不触发或只触发一次这个问题通常出在DMA配置上。检查几个点DMA请求是否使能ADC的DMA使能位是否置位DMA模式是否是Circular。如果只触发一次就停了很可能是DMA模式设成了Normal。还有一个容易忽略的点ADC的Overrun行为。如果DMA没来得及搬运ADC又产生了新数据Overrun标志会置位。如果Overrun设置是“Preserve”ADC会停止转换。改成“Overrun data overwritten”就能继续。在CubeMX里这个选项在ADC的Parameter Settings最下面叫Overrun behaviour。默认是Preserve很多人不改结果DMA一慢就卡死。5.3 过采样开启后结果不对最常见的原因是右移位数设错了。如果右移位数大于log2(倍率)结果会偏小如果小于结果会偏大。比如16x过采样log2(16)4如果右移了5位结果就是实际值的1/2。还有一个原因是过采样的触发源没配对。如果选了“Triggered by ADC conversion”但ADC本身没有被正确触发过采样就不会执行。检查TIM的TRGO是否真的连到了ADC的触发输入在CubeMX的ADC配置里External Trigger Conversion Source要选对。5.4 滤波后响应变慢的问题滑动平均窗口N越大响应越慢。如果发现电流环振荡或者响应迟钝先检查滤波窗口是不是太大了。在100kHz采样率下N8的群延迟大约是(N-1)/2 / fs 3.5 / 100000 35μs。这个延迟在1kHz带宽的电流环里会引入约12.6度的相位滞后通常可以接受。但如果N32延迟就变成155μs相位滞后55度很可能导致振荡。解决办法是减小窗口或者改用一阶低通滤波器因为一阶低通的相位滞后比同样截止频率的滑动平均要小。5.5 常见问题速查表现象可能原因排查方法解决措施采样值跳动大参考电压噪声用示波器看VREF换外部低噪声参考采样值偏低采样时间不足增大采样时间看是否改善增加采样周期数DMA只搬一次DMA模式错误检查DMA配置改为Circular模式过采样结果偏小右移位数过大核对log2(倍率)调整Shift值滤波后振荡滤波窗口过大计算群延迟减小窗口或改一阶低通ADC不触发触发源配置错误检查TIM TRGO重新配置触发源提示调试ADC时先用一个已知的稳定电压源比如基准芯片的输出接入ADC输入观察采样值的分布。如果标准差在2个LSB以内说明硬件和配置都没大问题。如果超过5个LSB就要从参考电压和地线开始查。6. 性能实测与优化建议6.1 实测数据在G474上ADC时钟42.5MHz12位16x过采样右移2位两通道扫描DMA循环搬运。输入接REF3033的3.0V输出采样1000个点统计结果如下平均值14912理论值3.0V / 3.3V * 16384 14894标准差1.8 LSB峰峰值9 LSB有效位数约12.6位如果不开启过采样同样条件下标准差约4.2 LSB有效位数约11.2位。过采样16x带来了约1.4位的有效位数提升和理论值log2(16)/2 2位有差距主要是因为参考电压噪声和量化噪声之外的其他噪声源。6.2 进一步优化的方向如果还想再压噪声有几个方向可以试。一是把过采样倍率提到64x有效位数能到13位左右但采样率降到75kHz。二是用外部16位ADC比如ADS8320之类的但成本会增加。三是优化PCB布局把模拟部分远离功率部分地平面分割更合理。软件端还可以加一个IIR陷波器针对PWM频率的干扰。如果PWM是20kHz采样率100kHz陷波器中心频率设20kHz带宽设1kHz能有效抑制开关噪声的基波。但陷波器的参数计算比较麻烦需要用到双线性变换这里就不展开了。6.3 资源占用情况整个ADC采集加滤波的代码在G474上编译后占用Flash约4KBRAM约200字节主要是DMA缓冲区和滤波器状态。CPU占用率在100kHz采样率下约3%主要是主循环里的滤波计算。如果改成DMA中断里处理CPU占用率能降到1%以下但中断频繁触发也会带来额外开销。我个人倾向于在主循环里处理因为100kHz的采样率下主循环每10μs处理一次对于170MHz的G4来说很轻松。而且主循环处理避免了中断嵌套的问题调试起来更简单。6.4 不同应用场景的参数推荐应用场景采样率过采样倍率滤波方案有效位数电机电流环100kHz16x中值滑动平均12.5位数字电源电压50kHz64x滑动平均13位温度采集1kHz256x一阶低通14位音频信号48kHz4x无12位电池管理10kHz32x中值一阶低通13位这张表是我在实际项目中总结出来的不一定适用于所有情况但可以作为起点。具体参数还要根据信号带宽、噪声特性、实时性要求来调整。7. 一些不太容易想到的细节过采样的右移操作是在ADC硬件里完成的不消耗CPU时间。但右移后的结果如果位数超过12位DMA搬运时要注意数据宽度。比如14位结果用uint16_t存没问题但如果右移0位得到16位结果DMA必须用Word宽度否则会丢数据。还有一点G4的ADC在过采样模式下EOC中断的产生时机是过采样序列完成后不是每次转换后。这意味着如果你用EOC中断来读数据读到的已经是过采样后的结果。但用DMA的话DMA请求也是在过采样完成后才产生所以DMA缓冲区的更新频率等于过采样输出频率不是原始转换频率。最后说一个关于校准的事。G4的ADC有自校准功能上电后调用HAL_ADCEx_Calibration_Start()就行。但校准只能在ADC停止时做而且校准前要确保ADC时钟已经使能并稳定。我遇到过校准失败的情况原因是ADC时钟配置成了异步模式但时钟源没选对。改成同步模式后就正常了。注意每次上电后都要重新校准因为校准值存在ADC的内部寄存器里掉电不保存。如果从低功耗模式唤醒也要重新校准。这些细节在官方参考手册里都有但分散在不同的章节实际调试时容易漏掉。我把它们集中列出来希望能帮你少走点弯路。
阅读完成 · 觉得有帮助?