STM32F405 ADC规则组+注入组混合采集


  • 📍相关篇《STM32F405 ADC+DMA双缓冲规则组采集》:https://blog.csdn.net/weixin_42880082/article/details/160885937?spm=1011.2415.3001.5331
🔰两种模式对比

在同时使能规则组和注入组,并开启自动转换(JAUTO=1)的模式下,规则组优先被转换
具体流程是:ADC会先完整执行完规则组的所有通道转换,转换结束后自动立即启动注入组的转换,无需外部触发。

模式 转换顺序 适用场景
自动注入 (JAUTO=1) 规则组 → 注入组(固定顺序) 需要规则组和注入组“打包”完成,对时序要求严格的场合
触发注入 (JAUTO=0) 注入组可随时打断规则组(高优先级) 注入组需要在特定时刻(如PWM中点)紧急插入

📘注入组模式下,触发方式

  • 在触发注入模式下(JAUTO = 0),STM32的注入组支持两类外部触发源:定时器事件外部中断线。具体的可用触发源如下:
⏱️ 定时器触发源(最常用)

注入组转换可通过多个定时器的输出事件来启动,支持选择的事件类型包括:

  • 比较/捕获事件:如 TIMx_CCy,在定时器计数值与通道比较值匹配时触发,非常适合在特定PWM位置进行采样(如电机控制)。
  • 触发输出事件:如 TIMx_TRGOTIMx_TRGO2,利用定时器自己的主模式输出,可用来同步触发ADC等多个外设。
🔌 外部中断线触发源

除了定时器,还可以通过 EXTI(外部中断/事件控制器)线 来触发。例如 EXTI Line 15,它能将任意GPIO的边沿信号直接作为ADC的触发源。这种方式逻辑简单,无需配置定时器,但需要注意可能比定时器触发的噪声更大一些。

🎯 触发极性与使能

除了选择触发源,你还可以配置触发极性

  • 上升沿触发
  • 下降沿触发
  • 双边沿触发(上升沿或下降沿都触发)
⚠️ 重要注意事项
  1. 配置时机:外部触发源的选择(JEXTSEL)和极性(JEXTEN)必须在停止注入组转换JADSTART = 0)时进行配置,否则可能无效。
  2. 系列差异:不同STM32系列支持的具体定时器编号和EXTI线不同。例如某些型号支持TIM15触发,而有些则不支持。具体支持哪些,请务必查阅对应型号的参考手册(Reference Manual)
  3. 优先级:一旦启用了外部触发(JEXTEN不为0),硬件触发将生效,此时软件触发通常会被忽略。
💡 典型应用场景
  • 电机控制/FOC:使用PWM定时器的比较事件(如TIM1_CC4)触发注入组,在PWM周期的特定时刻(如下桥臂导通中点)进行电流采样,以避开开关噪声。
  • 变频器/电源:利用定时器的更新事件(TRGO)同步多个ADC同时采样电压和电流。

📒例程配置

  • ADC规则组通道:2个(CH0, CH1) | 注入组通道:4个(CH2, CH3, CH4, CH5)
  • 本项目实现基于STM32F405的6通道ADC数据采集系统,采用规则组DMA传输+注入组中断读取的混合架构,实现高效、稳定的数据采集和串口输出。

主要特性:

  • 规则组(Regular Group):2通道(PA0, PA1),DMA双缓冲传输
  • 注入组(Injected Group):4通道(PA2, PA3, PA4, PA5),中断读取
  • 注入组在规则组转换完成后自动触发
  • 串口实时输出采集结果(含电压转换)

⚙️ 硬件配置
🖥️ MCU
参数 规格
型号 STM32F405RGT6
内核 ARM Cortex-M4
主频 168MHz
⏱️ 时钟配置
参数 设置
时钟源 HSE (8MHz 外部晶振)
PLL_M 4
PLL_N 168
PLL_P 2
PLL_Q 4
SYSCLK 168MHz
HCLK (AHB) 168MHz
PCLK1 (APB1) 42MHz (最大42MHz)
PCLK2 (APB2) 84MHz (最大84MHz)
ADC时钟 21MHz (PCLK2/4)
📊 ADC配置
参数 设置
使用ADC ADC2(独立模式)
规则组通道 CH0 (PA0), CH1 (PA1)
注入组通道 CH2 (PA2), CH3 (PA3), CH4 (PA4), CH5 (PA5)
分辨率 12位(0~4095)
采样时间 28个ADC时钟周期
转换模式 连续扫描模式
自动注入 ENABLE(规则组完成后自动触发注入组)

