STM32 HAL库ADC多通道DMA扫描模式:采样顺序与缓冲区映射陷阱排查

一、原理讲解:扫描模式与DMA的协作机制

STM32的ADC支持多通道扫描(Scan Mode),按规则组(Regular Group)中预设的顺序依次转换。每个规则通道的转换结果存入同一个数据寄存器(DR),因此必须借助DMA将每次转换结果搬运到内存数组,否则后一次转换会覆盖前一次数据。

关键机制:

  • 规则通道排序:由ADC_RegularChannelConfig()或CubeMX中的Rank设置决定,Rank 1最先转换。
  • DMA请求:每次规则通道转换结束(EOC)产生一次DMA请求,DMA将DR内容搬运到缓冲区。
  • 缓冲区映射:DMA目标地址递增,第N次转换结果存入buffer[N-1]。因此缓冲区索引与Rank顺序严格对应:buffer[0]对应Rank 1,buffer[1]对应Rank 2,依此类推。

常见陷阱根源:开发者误以为缓冲区索引对应通道号(如buffer[0]对应CH0),而实际对应的是转换顺序。若Rank顺序与通道号不一致,数据就会“张冠李戴”。

二、CubeMX配置步骤(以STM32F4为例)

  1. 使能ADC1,选择通道0(PA0)、通道1(PA1)、通道4(PA4)。
  2. 在Parameter Settings中:
    • Scan Conversion Mode = Enable
    • Continuous Conversion Mode = Disable(单次扫描)或Enable(连续扫描)
    • DMA Continuous Requests = Enable
    • Number of Conversion = 3
  3. 在Rank列表中按需排序,例如:Rank1=Channel 4,Rank2=Channel 0,Rank3=Channel 1。
  4. 在DMA Settings中添加DMA请求:ADC1,方向Peripheral To Memory,优先级High,模式Circular(连续扫描时)或Normal,数据宽度Half Word(12位ADC结果用uint16_t)。
  5. 生成代码。

三、完整代码示例与陷阱演示

3.1 正确配置与缓冲区映射

#define ADC_BUF_LEN 3
uint16_t adc_buf[ADC_BUF_LEN]; // [0]=Rank1(CH4), [1]=Rank2(CH0), [2]=Rank3(CH1)

void ADC_Init(void)
{
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 3;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV; // 序列结束才产生EOC
    HAL_ADC_Init(&hadc1);

    // 规则通道排序:Rank1=CH4, Rank2=CH0, Rank3=CH1
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = ADC_CHANNEL_4;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

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

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

    // 启动DMA搬运
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_BUF_LEN);
}

// 在转换完成回调中处理数据
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    // 注意:adc_buf[0]对应CH4,adc_buf[1]对应CH0,adc_buf[2]对应CH1
    uint16_t ch4_val = adc_buf[0];
    uint16_t ch0_val = adc_buf[1];
    uint16_t ch1_val = adc_buf[2];
    // 用户处理...
}

3.2 典型陷阱:缓冲区索引与通道号错位

若开发者按通道号顺序定义缓冲区:

uint16_t adc_buf[3]; // 误以为[0]=CH0, [1]=CH1, [2]=CH4

但实际Rank顺序为CH4→CH0→CH1,则adc_buf[0]得到的是CH4数据,adc_buf[1]是CH0,adc_buf[2]是CH1。若代码按通道号读取,将导致所有数据错位。排查方法:在调试器中观察adc_buf内容,与已知电压对比,确认映射关系。

3.3 陷阱二:DMA传输宽度不匹配

ADC分辨率12位,结果寄存器为16位右对齐。若DMA配置为Word(32位),则每次搬运4字节,但ADC DR仅低16位有效,高16位为0,导致缓冲区数据错乱且长度翻倍。必须配置DMA为Half Word。

3.4 陷阱三:过采样与DMA缓冲区长度

若使能硬件过采样(Oversampling),每个通道的转换结果仍为一个16位值,但转换次数增加。DMA请求次数不变(每个规则通道一次),缓冲区长度仍为通道数。但若误将过采样率当作缓冲区倍数,会导致数组越界。

四、注意事项与调试技巧

  • 始终用Rank顺序映射缓冲区:在代码中显式注释buffer[0]对应哪个通道,避免混淆。
  • 检查DMA数据宽度:hdma_adc1.Init.PeriphDataAlignment和MemDataAlignment均设为HALFWORD。
  • EOC选择:多通道扫描时建议EOCSelection = ADC_EOC_SEQ_CONV,确保整个序列转换完成才触发回调,避免频繁中断。
  • 注入通道干扰:若同时使用注入通道,注入转换会打断规则序列,且注入结果有独立寄存器,不影响规则DMA缓冲区,但需注意触发时机。
  • 调试手段:在HAL_ADC_ConvCpltCallback中设置断点,查看adc_buf数组内容;用已知电压(如VREF/2)接入各通道,验证映射是否正确。
  • 连续模式下的DMA循环:若使用Circular模式,缓冲区会被循环覆盖,需确保处理速度跟上采样率,或使用双缓冲。

通过以上分析与实践,可彻底规避ADC多通道DMA扫描模式下的采样顺序与缓冲区映射陷阱,确保数据采集准确可靠。