一、引言

在嵌入式系统中,多通道ADC采集是常见需求。STM32的ADC支持扫描模式配合DMA,可自动按顺序转换多个通道并将结果存入内存。但许多开发者在使用HAL库时,常遇到采样数据与通道不对应、缓冲区溢出或数据错位等问题。根源往往在于对扫描顺序和DMA缓冲区对齐的理解不足。本文将系统讲解其原理、配置与陷阱。

二、原理讲解

2.1 ADC扫描模式

  • 扫描模式(Scan Conversion Mode)使能后,ADC按规则组序列中预设的顺序依次转换多个通道。
  • 序列长度由NbrOfConversion设定,每个序列项包含通道号、采样时间等。
  • 转换顺序严格遵循序列Rank 1 → Rank N,与通道编号无关。

2.2 DMA传输机制

  • DMA将ADC数据寄存器(DR)的值搬运到用户缓冲区。
  • 在扫描模式下,每次转换完成产生DMA请求,DMA将数据写入缓冲区下一个位置。
  • 缓冲区大小必须≥序列长度,否则数据覆盖或溢出。

2.3 采样顺序与缓冲区对齐

  • 关键点:DMA写入顺序与ADC转换顺序完全一致。即缓冲区第0个元素对应Rank 1通道,第1个元素对应Rank 2通道,依此类推。
  • 若序列配置与缓冲区索引映射错误,将导致通道数据错位。
  • 对齐问题:ADC数据为12位右对齐(默认),存入16位变量时高位补零。若使用8位缓冲区将丢失精度。

三、CubeMX配置步骤

  1. 使能ADC1,选择多通道(如IN0、IN1、IN2)。
  2. 参数设置:
    • Scan Conversion Mode: Enable
    • Continuous Conversion Mode: Disable(单次扫描)或Enable(连续扫描)
    • DMA Continuous Requests: Enable
    • Number Of Conversion: 3
    • Rank 1: Channel 0,采样时间;Rank 2: Channel 1;Rank 3: Channel 2(顺序可自定义)
  3. DMA Settings:添加DMA请求,模式为Circular或Normal,数据宽度Half Word(16位)。
  4. 生成代码。

四、完整代码示例

#include "main.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

#define ADC_CH_NUM 3
uint16_t adc_buffer[ADC_CH_NUM]; // 缓冲区大小必须≥序列长度

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();

  // 校准ADC
  HAL_ADCEx_Calibration_Start(&hadc1);

  // 启动ADC+DMA,循环模式
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_CH_NUM);

  while (1)
  {
    // 等待转换完成(循环模式下DMA自动覆盖)
    HAL_Delay(100);
    // 此时adc_buffer[0]对应Rank1通道,[1]对应Rank2,[2]对应Rank3
    // 用户处理数据...
  }
}

static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 3;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  // 配置Rank1: 通道0
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  // 配置Rank2: 通道1
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  // 配置Rank3: 通道2
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_3;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_DMA_Init(void)
{
  __HAL_RCC_DMA1_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

五、注意事项与常见陷阱

  • 缓冲区大小必须≥序列长度:若小于,DMA会越界写入,导致内存破坏。
  • 采样顺序与缓冲区索引严格对应:Rank1→buffer[0],Rank2→buffer[1]……修改序列后必须同步调整缓冲区映射。
  • 数据宽度匹配:ADC结果12位,DMA应配置为半字(16位),缓冲区用uint16_t。若用8位将截断数据。
  • 循环模式与正常模式:循环模式下DMA自动重装,适合连续采集;正常模式需手动重启。
  • EOC选择:多通道扫描时,EOCSelection应设为ADC_EOC_SEQ_CONV(序列结束标志),否则DMA请求可能异常。
  • 校准:启动前执行HAL_ADCEx_Calibration_Start提高精度。
  • DMA中断:若使用DMA传输完成中断,注意在回调中处理数据,避免竞争。

六、总结

正确使用STM32 HAL库的ADC多通道DMA扫描模式,关键在于理解采样顺序与缓冲区索引的严格映射关系,并确保缓冲区大小、数据宽度与DMA配置匹配。通过本文的配置步骤和代码示例,开发者可快速实现稳定可靠的多通道采集,避开常见陷阱。