【动手学STM32G4】(1)STM32G431之创建项目
【动手学STM32G4】(4)STM32G431之PWM输出
【动手学STM32G4】(15)三路互补带死区 PWM 输出
【动手学STM32G4】(16)PWM 触发 ADC 采样
【动手学STM32G4】(16)PWM 触发 ADC 采样
-
- [1. 项目介绍](#1. 项目介绍)
-
- [1.1 实验目的与背景](#1.1 实验目的与背景)
- [1.2 技术原理:PWM 硬件触发 ADC 的同步机制](#1.2 技术原理:PWM 硬件触发 ADC 的同步机制)
- [1.3 实现策略:PWM 触发 ADC 的硬件链路](#1.3 实现策略:PWM 触发 ADC 的硬件链路)
- [1.4 硬件需求与软件架构](#1.4 硬件需求与软件架构)
- [2. CubeMX工程配置](#2. CubeMX工程配置)
-
- [2.1 创建新项目](#2.1 创建新项目)
- [2.2 TIM 配置](#2.2 TIM 配置)
- [2.3 ADC 配置](#2.3 ADC 配置)
- [3. 实验一:测试 TIM1_CH4 触发 ADC1](#3. 实验一:测试 TIM1_CH4 触发 ADC1)
- [4. 实验二:测试 ADC2 和 上位机显示波形](#4. 实验二:测试 ADC2 和 上位机显示波形)
- [5. 实验三:ADC1 三相电流(注入组)采样 + 上位机波形显示(DMA)](#5. 实验三:ADC1 三相电流(注入组)采样 + 上位机波形显示(DMA))
- [6. 小结](#6. 小结)
1. 项目介绍
1.1 实验目的与背景
本项目在上一篇《 【动手学STM32G4】(15)三路互补带死区 PWM 输出》的基础上,我们将实现电机 FOC 控制中的关键环节:PWM硬件触发 ADC 同步采样。
电流环是 FOC 快速响应的核心,而其稳定的前提是获得准确、实时的相电流数据。软件触发或异步采样会引入不可控的延迟和抖动,无法捕捉真实的电流波形。本实验将围绕 "TIM1_CH4比较事件 → 触发ADC1注入组" 这一经典架构,构建一个完全由硬件驱动的精准采样链路,并验证其数据上传与显示功能。
1.2 技术原理:PWM 硬件触发 ADC 的同步机制
在电机 FOC 控制中,电流采样必须与 PWM 波形严格同步。
在三相逆变桥驱动电机时,MOSFET 的高频开关会产生剧烈的电压变化与电磁干扰。如果 ADC 采样时刻与开关瞬间重合,极易捕获到电流尖峰或振铃噪声,导致测量值严重失真,进而导致 FOC 产生错误,引发电机震动、噪音甚至失控。
为解决上述问题,电机控制普遍采用中心对齐PWM模式。在此模式下,定时器计数器先向上计数至ARR值,再向下计数至0,产生对称的 PWM 波形(三角波)。在每个PWM周期的中点附近,存在一个短暂的"电流稳定窗口"。此时上下桥臂均未处于开关瞬态,相电流已趋于平稳,是进行采样的唯一可靠时机。

定时器尤其是 TIM1 高级定时器可以提供精确的时间基准。在 PWM 硬件事件触发 ADC 同步方案中,TIM 配置为通过比较事件(如 CCx)或触发输出(TRGO)在计数器达到周期中点时产生硬件触发信号。ADC 配置为外部触发模式,在接收到该触发信号后立即启动采样与转换过程。PWM 硬件事件触发 ADC 同步方案可以实现:
- 高精度:采样时刻由硬件定时器决定,精度可达纳秒级。
- 零延迟:触发、采样、转换全过程无需CPU干预,无中断响应延迟。
- 强抗扰:完美避开开关噪声,确保采样数据纯净可靠。
1.3 实现策略:PWM 触发 ADC 的硬件链路
- PWM 触发 ADC 采样的核心机制是:
bash
TIMx (PWM)
└─ TRGO / CCx Event
└─ ADC External Trigger
└─ ADC Sample & Convert
└─ DMA → 电流数组
这个过程由硬件自动完成,无需 CPU 参与,可以达到 ns 级精确同步,实现了极高的时效性与确定性。
- STM32的典型实现方案
在 STM32 中,通常采用 "TIM1中心对齐PWM + CCR4触发ADC注入组" 的组合,原因如下:
- 资源独立:CCR4通道不用于PWM输出,专用于产生内部触发事件,不影响驱动波形。
- 时刻精准:通过设置CCR4寄存器的值,可自由、精确地设定在PWM周期内的任意时刻触发。
- 链路直接:TIM1与ADC1同属高级外设,触发路径稳定,相位同步性好。
- 在FOC控制流程中的位置
在每个 PWM 周期内,定时器触发 ADC 完成电流采样后,控制中断或主循环读取 DMA 缓冲区中的电流数据,依次完成 Clarke 变换、Park 变换、电流 PI 调节及 SVPWM 计算,并将更新后的占空比写回 PWM 寄存器以驱动下一周期的电机运行。
该采样机制嵌入整个FOC控制循环,形成如下闭环:
bash
PWM周期开始
↓
到达周期中点 → TIM1_CC4事件触发ADC1
↓
ADC1注入组同步采样三相电流 (Ia, Ib, Ic)
↓
DMA将数据搬运至指定数组
↓
电流环中断或主循环中读取数据
↓
执行Clarke变换、Park变换、PI调节、SVPWM计算
↓
更新PWM占空比,驱动下一周期
1.4 硬件需求与软件架构
本实验使用的硬件:
- 主控板:NUCLEO-G431RB(STM32G431RBT6)
- 驱动板:X-NUCLEO-IHM16M1(需配置为使用内部OPAMP模式)
- 连接方式:两个板卡通过Arduino接口堆叠连接
本实验中硬件外设资源的分工如下:
| 外设 | 工作模式 | 功能说明 |
|---|---|---|
| TIM1 | 中心对齐模式,频率10kHz | CH1/CH1N, CH2/CH2N, CH3/CH3N :输出三相互补PWM CH4 :不输出波形,在计数中点产生比较事件,作为ADC1的硬件触发源。 |
| ADC1 | 注入组模式,外部触发 | 触发源:TIM1_CH4 事件。 采样通道:3路 (I_U / I_V / I_W)。 转换完成后自动触发DMA。 |
| ADC2 | 规则组模式,软件触发 | 用于低速采集母线电压(Vbus)、温度(Temp)。 |
| DMA | 循环模式 | 负责将ADC转换结果自动搬运到内存中的数据数组。 |
| LPUART1 | 虚拟串口 | 与上位机VOFA+通信,上传波形数据,用于实时监控与调试。 |
TIM 输出 和 ADC 采样通道需要根据硬件平台的原理图配置。本文基于 NUCLEO-G431RB 开发板 和 NUCLEO-IHM16M1 驱动板,支持三电阻或单电阻电流采样检测。典型的引脚配置如下图所示。

2. CubeMX工程配置
2.1 创建新项目
-
新建工程。
启动 STM32CubeMX,点击 "Start New Project" (或Ctrl-N快捷键)新建工程,进入 New Project 界面。
选择MCU为 STM32G431RBT6(参考开发板的 MCU 型号选择)。
选择开发板为 NUCLEO-G431RB 开发板。
点击右上角 "Start Project" 创建项目。
-
配置系统时钟树。
(1)点击顶部 "Clock Configuration" 选项卡,进入时钟树配置。
(2)设置输入频率 Input frequency=24 MHz,SysCLK frequency=160 MHz。
配置完成后,System Clock 显示为160MHz,HCLK、PCLK1、PCLK2 均为160MHz。

-
系统配置:在引脚配置(Pinout & Configuration)中,选择 "System Core -- SYS" 。
(1)设置调试器类型,将 Debug 模式设为 "Serial Wire"。
(2)设置基础时钟源(Timebase Source),可以选择默认设置 "SysTick"。
(3)时钟配置:在引脚配置(Pinout & Configuration)中,选择 "System Core -- RCC" 配置时钟模式。
设置高速时钟为外部晶振,将 High Speed Clock (HSE) 设为 "Crystal/Ceramic Resonator"。
Low Speed Clock (LSE) 设为 "Disable"(本实验不需要)。
-
GPIO 配置。
(1)将 LD2(PA5)配置为输出模式 "GPIO_Output"。
(2)将用户按键(PC13)配置为外部中断 "GPIO_EXTI13"。
-
配置虚拟串口(LPUART)
使用串口发送 ADC/DAC 数据到 PC 来显示波形:
启用 LPUART1,模式设为 Asynchronous;波特率设置为 115200;引脚使用默认的 PA2(TX)/PA3(RX),连接到 Nucleo 板上的 ST-LINK 虚拟串口。
用于高频率发送数据到上位机或更新波形时,可以为 LPUART1_TX 分配 DMA 通道。
2.2 TIM 配置
- 配置定时器 TIM1 中断频率为 10kHz,产生 三相互补 PWM 波。

- 配置 TIM1 管脚。
在右侧 Pinout View 图中,点击引脚打开设置选项 → 选择 TIM1_CHx / TIM1_CHxN 对应功能。TIM 具体设置如下:
bash
TIM1_CH1 → PA8
TIM1_CH2 → PA9
TIM1_CH3 → PA10
TIM1_CH1N → PB13
TIM1_CH2N → PB14
TIM1_CH3N → PB15
- 启用并配置TIM1 :
(1)选择 "引脚配置(Pinout & Configuration)",从左侧下拉列表中选择 "Timers -- TIM1 -- TIM1 Mode and Configuration ",配置 TIM1 Mode 如下:
bash
Clock Source 配置为: Internal Clock
Channel-1 配置为: PWM Generation CH1 CH1N(PWM 互补输出)
Channel-2 配置为: PWM Generation CH2 CH2N(PWM 互补输出)
Channel-3 配置为: PWM Generation CH3 CH3N(PWM 互补输出)
Channel-4 配置为: PWM Generation No Output(用于触发ADC采样)
(2)TIM1 计数器参数配置:
预分频 Prescaler(PSC)设为 0,不分频;
计数器模式 Counter Mode 选择中心对齐的三角波:Center Aligned model 1 ;
计数周期 Counter Period 设为 PWM 频率:8000-1;
允许重新加载:auto-reload preload 设为 Enable;
更新时触发一个事件:Trigger Event Selection TRGO 设为 Update Event。
bash
Counter Settings:
Prescaler (PSC-16 bits value): 0
Counter Mode: Center Aligned mode 1
Counter Period (ARR): 7999
Internal Clock Division (CKD): No Division
Repetition Counter (RCR): 0
auto-reloaded preload: Enable
Trigger Output (TRGO) Parameters:
Trigger Event Selection TRGO: Output Compare(OC4REF)
youcans
(3)死区时间参数配置:
在 "Break And Dead Time management - Output Configuration" 选项中:
bash
Break And Dead Time management - Output Configuration:
Automatic Output State: Enable
DeadTime Preload: Enable
Dead Time: 50(计数周期)
(4)TIM1 CH4 参数配置如下:
bash
PWM Generation Channel 4:
Mode: PWM mode 1
Pulse: 4000
Output compare preload: Enable
2.3 ADC 配置
- 根据硬件原理图中的管脚定义,在右侧 Pinout View 图中设置 ADC 管脚如下。并在 NVIC 中使能 ADC1_2 global interrupt。
bash
ADC1_IN2 → PA1(I_U)
ADC1_IN12 → PB1(I_V)
ADC1_IN15 → PB0(I_W)
ADC2_IN11 → PC5(Vbus)
ADC2_IN5 → PC4(Temp)
- 配置 ADC1 注入组 TIM1_CH4 硬件触发同步采样。

ADC1 参数配置如下:
bash
ADCs_Common_Settings
Mode: Independent mode
ADC_Settings:
Clock Prescaler: Synchronous clock mode divide by 4
Resolution: ADC 12 bits Resolution
Data Alignment: Right alignment
Gain Compensation: 0
Scan Conversion Mode: Enabled
End Of Conversion Selection: End of single conversion
Low Power Auto Wait: Disabled
Continuous Conversion Mode: Disabled
Discontinuous Conversion Mode: Disabled
DMA Continuous Requests: Enabled
ADC_Regular_Conversion Mode:
External RegularConversions: Disabled
ADC1 → Injected Conversion Mode:
External Injected Conversions: Enabled
Number Of Conversions: 3
External Trigger Source: Timer 1 Capture Compare 4 event
External Trigger Conversion Edge: Trigger detection on the rising edge
Rank 1:
Channel: Channel 2 (PA1, I_U)
Sampling Time: 6.5 cycles
Offset Number: No offset
Rank 2:
Channel: Channel 12 (PB1, I_V)
Sampling Time: 6.5 cycles
Offset Number: No offset
Rank 3:
Channel: Channel 15 (PB0, I_W)
Sampling Time: 6.5 cycles
Offset Number: No offset
youcans@qq.com
注意: "External Trigger Source: Timer 1 Capture Compare 4 event" 非常重要!!!
- 配置 ADC2 规则组软件触发采样。

ADC2 参数配置如下:
bash
ADCs_Common_Settings
Mode: Independent mode
ADC_Settings:
Clock Prescaler: Synchronous clock mode divide by 4
Resolution: ADC 12 bits Resolution
Data Alignment: Right alignment
ADC2 → Regular Conversion Mode:
External Regular Conversions: Enabled
External Regular Oversampling: Disabled
Number Of Conversions: 2
External Trigger Source: Regular Conversion launched by software
External Trigger Conversion Edge: None
Rank 1:
Channel: Channel 11 (PC5, Vbus)
Sampling Time: 24.5 cycles
Offset Number: No offset
Rank 2:
Channel: Channel 5 (PC4, Temp)
Sampling Time: 24.5 cycles
Offset Number: No offset
Rank 3:
Channel: Channel 15 (PB0, I_W)
Sampling Time: 6.5 cycles
Offset Number: No offset
在 NVIC 中使能 ADC1_2 global interrupt。
2.4 工程配置
- 点击 "Project Manager" 菜单按钮,进入工程配置界面。
- 输入项目名称为 "STM32G431_ADC03",选择项目的保存路径。
- 将Toolchain / IDE 设为 STM32CubeIDE(根据用户安装和使用的 IDE 选择,也可以选择 EWARM、MDK-ARM、MakeFile、CMake 等IDE工具)。
- 点击右上角 "GENERATE CODE" 生成代码。
- 加载完毕后,弹出代码生成提示窗口,点击" OPEN PROJECT",进入 STM32CubeIDE。

3. 实验一:测试 TIM1_CH4 触发 ADC1
在本实验中,只验证 TIM1_CH4 的比较事件,是否真的触发了 ADC1 注入组转换,而不实际进行 ADC 转换。
测试 TIM1_CH4 触发 ADC1,在 ADC1 注入回调中,暂时不读取 ADC 数据,而是只做一件最简单、最可靠的事情
本阶段的目标是验证:TIM1_CH4 比较事件(CC4 event) 能否可靠触发 ADC1 注入组转换,并进入 ADC 回调。
为了避免干扰因素,本实验不读取 ADC 转换结果,而是在 ADC1 注入转换完成回调中执行最简单的动作:翻转板载 LED(LD2,PA5)。如果 LED 引脚出现稳定方波,就表明以下链路完整成立:TIM1 计数运行 → CH4 比较事件产生 → ADC1 注入组被触发 → ADC 中断进入 → 回调函数执行 。
-
在 STM32CubeIDE 打开代码文件 main.c。
代码生成后,已经自动进入 STM32CubeIDE,并打开创建的 STM32G431_DAC01 项目。在 "Core\Src" 目录中,已经生成了 main.c 和 adc.c, tim.c 等基础程序。
-
修改主程序 main.c:
(1)添加用户自定义的参数和变量如下。
c
/* Private define ------------------------------------------------------------*/
/* USER youcans@qq.com CODE BEGIN PD */
#define TIM_CLK_MHz 160
#define PWM_FREQ 10000
#define PWM_HalfPeriod 1000000*TIM_CLK_MHz/PWM_FREQ/2
/* USER CODE END PD */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint16_t adc1_iu = 0; // ADC1_IN2 (PA1) I_U
volatile uint16_t adc1_iv = 0; // ADC1_IN12 (PB1) I_V
volatile uint16_t adc1_iw = 0; // ADC1_IN15 (PB0) I_W
volatile uint8_t adc1_inj_ready = 0;
volatile uint16_t adc2_vbus = 0; // ADC2_IN11 (PC5)
volatile uint16_t adc2_temp = 0; // ADC2_IN5 (PC4)
(2)添加 TIM1 生成 三相互补 PWM 输出的函数如下。
c
void init_TIM1_PWM(void)
{
// 启动 TIM1 三相互补带死区 PWM
HAL_TIM_Base_Start( &htim1); // 启动定时器计数
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动 TIM1_CH1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 启动 TIM1_CH1N
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);
__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, 0.2*PWM_HalfPeriod); // 设置占空比 PWM_CH1
__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_2, 0.4*PWM_HalfPeriod);
__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_3, 0.5*PWM_HalfPeriod);
}
(3)添加 ADC1 注入组硬件触发的初始化函数如下。
c
void init_ADC1_Injected(void)
{
HAL_TIM_Base_Start( &htim1); // 启动定时器计数
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); // 启动 TIM1_CH4 用于产生ADC触发事件
// 校准 ADC
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK) {
Error_Handler();
}
if (HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED) != HAL_OK) {
Error_Handler();
}
// 2) 启动 ADC1 注入组(外部触发 + 中断方式)
if (HAL_ADCEx_InjectedStart_IT(&hadc1) != HAL_OK) {
Error_Handler();
}
__HAL_ADC_ENABLE_IT(&hadc1, ADC_IT_JEOC);
// __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_4,PWM_HalfPeriod-1); // TIM1_CH4 三角波触发 ADC1 采样
}
(4)在外设初始化之后,调用 TIM1 初始化函数 init_TIM1_PWM 和 ADC 初始化函数 init_ADC1_Injected。
c
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_LPUART1_UART_Init();
MX_TIM1_Init();
MX_ADC1_Init();
MX_ADC2_Init();
/* USER youcans@qq.com CODE BEGIN 2 */
init_TIM1_PWM();
init_ADC1_Injected();
/* USER CODE END 2 */
/* Infinite loop */
while (1)
{
}
}
(5)添加 ADC1 回调函数 HAL_ADCEx_InjectedConvCpltCallback如下。本实验不读取 ADC 数据,只在回调中翻转 LED。
c
/* USER CODE BEGIN 4 */
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
// 用 LED2 观察触发节拍
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
}
-
程序编译与下载。
用 USB连接线,连接 NUCLEO-G431RB 开发板。
编译程序,将程序下载烧录到目标板 NUCLEO-G431RB 。
-
实验运行后,用示波器同时观测 PA8 和 PA5 波形如下图所示。
- PA8(TIM1_CH1):黄色,输出 10 kHz PWM,占空比 25% 由 init_TIM1_PWM() 设置 确定。
- PA5(LD2):蓝色,输出约 5 kHz 方波,占空比 50%
TIM1_CH4 触发 ADC1 的频率约为 10 kHz。回调函数每触发一次就翻转一次 LED,两次翻转才构成一个完整周期,因此 LED 引脚波形频率是:10 kHz / 2 = 5 kHz。
这表明 ADC1 注入组已被 TIM1_CH4(CC4 event)以 10 kHz 可靠触发,中断与回调链路工作正常。

4. 实验二:测试 ADC2 和 上位机显示波形
在实验一已经验证 TIM1_CH4 能稳定触发 ADC1 注入组并进入回调的基础上,本实验引入一个完全独立、低速、非实时敏感的采样任务:使用 ADC2 规则组对 Vbus 与温度 Tem 两路模拟量进行采样,并通过 LPUART 虚拟串口将采样结果发送到上位机 VOFA+ 实时显示。
本阶段的验证重点是:
(1)规则组 ADC 的软件触发流程是否正确:Start → Poll → GetValue → Stop
(2)虚拟串口 + VOFA+ 数据可视化链路是否可用:下位机发送浮点数据,上位机连续绘制波形
由于 ADC2 的采样频率设置为低速(例如 100 Hz),且采样放在 while(1) 中轮询执行,因此不涉及中断并发/数据一致性问题,实现与调试都更加直接。
- 主循环中的采样代码
本实验只做轮询读取。在 while(1) 中周期性执行一次"规则组两通道扫描",并将结果发送给 VOFA+。示例代码如下(100 Hz):
c
volatile uint16_t adc2_vbus = 0;
volatile uint16_t adc2_temp = 0;
/* 初始化 VOFA+ 帧尾:00 00 80 7F (frame[3].f=NaN,小端) */
memcpy(frame[3].b, (uint8_t[]){0x00, 0x00, 0x80, 0x7F}, 4);
/* Infinite loop */
while (1)
{
// ---- 启动 ADC2 规则组单次扫描(两通道:Vbus, Temp)
if (HAL_ADC_Start(&hadc2) == HAL_OK)
{
// ADC2_Rank1 转换: Vbus
adc2_vbus = (uint16_t)HAL_ADC_GetValue(&hadc2);
// ADC2_Rank2 转换: Temp
adc2_temp = (uint16_t)HAL_ADC_GetValue(&hadc2);
HAL_ADC_Stop(&hadc2);
}
LPUART_Transmit(); // 发送到 VOFA+(JustFloat,3通道示例)
HAL_Delay(10); // 100Hz
}
- 串口通信代码
串口通信程序 LPUART_Transmit 的代码如下,用于发送到 VOFA+(JustFloat,3通道示例)。
c
// 串口发送缓冲区
// DMA Transmission Buffer: 3 Channels of float = 12 Bytes
typedef union {
float f;
uint8_t b[4];
} FloatBytes;
// One frame of data: 3-CH of float + 4-byte frame tail = 4*4 = 16 bytes
FloatBytes frame[4]; // frame[0]~[2] = 3 waveform channels; frame[3] = frame tail
volatile uint8_t txReady = 1; // 1 = New DMA transmission can be initiated; 0 = Transmission in progress
void LPUART_Transmit(void)
{
// Update the transmission
float Vch1 = adc2_vbus * 3.3f / 4095.0f; //
float Vch2 = 1.0f + adc2_temp * 3.3f / 4095.0f;
float Vch3 = 0.0f;
// New DMA transmission can only be initiated after the previous one is completed
if (txReady)
{
txReady = 0;
frame[0].f = Vch1; // VOFA+CH1
frame[1].f = Vch2; // VOFA+CH2
frame[2].f = Vch3; // VOFA+CH3
// The frame tail at frame[3] is a fixed value youcans@qq.com
// A total of 4 floats (3 channels of data + 1 frame tail), 16 bytes in total.
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)frame, 4 * sizeof(float));
}
}
- 实验现象与结果说明
运行程序后,上位机 VOFA+ 中应能看到 Vbus 通道、Temp 通道两路波形(由于驱动板没有上电,当前采样信号为 0)。这表明 ADC2 规则组软件触发流程正确,且虚拟串口到 VOFA+ 的波形显示链路可用。
同时,由于 ADC2 采样频率较低(100 Hz),且不占用高频中断资源,因此不会破坏实验一 ADC1 10 kHz 注入触发的稳定性。

5. 实验三:ADC1 三相电流(注入组)采样 + 上位机波形显示(DMA)
实验目的与内容
在实验二已经验证 虚拟串口 和 VOFA+ 可视化链路可用的基础上,本实验将显示对象切换为 ADC1 注入组采样得到的三相电流(I_U/I_V/I_W)。
本阶段的核心验证点有两点:
(1)在 ADC1 注入回调中读取三路注入结果(JDR1/JDR2/JDR3),确保每次触发得到一组完整三相数据;
(2)只发送 ADC1 三相数据到 VOFA+,且必须考虑线程安全与数据一致性:
-
ADC1 回调(10 kHz)会持续更新三相值;
-
串口 DMA 发送(例如 100 Hz)在主循环执行;
-
必须避免"IU 是新值、IV/IW 是旧值"的撕裂问题;同时避免 DMA 正在发时被覆盖。
因此,本实验采用 "快照(Snapshot)" 发送节流"策略,适合"高速采样、低速显示"的调试与验证场景。
- 中断里只做采样快照(写入共享结构体 + 置标志);
- 主循环按固定频率读取一致快照并发起 DMA 发送(使用 txReady 控制 DMA 发送状态)/
注:本实验不再发送 ADC2 的 Vbus / Temp,ADC2 可保留采样但不上传,或直接注释掉相关发送代码。
- 主循环中的采样代码
c
/* ADC1 injected latest snapshot (written in ISR, read in main) */
typedef struct {
uint16_t iu;
uint16_t iv;
uint16_t iw;
} adc3_u16_t;
volatile adc3_u16_t adc1_latest = {0};
volatile uint8_t adc1_new = 0; // optional flag
volatile uint8_t txReady = 1; // 1 = New DMA transmission can be initiated
/* Infinite loop */
uint32_t t_last = 0;
while (1)
{
if (HAL_GetTick() - t_last >= 10)
{
t_last = HAL_GetTick();
transmit_LPUART_ADC1(); // 发送到 VOFA+
}
}
- 串口通信代码
串口通信程序 LPUART_Transmit 的代码如下,用于发送到 VOFA+(JustFloat,3通道示例)。
c
/* DMA TX: 3 floats + tail, only when txReady==1 */
void transmit_LPUART_ADC1(void)
{
if (!txReady) return;
/* Thread-safety / consistency: copy a coherent snapshot */
adc3_u16_t snap;
__disable_irq();
snap = adc1_latest;
__enable_irq();
/* Send raw ADC counts as float (0..4095). You can replace with calibrated current later. */
txReady = 0;
frame[0].f = (float)snap.iu * 3.3f / 4095.0f; // VOFA+ CH1
frame[1].f = (float)snap.iv * 3.3f / 4095.0f + 0.1f; // VOFA+ CH2
frame[2].f = (float)snap.iw * 3.3f / 4095.0f - 0.1f; // VOFA+ CH3
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)frame, 4 * sizeof(float));
}
- 实验现象与结果说明
运行程序后,上位机 VOFA+ 中应能看到 I_U、I_V、I_W 通道 3路波形(由于驱动板没有上电,当前采样信号为 0)。
- CH1:I_U(ADC1 注入 Rank1)
- CH2:I_V(ADC1 注入 Rank2)
- CH3:I_W(ADC1 注入 Rank3)
在不接真实电机功率级时,三路电流采样电压通常呈现稳定的直流偏置并叠加一定噪声。如果改变模拟输入(例如改变电流/电压或注入测试信号),三路曲线会同步响应。
为了测试 ADC1 注入组软件触发是否正确,也可以将其中一路先短到 GND、再短到 3.3V,观察采样电压是否变化及显示正常。如下图所示,这表明 ADC1 注入组软件触发流程正确,且虚拟串口到 VOFA+ 的波形显示链路可用。

