☰
STM32G431 ADC采集实战:从单通道到DMA环形缓冲与多通道扩展
2026/10/20 8:25:41 网站建设 项目流程

简介:面向使用STM32G431RBT6进行模拟信号采集的嵌入式开发者,一套围绕ADC的直接采集与DMA传输展开的资源包,集合原理说明、配置流程与工程代码,适合需要在小体积、高性能场景下实现连续数据捕获的工程师学习参考。包内共1254个文件,大小29.73MB,以676个C源文件、275个头文件为主体,另含启动汇编、链接脚本、HAL库头文件、工程配置及hex/axf固件,可支撑CubeMX生成的完整工程直接编译调试。目前已有1978人浏览学习。资料从ADC高精度多通道特性出发,对比直接读取寄存器与DMA自动搬运两种方式的差异,并给出了DMA通道连接、中断配合和采样率优化等细节,配合目录中的示例程序,能够帮助开发者快速理清从配置到实际采集的完整链路,减少踩坑,可直接迁移到传感器信号处理或电源监测等项目中。资源包目录分类清晰,既包含可直接烧录的固件,也保留了工程源文件,便于对照学习和二次开发。

1. 项目实战复盘:STM32G431RBT6 的 ADC 采集,从单通道到 DMA 环形缓冲

拿到一块 STM32G431RBT6 的开发板,十有八九是从电机控制或者数字电源项目开始的。这颗芯片的亮点是主频跑到 170MHz,内置 12 位 ADC 支持硬件过采样,配合 DMA 可以做到多通道连续采集不占 CPU。本文拆解的就是一个基于 G431 的 ADC 直接采集与 DMA 采集的完整工程,覆盖 CubeMX 配置、HAL 库代码、多通道序列和 DMA 环形缓冲区处理。读完你能直接对着源码跑通一个从模拟量到串口打印的完整链路,也能快速改造成自己项目里的电压、电流采样模块。

适合的人群是那些准备用 G431 做电机控制、数字电源或者数据采集,但被 ADC 校准、DMA 中断、过采样这几件事绕晕的开发者。

2. 技术选型与硬件准备:为什么用 G431 的 ADC 而不是软件轮询

2.1 G431RBT6 的 ADC 资源到底强在哪

STM32G431RBT6 内部集成了两个 12 位 ADC,分别是 ADC1 和 ADC2,两者可以配合做同步采样,这在电机控制的相电流采集里是刚需。每个 ADC 的转换速率最高能到 4Msps,当使能硬件过采样后,相当于用速度换精度,能把有效位数从 12 位拉到 14 位甚至更高,省掉外部运放和滤波器的成本。

除了常规的通道扫描,G431 的 ADC 还支持注入通道、模拟看门狗、偏移校准等功能。其中校准是很多人忽略的步骤,芯片出厂后增益和偏移误差并不一致,上电后必须执行一次校准,否则采出来的电压值可能偏差百分之几。这个工程里在初始化函数中显式调用了校准接口,做完校准才启动 DMA。

硬件准备上,一块 G431 核心板即可,不需要专门买高精度基准源。板载稳压器输出的 3.3V 作为参考电压,但要注意这个电压本身有误差,如果做的是高精度采集,建议外接一个 2.5V 或者 3.0V 的基准芯片,这里源码里默认用 VDDA,代码中留了宏定义可以切换。

2.2 CubeMX 配置里那些容易踩的选项

工程使用 STM32CubeMX 生成初始化代码,选择芯片型号后,ADC 配置页里有一堆下拉选项,这里逐个说明:

  • Scan Conversion Mode:多通道采集时必须 Enabled,单通道可以关掉
  • Continuous Conversion Mode:和 DMA 搭配时建议 Enabled,ADC 会自动一轮接一轮地转换
  • DMA Continuous Requests:必须 Enabled,否则一轮 DMA 传输完成后就停了
  • End of Conversion Selection:选择 EOC flag at the end of each conversion,这样每次转换结束都会触发一次 DMA 请求
