news 2026/9/24 6:21:10

STM32G4 ADC硬件过采样与软件滤波实战:从配置到代码落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32G4 ADC硬件过采样与软件滤波实战:从配置到代码落地

1. 为什么STM32 G4的ADC值得单独拿出来聊

STM32G4系列定位很特殊,它夹在F4和H7之间,主频不算最高,但片上模拟外设给得异常大方。以G474为例,三个独立的12位ADC,最高采样率可以到4Msps(12位下),如果开启硬件过采样还能把有效位数推到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时钟如果是60MHz,12位转换需要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以上,滑动平均的窗口就不能太大。我实测下来,N=8在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的最大时钟范围内。
  • Resolution:12位。
  • Scan Conversion Mode:Enabled,因为要扫两个通道。
  • Continuous Conversion Mode:Disabled,我们用定时器触发。
  • DMA Continuous Requests:Enabled,这样DMA可以在每次转换后自动搬运。
  • End Of Conversion Selection:EOC after each conversion,每个通道转换完都产生EOC。
  • Overrun:Overrun data overwritten,避免溢出时卡死。

在Oversampling里,Mode选Enabled,Ratio选16x,Shift选2,Trigger选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缓冲区的大小要结合滤波窗口来定。假设滑动平均窗口N=8,中值滤波窗口M=3,那么DMA缓冲区至少要是N+M-1=10个样本。但实际上为了处理方便,我通常设成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[i+1]; 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.3V,12位分辨率,过采样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时钟下,每次转换约208ns,64次就是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采样率下,N=8的群延迟大约是(N-1)/2 / fs = 3.5 / 100000 = 35μs。这个延迟在1kHz带宽的电流环里会引入约12.6度的相位滞后,通常可以接受。但如果N=32,延迟就变成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.5MHz,12位,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约4KB,RAM约200字节(主要是DMA缓冲区和滤波器状态)。CPU占用率在100kHz采样率下约3%,主要是主循环里的滤波计算。如果改成DMA中断里处理,CPU占用率能降到1%以下,但中断频繁触发也会带来额外开销。

我个人倾向于在主循环里处理,因为100kHz的采样率下,主循环每10μs处理一次,对于170MHz的G4来说很轻松。而且主循环处理避免了中断嵌套的问题,调试起来更简单。

6.4 不同应用场景的参数推荐

应用场景采样率过采样倍率滤波方案有效位数
电机电流环100kHz16x中值+滑动平均12.5位
数字电源电压50kHz64x滑动平均13位
温度采集1kHz256x一阶低通14位
音频信号48kHz4x12位
电池管理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的内部寄存器里,掉电不保存。如果从低功耗模式唤醒,也要重新校准。

这些细节在官方参考手册里都有,但分散在不同的章节,实际调试时容易漏掉。我把它们集中列出来,希望能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 6:17:45

林内RBS-35UCA说明书深度解读:压力、防冻与故障诊断实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 6:14:56

CCM图腾柱PFC设计实战:GaN器件与核心控制要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 6:07:48

Flink 应用参数处理:使用 ParameterTool 管理配置输入的完整指南

大数据流处理批处理数据工程 【免费下载链接】flink 项目地址&#xff1a; https://gitcode.com/gh_mirrors/fli/flink 点击查看 免费下载 在 Flink 中&#xff0c;无论是批处理还是流处理应用&#xff0c;几乎都依赖外部配置参数来驱动运行&#xff1a;它们用于指定输入输出源…

作者头像 李华
网站建设 2026/9/24 5:56:44

零漂移运放ADA4528-1实战:斩波稳零原理、选型与精密电路布局

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 5:53:30

私有化办公IM选型指南:从消息可靠性到运维成本的全维度评估

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华