6. 小结
在 STM32 中,ADC 既可以通过定时器的 TRGO 信号触发,也可以直接由捕获比较事件(CC event)触发。在基于中心对齐 PWM 的电机控制应用中,直接使用 TIMx_CC4 event 触发 ADC 注入组采样,可以精确控制采样相位并避开 PWM 翻转噪声;此时 TRGO 的配置对 ADC 触发不产生影响,使用 Update Event 作为触发源反而可能引入时序不确定性和采样误差。
本文以 STM32G431 为实验平台,围绕 TIM1_CH4 触发 ADC 注入采样这一电机控制中的关键技术点,采用"分阶段、可验证"的工程方法,逐步搭建并验证了从 PWM 定时触发、ADC 同步采样到上位机可视化显示的完整链路:
- 在实验一中,通过 LED 翻转验证,确认了定时器比较事件能够稳定触发 ADC 注入组转换;
- 在实验二中,引入独立、低速的 ADC2 规则组采样任务,验证了软件触发 ADC 及虚拟串口与 VOFA+ 的数据可视化能力;
- 在实验三中,将三相电流采样引入 ADC1 注入回调,并通过快照将高频采样数据安全可靠地传输至上位机,实现了三通道波形的稳定显示。
版权声明:
youcans@qq 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/157330984)
Copyright@youcans 2026
Crated:2026-1