问题现象与背景

在STM32项目中使用ADC多通道扫描+DMA传输时,常出现采样数据与通道不对应的情况。例如配置了IN0、IN1、IN2三个通道,DMA缓冲区却读到buf[0]对应IN1、buf[1]对应IN2、buf[2]对应IN0,或者数据整体偏移一位。这种错位在HAL库中尤为常见,根源多与ADC扫描顺序、DMA传输宽度、HAL回调处理不当有关。

原理讲解:ADC扫描与DMA如何协作

  • ADC扫描模式:使能ScanConvMode后,ADC按Rank顺序依次转换多个通道,每个通道转换结果存入DR寄存器。
  • DMA请求:每次转换结束(EOC)产生DMA请求,DMA将DR内容搬运到内存缓冲区。
  • 关键点:DMA传输宽度必须与ADC数据对齐方式匹配。ADC结果通常为12位,HAL库默认右对齐,存入16位半字。若DMA配置为字节传输,会导致高低字节错位。
  • HAL库回调:HAL_ADC_ConvCpltCallback在DMA传输完成时调用,但若在回调中直接读取缓冲区,需确保DMA传输完成中断已清除,否则可能读到旧数据。

常见错位原因分析

  1. DMA传输宽度不匹配:ADC数据为16位,DMA配置为DMA_PDATAALIGN_BYTE,导致每个通道数据被拆成两个字节,缓冲区错位。
  2. ADC扫描顺序与缓冲区索引不一致:CubeMX中Rank顺序与代码中缓冲区索引未对应,例如Rank1为IN0,但代码认为buf[0]是IN1。
  3. DMA循环模式与中断处理冲突:循环模式下DMA持续搬运,若在回调中处理数据时未考虑半传输/全传输中断,可能读到正在更新的数据。
  4. ADC校准或启动顺序错误:未等待ADC稳定就启动DMA,导致首次转换数据无效,后续数据整体偏移。
  5. HAL库版本差异:部分HAL版本中HAL_ADC_Start_DMA内部未正确配置DMA,需手动检查。

配置步骤与修正方案

步骤1:CubeMX中正确配置ADC与DMA

  • ADC参数:ScanConvMode = ENABLE,ContinuousConvMode = DISABLE(单次扫描)或ENABLE(连续),ExternalTrigConv = ADC_SOFTWARE_START。
  • Rank设置:按实际通道顺序配置Rank1、Rank2...,并记录顺序。
  • DMA设置:Mode = Circular(连续模式)或Normal,Data Width = Half Word(关键!),Increment = Enable。

步骤2:代码中初始化与启动

#define ADC_CH_NUM 3
uint16_t adc_buf[ADC_CH_NUM];

void ADC_Init(void)
{
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = ADC_CH_NUM;
    HAL_ADC_Init(&hadc1);

    // 配置通道Rank
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    sConfig.Channel = ADC_CHANNEL_2;
    sConfig.Rank = 3;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    // 启动DMA
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CH_NUM);
}

步骤3:DMA配置检查

在HAL_ADC_Start_DMA内部会调用HAL_DMA_Start_IT,需确保hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD,MemDataAlignment = DMA_MDATAALIGN_HALFWORD。若CubeMX生成代码有误,手动修改:

hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
HAL_DMA_Init(&hdma_adc1);

步骤4:数据读取与校准

在HAL_ADC_ConvCpltCallback中处理数据,注意此时DMA已完成一轮传输,缓冲区数据有效。若使用连续模式,需在回调中及时复制数据,避免被下一轮覆盖。

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC1) {
        // 此时adc_buf[0]对应Rank1通道,adc_buf[1]对应Rank2...
        // 可在此处进行数据处理
    }
}

排查与修正实战

  • 检查DMA传输宽度:用调试器查看hdma_adc1.Init.PeriphDataAlignment是否为HALFWORD。
  • 验证扫描顺序:在回调中打印adc_buf值,对比已知电压的通道,确认索引对应关系。
  • 使用半传输中断:若数据错位发生在半传输时,可启用HAL_ADC_ConvHalfCpltCallback,分别处理前后半段。
  • 关闭DMA循环模式测试:改为Normal模式,单次采集后停止,排除循环覆盖干扰。
  • 检查ADC校准:在启动前调用HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED),并等待完成。

注意事项

  • 确保DMA缓冲区大小与通道数一致,且为uint16_t类型。
  • 连续模式下,回调中处理数据时间不宜过长,否则可能丢失后续数据。
  • 若使用多个ADC,注意DMA通道冲突,每个ADC需独立DMA。
  • HAL库版本不同,HAL_ADC_Start_DMA内部实现有差异,建议查看对应版本源码确认DMA配置。
  • 调试时可用固定电压输入各通道,通过串口打印缓冲区值,快速定位错位通道。

通过以上原理分析与配置修正,可有效解决STM32 HAL库ADC多通道DMA扫描模式下的数据错位问题,确保采样数据与通道严格对应。