ADC初始化配置代码(adc.c):

hadc2.Instance = ADC2;                                   // 使用ADC2
hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;    // 时钟分频:PCLK2/4 = 21MHz
hadc2.Init.Resolution = ADC_RESOLUTION_12B;              // 分辨率:12位
hadc2.Init.ScanConvMode = ENABLE;                        // 扫描模式:使能(多通道)
hadc2.Init.ContinuousConvMode = ENABLE;                  // 连续转换模式:使能
hadc2.Init.DiscontinuousConvMode = DISABLE;              // 间断转换模式:禁用
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;  // 外部触发边沿:无
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;        // 触发源:软件触发
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;              // 数据对齐:右对齐
hadc2.Init.NbrOfConversion = 2;                          // 规则组通道数:2个
hadc2.Init.DMAContinuousRequests = ENABLE;               // DMA连续请求:使能
hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;              // EOC选择:序列转换完成

注入组配置代码:

sConfigInjected.InjectedChannel = ADC_CHANNEL_2;         // 注入通道2
 sConfigInjected.InjectedRank = 1;                        // 注入序列位置1
sConfigInjected.InjectedNbrOfConversion = 4;              // 注入组通道数:4个
sConfigInjected.AutoInjectedConv = ENABLE;                // 自动注入:使能
sConfigInjected.ExternalTrigInjecConv = ADC_INJECTED_SOFTWARE_START;
  • 自动注入触发方式有关宏:
#define ADC_EXTERNALTRIGINJECCONV_T1_CC4           0x00000000U
#define ADC_EXTERNALTRIGINJECCONV_T1_TRGO          ((uint32_t)ADC_CR2_JEXTSEL_0)
#define ADC_EXTERNALTRIGINJECCONV_T2_CC1           ((uint32_t)ADC_CR2_JEXTSEL_1)
#define ADC_EXTERNALTRIGINJECCONV_T2_TRGO          ((uint32_t)(ADC_CR2_JEXTSEL_1 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T3_CC2           ((uint32_t)ADC_CR2_JEXTSEL_2)
#define ADC_EXTERNALTRIGINJECCONV_T3_CC4           ((uint32_t)(ADC_CR2_JEXTSEL_2 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T4_CC1           ((uint32_t)(ADC_CR2_JEXTSEL_2 | ADC_CR2_JEXTSEL_1))
#define ADC_EXTERNALTRIGINJECCONV_T4_CC2           ((uint32_t)(ADC_CR2_JEXTSEL_2 | ADC_CR2_JEXTSEL_1 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T4_CC3           ((uint32_t)ADC_CR2_JEXTSEL_3)
#define ADC_EXTERNALTRIGINJECCONV_T4_TRGO          ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T5_CC4           ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_1))
#define ADC_EXTERNALTRIGINJECCONV_T5_TRGO          ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_1 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T8_CC2           ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_2))
#define ADC_EXTERNALTRIGINJECCONV_T8_CC3           ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_2 | ADC_CR2_JEXTSEL_0))
#define ADC_EXTERNALTRIGINJECCONV_T8_CC4           ((uint32_t)(ADC_CR2_JEXTSEL_3 | ADC_CR2_JEXTSEL_2 | ADC_CR2_JEXTSEL_1))
#define ADC_EXTERNALTRIGINJECCONV_EXT_IT15         ((uint32_t)ADC_CR2_JEXTSEL)
#define ADC_INJECTED_SOFTWARE_START                ((uint32_t)ADC_CR2_JEXTSEL + 1U)
🔄 ADC规则组 DMA配置
  • ✨ ADC注入组不支持转换后DMA传输方式。ADC注入组有专门的中断回调函数。
参数 设置
DMA通道 DMA2_Stream2,通道1
数据宽度 半字(16位)
传输模式 正常模式
传输方向 外设到内存
内存增量模式 使能

DMA初始化配置代码(dma.c):

hdma_adc2.Instance = DMA2_Stream2;                       // DMA流:DMA2_Stream2
hdma_adc2.Init.Channel = DMA_CHANNEL_1;                  // DMA通道:通道1
hdma_adc2.Init.Direction = DMA_PERIPH_TO_MEMORY;         // 传输方向:外设到内存
hdma_adc2.Init.PeriphInc = DMA_PINC_DISABLE;             // 外设地址增量:禁用
hdma_adc2.Init.MemInc = DMA_MINC_ENABLE;                 // 内存地址增量:使能
hdma_adc2.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;  // 外设数据宽度:半字(16位)
hdma_adc2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;     // 内存数据宽度:半字(16位)
hdma_adc2.Init.Mode = DMA_NORMAL;                        // 传输模式:正常模式
hdma_adc2.Init.Priority = DMA_PRIORITY_MEDIUM;           // 优先级:中等
hdma_adc2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;          // FIFO模式:禁用
📡 串口配置
参数 设置
串口 USART1
波特率 115200
数据位 8位
停止位 1位
校验

