【动手学STM32G4】(16)PWM 触发 ADC 精确同步采样

【动手学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 的硬件链路

  1. PWM 触发 ADC 采样的核心机制是:
bash 复制代码
TIMx (PWM)
 └─ TRGO / CCx Event
     └─ ADC External Trigger
         └─ ADC Sample & Convert
             └─ DMA → 电流数组

这个过程由硬件自动完成,无需 CPU 参与,可以达到 ns 级精确同步,实现了极高的时效性与确定性。

  1. STM32的典型实现方案
    在 STM32 中,通常采用 "TIM1中心对齐PWM + CCR4触发ADC注入组" 的组合,原因如下:
  • 资源独立:CCR4通道不用于PWM输出,专用于产生内部触发事件,不影响驱动波形。
  • 时刻精准:通过设置CCR4寄存器的值,可自由、精确地设定在PWM周期内的任意时刻触发。
  • 链路直接:TIM1与ADC1同属高级外设,触发路径稳定,相位同步性好。
  1. 在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 硬件需求与软件架构

本实验使用的硬件:

  1. 主控板:NUCLEO-G431RB(STM32G431RBT6)
  2. 驱动板:X-NUCLEO-IHM16M1(需配置为使用内部OPAMP模式)
  3. 连接方式:两个板卡通过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 创建新项目

  1. 新建工程。

    启动 STM32CubeMX,点击 "Start New Project" (或Ctrl-N快捷键)新建工程,进入 New Project 界面。

    选择MCU为 STM32G431RBT6(参考开发板的 MCU 型号选择)。

    选择开发板为 NUCLEO-G431RB 开发板。

    点击右上角 "Start Project" 创建项目。

  2. 配置系统时钟树。

    (1)点击顶部 "Clock Configuration" 选项卡,进入时钟树配置。

    (2)设置输入频率 Input frequency=24 MHz,SysCLK frequency=160 MHz。

    配置完成后,System Clock 显示为160MHz,HCLK、PCLK1、PCLK2 均为160MHz。

  1. 系统配置:在引脚配置(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"(本实验不需要)。

  2. GPIO 配置。

    (1)将 LD2(PA5)配置为输出模式 "GPIO_Output"。

    (2)将用户按键(PC13)配置为外部中断 "GPIO_EXTI13"。

  3. 配置虚拟串口(LPUART)

    使用串口发送 ADC/DAC 数据到 PC 来显示波形:

    启用 LPUART1,模式设为 Asynchronous;波特率设置为 115200;引脚使用默认的 PA2(TX)/PA3(RX),连接到 Nucleo 板上的 ST-LINK 虚拟串口。

    用于高频率发送数据到上位机或更新波形时,可以为 LPUART1_TX 分配 DMA 通道。

2.2 TIM 配置

  1. 配置定时器 TIM1 中断频率为 10kHz,产生 三相互补 PWM 波。
  1. 配置 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
  1. 启用并配置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 配置

  1. 根据硬件原理图中的管脚定义,在右侧 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)
  1. 配置 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" 非常重要!!!

  1. 配置 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 工程配置

  1. 点击 "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 中断进入 → 回调函数执行

  1. 在 STM32CubeIDE 打开代码文件 main.c。

    代码生成后,已经自动进入 STM32CubeIDE,并打开创建的 STM32G431_DAC01 项目。在 "Core\Src" 目录中,已经生成了 main.c 和 adc.c, tim.c 等基础程序。

  2. 修改主程序 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);
    }
}
  1. 程序编译与下载。

    用 USB连接线,连接 NUCLEO-G431RB 开发板。

    编译程序,将程序下载烧录到目标板 NUCLEO-G431RB 。

  2. 实验运行后,用示波器同时观测 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) 中轮询执行,因此不涉及中断并发/数据一致性问题,实现与调试都更加直接。

  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
  }
  1. 串口通信代码
    串口通信程序 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));
	}
}
  1. 实验现象与结果说明

运行程序后,上位机 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 可保留采样但不上传,或直接注释掉相关发送代码。

  1. 主循环中的采样代码
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+
    }
  }
  1. 串口通信代码
    串口通信程序 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));
}
  1. 实验现象与结果说明

运行程序后,上位机 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

相关推荐
Hello_Embed1 小时前
libmodbus 移植 STM32(USB 串口后端篇)
笔记·stm32·单片机·嵌入式·freertos·libmodbus
VekiSon2 小时前
Linux内核驱动——杂项设备驱动与内核模块编译
linux·c语言·arm开发·嵌入式硬件
来自晴朗的明天3 小时前
14、光耦隔离电路(EL3H7)
单片机·嵌入式硬件·硬件工程
G***技3 小时前
杰和IB3-272:以低功耗高性能打造新一代工业智能交互核心
单片机·嵌入式硬件·物联网
czhaii4 小时前
STC AI8052U单片机特点
单片机
MAR-Sky5 小时前
keil5中数据的不同定义和单片机(以stc8为例)里的对应关系(idata,xdata,data,code)
单片机·嵌入式硬件
项目題供诗7 小时前
51单片机入门(八)
单片机·嵌入式硬件·51单片机
羽获飞7 小时前
从零开始学嵌入式之STM32——9.STM32的时钟系统
stm32·单片机·嵌入式硬件
飞睿科技8 小时前
乐鑫智能开关方案解析:基于ESP32-C系列的低功耗、高集成设计
嵌入式硬件·物联网·esp32·智能家居·乐鑫科技
来自晴朗的明天9 小时前
13、NMOS 电源防反接电路
单片机·嵌入式硬件·硬件工程