参数名称推荐取值作用
Clock Prescaler6 分频让 ADC 时钟落在 14MHz 附近,保证转换精度
Resolution12 位默认即可,追求精度可以开过采样
Sampling Time8.5 到 32.5 周期信号源内阻大的时候调高,值越大越准但越慢
Trigger定时器触发或软件触发电机控制用定时器,数据采集用软件

2.3 硬件接线与电位器采样准备

为了验证双通道 DMA 采集,工程在 PA0 和 PA1 上各接了一个 10K 电位器,分别对应 ADC1 的通道 1 和通道 2。PA0 和 PA1 在 G431 上可以直接用作模拟输入,不需要外部下拉电阻。把两个电位器的滑片分别连到这两个引脚,另外两端接 VCC 和 GND,旋动时电压从 0 到 3.3V 变化,串口打印值也会跟着变。

如果手头没有电位器,也可以直接测 VDDA 和 GND,电压值应该是 4095 和 0,用于验证链路是否通。还有一个常见的做法是测芯片内部的参考电压通道,G431 可以选择 VREFINT 通道,它是一个固定值大约 1.2V 的内部基准,用来反推 VDDA 的实际电压,代码里也有这个通道的注释,但没有默认打开。

3. 核心代码拆解:一个请求从电压值到串口打印的完整链路

3.1 多通道扫描与 DMA 的初始化逻辑

工程初始化部分的代码集中在 MX_ADC1_Init 函数里,这里是用 CubeMX 生成再手工调整后的版本:

static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; // 多通道扫描 hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换 hadc1.Init.DiscontinuousConvMode = DISABLE; // 不使用间断模式 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐 hadc1.Init.NbrOfConversion = 2; // 两个通道 hadc1.Init.DMAContinuousRequests = ENABLE; // DMA 连续请求 hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED; // 溢出时保留旧数据 hadc1.Init.OversamplingMode = DISABLE; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_32CYCLES_5; // 采样时间拉长一点 HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_2; sConfig.Rank = ADC_REGULAR_RANK_2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); }

ScanConvMode 打开后,ADC 会按 Rank 顺序依次转换通道 1 和通道 2。每次转换结束产生 EOC 事件,DMA 请求随即发出,数据被搬到内存数组里。注意这里的采样时间选了 32.5 周期,不是最快的 2.5 周期,这是因为接电位器时信号源内阻不固定,采样时间太短会导致采样电容没充满,采集值跳动明显。

外部触发选择软件启动,意味着调用 HAL_ADC_Start_DMA 之后 ADC 就开始自动循环。如果换到电机控制场景,把 ExternalTrigConv 改成某个定时器的触发输出即可,其他配置不用动。

3.2 校准函数:启动 DMA 之前不能省掉的一步

G431 的 ADC 在每次上电后会有一个随机的偏移误差,如果不校准就转换,读到的值会整体偏移。校准函数写在 ADC 初始化之后:

static void ADC_Calibration_Init(void) { HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); }

单端输入模式下传入 ADC_SINGLE_ENDED。如果是差分输入,这里要换成 ADC_DIFFERENTIAL_ENDED,并且两个输入引脚都需要接信号。校准完成后 ADC 内部会打一个偏移值,后续转换结果自动修正。

不少人在第一次跑 G431 工程时发现 DMA 采上来的值整体偏移了 50 到 100 个 LSB,原因就是没做校准。这个操作耗时在毫秒级,放在初始化末尾即可,不会影响实时性。

3.3 DMA 环形缓冲与空闲中断的配合

DMA 配置使用了环形模式,配置如下:

static void MX_DMA_Init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); hdma_adc1.Instance = DMA1_Channel1; hdma_adc1.Init.Request = DMA_REQUEST_ADC1; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataSize = DMA_PDATAALIGN_WORD; hdma_adc1.Init.MemDataSize = DMA_MDATAALIGN_WORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); }