🏗️ 软件架构

✨ 核心功能
  1. 规则组采集:2通道(CH0, CH1),DMA双缓冲传输
  2. 注入组采集:4通道(CH2, CH3, CH4, CH5),中断读取
  3. 自动触发:规则组完成后自动触发注入组转换
  4. 双缓冲机制:规则组使用双缓冲,实现零等待数据交换
  5. 串口输出:转换完成后通过串口打印各通道结果(含电压值)
🔀 工作原理

串行执行流程(推荐配置):

  1. 规则组转换完成 → 自动触发注入组(AutoInjectedConv = ENABLE)
  2. 注入组转换完成 → 在注入组回调中重启规则组DMA转换

完整的转换链:规则组 → 注入组 → 规则组 → 注入组…

┌──────────────────────────────────────────────────────────────────────────┐
│                    规则组+注入组串行采集流程                              │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. 启动规则组DMA转换                                                     │
│       │                                                                 │
│       ↓                                                                 │
│  2. 规则组转换 (CH0, CH1) + DMA传输                                      │
│       │                                                                 │
│       │ DMA完成中断                                                      │
│       ↓                                                                 │
│  3. HAL_ADC_ConvCpltCallback()                                          │
│       │  - 设置new_regular_data_flag = 1                                 │
│       │  - 切换缓冲区                                                     │
│       ↓                                                                 │
│  4. [硬件自动触发] 注入组转换 (CH2, CH3, CH4, CH5)                        │
│       │                                                                 │
│       │ JEOC中断                                                         │
│       ↓                                                                 │
│  5. HAL_ADCEx_InjectedConvCpltCallback()                                 │
│       │  - 读取注入组数据                                                │
│       │  - 设置new_injected_data_flag = 1                                │
│       │  - 启动下一轮规则组DMA转换                                        │
│       ↓                                                                 │
│  6. 主循环检测双标志 → 串口输出规则组+注入组数据                           │
│       ↓                                                                 │
│  循环回到步骤2...                                                        │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

工作时序:

  1. 启动规则组DMA转换(首次)
  2. 规则组完成 → DMA中断 → 设置规则组标志 + 切换缓冲区
  3. 硬件自动触发注入组转换
  4. 注入组完成 → JEOC中断 → 读取注入数据 + 设置注入组标志 + 重启规则组
  5. 主循环检测双标志 → 串口输出
  6. 循环往复
⚡ 中断触发流程
┌──────────────────────────────────────────────────────────────────────┐
│                    规则组+注入组中断触发流程                          │
├──────────────────────────────────────────────────────────────────────┤
│  1. HAL_ADC_Start_DMA() 启动规则组DMA转换                            │
│         ↓                                                           │
│  2. ADC开始规则组转换 (CH0, CH1)                                     │
│         ↓                                                           │
│  3. DMA将规则组数据传输到内存缓冲区                                   │
│         ↓                                                           │
│  4. 规则组完成 → DMA TC中断                                          │
│         ↓                                                           │
│  5. HAL_ADC_ConvCpltCallback()                                      │
│         ↓                                                           │
│  6. 设置 new_regular_data_flag = 1 + 切换缓冲区                       │
│         ↓                                                           │
│  7. [硬件自动触发] 注入组开始转换 (CH2, CH3, CH4, CH5)                │
│         ↓                                                           │
│  8. 注入组完成 → JEOC中断 (注入组中断必须使能)                        │
│         ↓                                                           │
│  9. HAL_ADCEx_InjectedConvCpltCallback()                            │
│         ↓                                                           │
│ 10. 读取注入组数据 + 设置 new_injected_data_flag = 1                 │
│         ↓                                                           │
│ 11. HAL_ADC_Start_DMA() 启动下一轮规则组转换                         │
│         ↓                                                           │
│ 12. 主循环检测双标志 → 读取数据 → 串口输出                           │
└──────────────────────────────────────────────────────────────────────┘

关键配置要点:

