问题现象与背景

在STM32项目中使用ADC多通道扫描+DMA搬运时,常出现“通道数据错位”:例如配置了IN0、IN1、IN2三个通道,DMA缓冲区却读到buf[0]对应IN1、buf[1]对应IN2、buf[2]对应IN0,或数据整体偏移一位。该问题在HAL库中尤为常见,根源多与DMA配置、规则组顺序、采样时间及缓冲区对齐有关。

原理讲解:ADC扫描与DMA如何配合

  • 扫描模式(Scan):ADC按规则组(Rank)顺序依次转换多个通道,每个通道转换完成后产生EOC事件。
  • DMA请求:使能ADC的DMA连续请求(ADC_CFGR_DMAEN)后,每次EOC触发一次DMA传输,将DR寄存器内容搬运到内存。
  • 数据宽度:ADC_DR为32位寄存器,但实际转换结果位宽由分辨率决定(12位时低12位有效)。DMA外设数据宽度应配置为HAL_DMA_PDATAALIGN_WORD(32位)或HAL_DMA_PDATAALIGN_HALFWORD(16位),需与内存宽度匹配。
  • 关键点:DMA传输次数等于规则组通道数,且传输顺序严格遵循Rank顺序。若Rank配置与预期不符,或DMA缓冲区长度/对齐错误,就会错位。

常见错位原因排查清单

  • 规则组顺序配置错误:ADC_ChannelConfTypeDef.Rank未按预期递增,或重复Rank导致覆盖。
  • DMA缓冲区长度不足:HAL_ADC_Start_DMA的Length参数小于通道数,导致数据截断或回绕。
  • DMA数据宽度不匹配:外设宽度与内存宽度不一致(如外设Word、内存HalfWord),造成地址步进错误。
  • 采样时间过短:高阻抗信号源采样不足,转换结果异常,被误判为错位。
  • DMA循环模式与中断冲突:循环模式下未及时处理半满/全满中断,数据被覆盖。
  • ADC校准未执行:未调用HAL_ADCEx_Calibration_Start,导致首通道偏差。

配置步骤与完整代码示例

以下以STM32F4系列为例,配置ADC1的IN0、IN1、IN2三个通道,DMA2 Stream0循环搬运。

1. CubeMX关键配置

  • ADC1:Scan Conversion Mode = Enable,Continuous Conversion Mode = Enable,DMA Continuous Requests = Enable,Number Of Conversion = 3。
  • Rank1:Channel 0,采样时间 84 cycles;Rank2:Channel 1;Rank3:Channel 2。
  • DMA2 Stream0:Direction = Peripheral To Memory,Mode = Circular,Data Width = Word/Word,Priority = High。

2. 完整代码

#include "main.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

#define ADC_CH_NUM 3
uint32_t adc_buf[ADC_CH_NUM];  // 必须32位对齐,与DMA内存宽度一致

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

    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.NbrOfDiscConversion = 0;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = ADC_CH_NUM;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); }

    // Rank1: IN0
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); }

    // Rank2: IN1
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 2;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); }

    // Rank3: IN2
    sConfig.Channel = ADC_CHANNEL_2;
    sConfig.Rank = 3;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); }
}

void DMA_Init(void)
{
    __HAL_RCC_DMA2_CLK_ENABLE();
    hdma_adc1.Instance = DMA2_Stream0;
    hdma_adc1.Init.Channel = DMA_CHANNEL_0;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) { Error_Handler(); }
    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}

void ADC_Start(void)
{
    HAL_ADCEx_Calibration_Start(&hadc1);
    if (HAL_ADC_Start_DMA(&hadc1, adc_buf, ADC_CH_NUM) != HAL_OK) {
        Error_Handler();
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    ADC1_Init();
    DMA_Init();
    ADC_Start();

    while (1) {
        // adc_buf[0] -> IN0, adc_buf[1] -> IN1, adc_buf[2] -> IN2
        HAL_Delay(100);
    }
}

修复与验证方法

  • 核对Rank顺序:在调试器中查看ADC1->SQR1/SQR2/SQR3寄存器,确认SQ1~SQ3与通道对应关系。
  • 检查DMA传输计数:在DMA完成中断中读取__HAL_DMA_GET_COUNTER(&hdma_adc1),确认每次循环传输3次。
  • 验证数据对齐:将adc_buf定义为__attribute__((aligned(4))),确保32位对齐。
  • 增加采样时间:对高阻抗信号源,将采样时间提升至480 cycles,排除采样不足导致的异常。
  • 单步调试:在DMA传输完成回调中打印三个通道值,与已知电压对比,确认映射关系。

注意事项

  • DMA缓冲区必须为全局或静态变量,避免栈上分配导致地址失效。
  • 循环模式下数据覆盖:若处理速度慢,可改用Normal模式+定时重启,或增大缓冲区并采用双缓冲。
  • ADC时钟不超过规格:如F4系列ADC时钟≤36MHz,超频会导致转换错误。
  • HAL_ADC_Start_DMA的Length参数:必须等于规则组通道数,否则DMA传输次数错误。
  • 多ADC共用DMA:若使用ADC1/ADC2同时DMA,需确保DMA流不冲突,并分别配置。

通过以上排查与修复,可彻底解决HAL库下ADC多通道DMA扫描采样的通道数据错位问题,确保数据映射准确可靠。