STM32 HAL库ADC多通道DMA扫描模式:采样顺序与注入通道混用配置详解

在多路模拟量采集场景中,ADC多通道DMA扫描是最常用的方案。但很多开发者对“采样顺序到底由谁决定”“注入通道如何插队”“DMA数据怎么和通道对应”等问题一知半解。本文以STM32F4/F1系列为例,讲清原理并给出可直接复用的配置与代码。

一、原理讲解:扫描序列与注入插队

1.1 规则通道扫描顺序

ADC的规则通道组最多16个通道,扫描顺序由SQR寄存器(规则序列寄存器)决定,而非通道号大小。HAL库中通过ADC_ChannelConfTypeDef.Rank指定顺序,Rank=1最先转换。

  • 扫描模式(Scan)使能后,ADC按Rank顺序依次转换所有已配置通道
  • 每个通道转换完成后产生EOC,DMA在每次EOC时搬运一次DR寄存器
  • DMA缓冲区下标与Rank顺序一一对应,与通道号无关

1.2 注入通道的“插队”机制

注入通道组最多4个通道,由JSQR寄存器定义顺序。其核心特性是:

  • 注入转换可被软件(JSWSTART)或外部触发启动
  • 若规则转换正在进行,注入请求会暂停规则序列,先完成注入组,再恢复规则序列
  • 注入结果存放在独立的JDR1~JDR4寄存器,不占用规则DMA通道
  • 注入组可配置为“自动注入”(JAUTO),在规则序列结束后自动执行

这一机制非常适合“慢速轮询+快速事件采样”的混合场景,例如电机控制中规则通道慢速采集温度,注入通道由PWM触发采集母线电流。

二、CubeMX配置步骤

  1. 使能ADC1,勾选Scan Conversion Mode与Continuous Conversion Mode(按需)
  2. Number of Conversion设为规则通道数,例如3
  3. 在Rank列表中依次添加通道并设置采样时间,注意Rank顺序即采样顺序
  4. DMA Settings中添加ADC1的DMA请求,模式选Circular,数据宽度Half Word
  5. 使能Injected Conversion Mode,配置注入通道数与Rank
  6. 注入触发源选择(如Timer 2 Trigger Out),规则触发源按需配置

三、完整代码示例

#include "adc.h"

#define RULE_CH_NUM   3
#define INJ_CH_NUM    2

uint16_t rule_buf[RULE_CH_NUM];   // 与Rank顺序对应
uint16_t inj_buf[INJ_CH_NUM];     // 与注入Rank顺序对应

ADC_HandleTypeDef hadc1;

void ADC_MultiChannel_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode        = ENABLE;
    hadc1.Init.ContinuousConvMode  = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion     = RULE_CH_NUM;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection       = EOC_SEQ_CONV;
    HAL_ADC_Init(&hadc1);

    /* Rank1: CH0 最先采样 */
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank    = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* Rank2: CH1 */
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank    = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* Rank3: CH4 */
    sConfig.Channel = ADC_CHANNEL_4;
    sConfig.Rank    = 3;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* 注入通道配置:CH5、CH6 */
    sConfig.Channel = ADC_CHANNEL_5;
    sConfig.Rank    = ADC_INJECTED_RANK_1;
    HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_6;
    sConfig.Rank    = ADC_INJECTED_RANK_2;
    HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfig);
}

void ADC_Start(void)
{
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)rule_buf, RULE_CH_NUM);
    HAL_ADCEx_InjectedStart_IT(&hadc1);   // 注入组中断方式启动
}

/* 注入组转换完成回调 */
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    if (hadc->Instance == ADC1) {
        inj_buf[0] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1);
        inj_buf[1] = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_2);
        /* 在此处理注入采样结果,如过流保护判断 */
    }
}

四、注意事项与避坑要点

  • DMA缓冲区顺序由Rank决定:rule_buf[0]对应Rank1通道,而非CH0。改通道时务必同步检查Rank。
  • 注入通道不参与规则DMA:注入结果只能通过JDR寄存器或中断回调读取,不要指望它出现在DMA缓冲区。
  • EOCSelection选择:多通道DMA建议用EOC_SEQ_CONV(序列结束触发),否则每次转换都触发DMA,需确认与DMA请求配置匹配。
  • 注入与规则共用采样时间寄存器:同一通道若同时出现在两组,采样时间以最后配置为准。
  • 注入启动时机:HAL_ADCEx_InjectedStart_IT在规则转换进行中调用会立即插队,注意保护关键规则采样。
  • DMA循环模式下的数据一致性:Circular模式下读取缓冲区建议加临界区或双缓冲,避免读到半更新数据。
  • F1与F4差异:F1系列注入通道配置函数与寄存器位定义略有不同,移植时需核对参考手册。

掌握扫描序列与注入插队的本质——顺序由SQR/JSQR寄存器决定,DMA只忠实搬运规则序列结果——就能灵活设计采样调度,满足复杂嵌入式系统的多速率采集需求。