环形模式意味着 DMA 传输完一轮后会自动回到起始地址继续传,不需要软件干预。适合 ADC 连续转换、CPU 稍后处理的场景。如果配成普通模式,一轮传完 DMA 就停了,ADC 数据会覆盖寄存器里的旧值,必须重新启动 DMA,这在实时采集里是不可接受的。

接收数组定义成了两个 32 位无符号数:

static uint32_t adc_buf[2];

通道 1 的数据存在 adc_buf[0],通道 2 的数据存在 adc_buf[1]。每次 DMA 传输完成,CPU 在 while 主循环里直接读取这个数组,不需要加锁,因为 ADC 转换周期远大于主循环读取时间,即使偶尔读到边沿数据,下一轮也会被覆盖修正。

3.4 电压换算与串口输出

原始值换算成电压的代码在 main 函数里:

float vol1 = (float)adc_buf[0] * 3.3f / 4095.0f; float vol2 = (float)adc_buf[1] * 3.3f / 4095.0f; printf("ADC1: %u -> %.3f V, ADC2: %u -> %.3f V\r\n", adc_buf[0], vol1, adc_buf[1], vol2);

换算公式是电压等于原始值乘参考电压除满量程值。G431 是 12 位 ADC,满量程是 4095。参考电压用的是 VDDA,宏定义里是 3.3F。如果外接基准电压,只需要改这一个宏。

串口重定向使用简单的寄存器轮询方式,不走 HAL_UART_Transmit 的阻塞接口,而是直接操作串口数据寄存器,减少库函数的调用开销。在嵌入式场景里 printf 重定向是标配,但要注意 float 格式化需要编译器开启浮点打印支持,否则串口打印出的是乱码或者空值。

4. 避坑记录:G431 ADC 采集中的五个高频翻车现场

4.1 DMA 采出来的全是零或者固定值

现象是串口打印的电压值恒为 0,或者两个通道显示同一个值。

原因多半是 DMA 配置里外设地址没有指向 ADC1 的数据寄存器,或者是 DMA 请求号选错了。G431 的 DMA1 请求映射和 F1 系列不完全相同,不是随便选一个通道就能用。

解决方法是核对 DMA 请求表,ADC1 对应的是 DMA_REQUEST_ADC1,确认外设地址是 &hadc1.Instance->DR。如果这两项没问题,再检查 DMA 中断里是否启用了传输完成中断,虽然只有环形模式也可以不用中断,但保险起见还是打开。

4.2 采集值跳动超过 20 个 LSB

现象是电位器不动,串口打印值在几十个 LSB 范围内抖动。

原因通常有两个:一是采样时间太短,信号源内阻过高导致采样电容充不满;二是电源纹波太大,主板使用 USB 供电时 3.3V 有开关噪声。

解决方法是把采样时间调到 32.5 周期甚至 64.5 周期,同时在 VDDA 引脚附近加一个 100nF 到 1uF 的退耦电容。如果还不行,开启硬件过采样,把 OversamplingRatio 设为 4 或 8,相当于硬件取平均,效果立竿见影。

4.3 HAL_ADC_Start_DMA 卡死在超时里

现象是调用 HAL_ADC_Start_DMA 后程序阻塞,没有返回。

原因是 ADC 时钟没有使能,或者 ADC 状态机卡在某个状态没有复位。CubeMX 生成工程时一般不会漏掉时钟使能,但手动移植代码时容易漏掉 ADC 时钟源选择。

解决方法是检查 ADC 的时钟源是异步还是同步,在 G431 上推荐使用异步时钟模式,即选择 PLLP 或其他独立时钟源,不依赖 AHB 总线的 HCLK,这样可以避免总线频率变化影响 ADC 采样时间。

4.4 串口打印浮点数显示乱码

现象是整数部分正常,小数点后的数据全是问号或者乱码。

原因是编译器默认不链接浮点打印库。在 GCC 环境下需要指定 -u _printf_float 链接选项,在 IAR 和 Keil 下勾选支持浮点格式化的选项。

解决方法是检查工具链的浮点支持开关,没有打开就只能把电压值放大一千倍转成整数再打印,避开浮点格式化。

