【动手学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

相关推荐
SundayBear11 小时前
零基础入门MQTT协议
c语言·单片机
嗯嗯=12 小时前
STM32单片机学习篇9
stm32·单片机·学习
小范馆16 小时前
ESP各模组的引脚图-小智接线图
stm32
松涛和鸣16 小时前
DAY63 IMX6ULL ADC Driver Development
linux·运维·arm开发·单片机·嵌入式硬件·ubuntu
想放学的刺客20 小时前
单片机嵌入式试题(第23期)嵌入式系统电源管理策略设计、嵌入式系统通信协议栈实现要点两个全新主题。
c语言·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆20 小时前
【Linux 驱动开发】五. 设备树
linux·arm开发·驱动开发·stm32·嵌入式硬件·mcu·硬件工程
YouEmbedded20 小时前
解码内部集成电路(IIC)与OLED屏
stm32·0.96寸oled·硬件iic·软件模拟iic·图片取模·汉字取模
jghhh0121 小时前
基于上海钜泉科技HT7017单相计量芯片的参考例程实现
科技·单片机·嵌入式硬件
恶魔泡泡糖21 小时前
51单片机外部中断
c语言·单片机·嵌入式硬件·51单片机