配置项 设置 说明
AutoInjectedConv ENABLE 规则组完成后自动触发注入组
ADC_IT_JEOC ENABLE 使能注入组转换完成中断
DMA模式 NORMAL 正常模式,每轮转换完成后需重新启动
🔑 关键变量
变量名 类型 说明
adc_regular_buf[2][2] uint16_t 规则组双缓冲区(2个缓冲区,每区2个通道)
adc_injected_buf[4] uint16_t 注入组数据缓冲区(4个通道)
current_buf uint8_t 当前规则组DMA写入的缓冲区索引(0或1)
new_regular_data_flag uint8_t 规则组新数据标志
new_injected_data_flag uint8_t 注入组新数据标志

📖 使用说明
🔌 硬件连接
信号 MCU引脚 说明
ADC_IN0 PA0 模拟输入通道0
ADC_IN1 PA1 模拟输入通道1
ADC_IN2 PA2 模拟输入通道2
ADC_IN3 PA3 模拟输入通道3
ADC_IN4 PA4 模拟输入通道4
ADC_IN5 PA5 模拟输入通道5
USART1_TX PA9 串口发送(连接USB转TTL模块的RX)
USART1_RX PA10 串口接收(可选,连接USB转TTL模块的TX)
VREF+ VDDA 参考电压输入(连接稳定3.3V)
GND GND 共地
📤 串口输出格式
=== ADC2 规则组数据 (Regular Group) ===
CH0 (PA0): 1270 (1.01V)
CH1 (PA1): 1287 (1.02V)

=== ADC2 注入组数据 (Injected Group) ===
CH2 (PA2): 1262 (1.00V)
CH3 (PA3): 1249 (0.99V)
CH4 (PA4): 1259 (1.00V)
CH5 (PA5): 1306 (1.03V)

------------------------
⚙️ 软件配置

编译环境

  • STM32CubeIDE / Keil MDK
  • STM32Cube HAL库

烧录方式

  1. 使用ST-Link连接开发板
  2. 编译工程生成.hex或.bin文件
  3. 通过烧录工具下载到STM32F405
⚠️ 注意事项
  1. 电压范围:模拟输入电压必须在0~3.3V范围内
  2. 参考电压:VDDA引脚必须连接稳定的3.3V电源,建议添加去耦电容
  3. ADC精度:12位分辨率,电压分辨率约为0.8mV(3.3V/4096)
  4. 串口设置:调试工具波特率需设置为115200
  5. 初始化:主函数中调用 HAL_ADC_Start_DMA()启动采集

💻 核心代码说明
📦 全局变量定义
#define ADC_REGULAR_CHANNEL_NUM  2   // 规则组通道数
#define ADC_INJECTED_CHANNEL_NUM 4   // 注入组通道数

uint16_t adc_regular_buf[2][ADC_REGULAR_CHANNEL_NUM];  // 规则组双缓冲
uint16_t adc_injected_buf[ADC_INJECTED_CHANNEL_NUM];   // 注入组缓冲区
uint8_t current_buf = 0;                                // 当前DMA写入缓冲区索引
uint8_t new_regular_data_flag = 0;                      // 规则组新数据标志
uint8_t new_injected_data_flag = 0;                     // 注入组新数据标志
▶️ 启动ADC转换
// 启动规则组DMA转换(注入组由硬件自动触发)
HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc_regular_buf[current_buf], ADC_REGULAR_CHANNEL_NUM);
//开启注入组中断
HAL_ADCEx_InjectedStart_IT(&hadc2); 
🔔 规则组转换完成回调函数(main.c)
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if(hadc->Instance == ADC2)
    {
        new_regular_data_flag = 1;           // 标记规则组有新数据
        current_buf = 1 - current_buf;       // 切换缓冲区
        // 由于AutoInjectedConv=ENABLE,硬件会自动触发注入组
    }
}
🔔 注入组转换完成回调函数(main.c)
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    if (hadc->Instance == ADC2)
    {
        // 读取注入组各通道数据
        adc_injected_buf[0] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1);
        adc_injected_buf[1] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_2);
        adc_injected_buf[2] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_3);
        adc_injected_buf[3] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_4);

        new_injected_data_flag = 1;          // 标记注入组有新数据

        // 启动下一轮规则组DMA转换(形成串行循环)
        HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc_regular_buf[current_buf], ADC_REGULAR_CHANNEL_NUM);
    }
}
🔄 主循环数据处理
while (1)
{
       // 处理规则组和注入组数据
    if(new_regular_data_flag )
    {
      uint8_t read_buf = 1 - current_buf;
      printf("=== ADC2 规则组数据 (Regular Group) ===\r\n");
      printf("CH0 (PA0): %d (%.2fV)\r\n", adc_regular_buf[read_buf][0], ADC_to_Voltage(adc_regular_buf[read_buf][0]));
      printf("CH1 (PA1): %d (%.2fV)\r\n", adc_regular_buf[read_buf][1], ADC_to_Voltage(adc_regular_buf[read_buf][1]));
      printf("\r\n");
      HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED闪烁指示
      new_regular_data_flag = 0;
    }
     else if(new_injected_data_flag)
     {
      printf("=== ADC2 注入组数据 (Injected Group) ===\r\n");
      printf("CH2 (PA2): %d (%.2fV)\r\n", adc_injected_buf[0], ADC_to_Voltage(adc_injected_buf[0]));
      printf("CH3 (PA3): %d (%.2fV)\r\n", adc_injected_buf[1], ADC_to_Voltage(adc_injected_buf[1]));
      printf("CH4 (PA4): %d (%.2fV)\r\n", adc_injected_buf[2], ADC_to_Voltage(adc_injected_buf[2]));
      printf("CH5 (PA5): %d (%.2fV)\r\n", adc_injected_buf[3], ADC_to_Voltage(adc_injected_buf[3]));
      printf("\r\n");
       new_injected_data_flag = 0;
     }
}
⚡ ADC值转电压函数
float ADC_to_Voltage(uint16_t adc_value)
{
    return (adc_value * 3.3f) / 4095.0f;
}
  • 🎉对于不支持硬件浮点运算内核的MCU(Cortex-M0/M3),可以采用毫伏表示:
uint32_t ADC_to_Voltage_mv(uint16_t adc_value)
{
//定点(保留 3 位小数)
  return ((uint32_t)adc_value*3300)/4096;//单位:毫伏
}

如果需要伏显示输出,使用sprintf函数:

sprintf(buf, "%d.%03d\n", voltage_mv / 1000, voltage_mv % 1000);

📐ADC转换时间计算

STM32系列(Cortex-M3/M4)的ADC总转换时间计算公式(以12位分辨率为例)通常为:

TCONV=(采样时间+12.5)×TADC_CLKT_{CONV} = (采样时间 + 12.5) \times T_{ADC\_CLK}TCONV=(采样时间+12.5)×TADC_CLK

其中12.5是逐次逼近(SAR)ADC内部完成量化所需的内核时钟周期数,固定不变。

🔰不同系列的差异

MCU系列 内核 12位模式转换周期 其他分辨率对应周期
STM32F1 Cortex-M3 12.5个周期 -
STM32F4 Cortex-M4 12.5个周期 10位: 10.5 / 8位: 8.5 / 6位: 6.5
STM32L4 Cortex-M4 12.5个周期 受电源电压影响
STM32G4 Cortex-M4 12.5个周期 支持硬件过采样,会额外增加时间

说明:即使在同一厂商、同一Cortex内核的不同型号中,公式中的常数也可能不同。本项目使用STM32F405,采用12位分辨率,因此转换周期为12.5个ADC时钟周期。

⚡ 性能参数

参数 数值 说明
ADC时钟 21MHz PCLK2=84MHz,分频4
单通道采样时间 28个ADC时钟周期 约1.33μs
单通道总转换时间 40.5个ADC时钟周期 约1.93μs(Tconv = 28 + 12.5)
规则组(2通道)转换时间 ~81个ADC时钟周期 ~3.86μs
注入组(4通道)转换时间 ~162个ADC时钟周期 ~7.71μs
单次完整转换(6通道) ~243个ADC时钟周期 ~11.57μs
理论采样率 ~86.4kSps 6通道扫描模式
实际采样率 ~70-80kSps 受串口输出影响

🔧 故障排除

问题现象 可能原因 解决方法
串口无输出 串口波特率不匹配 确认串口工具波特率为115200
ADC值始终为0 模拟输入未连接或接地 检查模拟输入信号连接
ADC值始终为4095 模拟输入超过3.3V或短路到VDD 检查输入电压范围
ADC值波动大 电源不稳定或未加滤波 增加电源去耦电容,使用RC滤波
数据不更新 DMA配置错误 检查DMA通道、流配置和中断使能
中断不触发 NVIC优先级配置错误 确认ADC和DMA中断已正确配置
缓冲区数据错误 缓冲区索引逻辑错误 检查 current_buf切换逻辑

📚测试源码
通过网盘分享的文件:STM32F405_ADC2_Injected_DMA.rar
链接: https://pan.baidu.com/s/1NDrXLmGOjORGSJtLUs2eFg 提取码: 5a4e
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