4.5 多通道顺序错乱或者只采到第一个通道

现象是给通道 2 加电压,打印出来的 adc_buf[1] 不变,但 adc_buf[0] 跟着变。

原因是通道的 Rank 配置有误,两个通道的 Rank 写反了,或者 NbrOfConversion 仍然设成 1,导致 ADC 只转第一个 Rank。

解决方法是检查 NbrOfConversion 是否等于实际通道数,再确认每个通道对应的 Rank 和 Channel 是一次配对设置的。

5. 跑通之后怎么改:从双通道采集到多路高速同步采样

5.1 改成三通道和四通道:改三处配置

工程默认是两个通道,改多路只需动三处:NbrOfConversion 改成 3 或 4,adc_buf 数组长度同步修改,再增加几组 Channel 和 Rank 的配置。代码片段如下:

hadc1.Init.NbrOfConversion = 3; sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = ADC_REGULAR_RANK_1; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_2; sConfig.Rank = ADC_REGULAR_RANK_2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_3; sConfig.Rank = ADC_REGULAR_RANK_3; HAL_ADC_ConfigChannel(&hadc1, &sConfig);

注意 G431 的 ADC 通道号和 GPIO 引脚不是一一对应的,通道 3 可能映射在 PB0 或 PB1 上,需要查数据手册的引脚定义表。另外轮询模式下多一个通道 CPU 占用就高一分,DMA 模式下只要缓冲区够大,几个通道的差别不大。

5.2 双 ADC 同步采样:电机电流采集的典型玩法

G431 的 ADC1 和 ADC2 可以配置成同步模式,由同一个触发源同时启动两个 ADC 的转换,分别采样两路电流。关键配置是把 ADC1 设置为 master,ADC2 设置为 slave,触发源统一由定时器提供。

这个工程里没有打开同步采样,但代码结构已经预留了接口。需要改的是在 ADC2 初始化中把 ExternalTrigConv 配成和 ADC1 相同的触发源,然后在定时器中断或者 DMA 完成中断里同时读取两个 ADC 的转换结果。这样做的意义是两路电流在同一时刻被采样,避免因时间错位导致电流角度计算误差。

5.3 硬件过采样的开关与精度实测

在初始化结构体里配置以下参数:

hadc1.Init.OversamplingMode = ENABLE; hadc1.Init.Oversampling.Ratio = 4; hadc1.Init.Oversampling.RightBitShift = 2;

Ratio 是过采样倍数,RightBitShift 是右移位数。Ratio 4 配合右移 2 位,等效于把 4 次采样的平均作为结果,信噪比提升约 6dB。这种做法比软件多次采样求均值效率高,因为硬件自动完成累加和移位,不占 CPU。

实际测试下来,在同样采样时间下,开 4 倍过采样可以把跳动范围从 20 个 LSB 压到 5 个 LSB 左右。代价是有效转换速率下降,原来是 4Msps,现在等效 1Msps,对大多数电机控制场景足够用了。

5.4 代码移植到自己的工程时的几个习惯

把这份工程里的 ADC 部分移植到自己的 G431 项目时,建议不要直接复制整个 main.c,而是把 ADC 初始化、DMA 初始化和校准函数整理成独立模块,接口对外只暴露一个读取通道电压的函数。这样业务代码不需要关心 DMA 缓冲区里数组下标对应哪个通道。

我从这个项目里学到的习惯是每次上电先跑一遍校准再启动转换,改动 ADC 配置之后用固定电压源验证一下线性度,不要直接信任串口打印的第一个数值。

如果你的需求是实时控制而不是数据记录,建议把 DMA 传输完成中断打开,在中断里做标志位置位,主循环检测到标志后读取数据并清零。这种模式虽然多了一点中断开销,但能保证数据的新鲜度,不至于在低速主循环里读到上一轮甚至上几轮的数据。

希望这份 G431 ADC 的直接与 DMA 采集工程解析能帮你少走几步弯路,跑通之后再回头看我上面提到的几个坑,你会觉得它们其实都是小问题。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询