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为例)
- 使能ADC1,选择通道0(PA0)、通道1(PA1)、通道4(PA4)。
- 在
Parameter Settings中:-
Scan Conversion Mode= Enable -
Continuous Conversion Mode= Disable(单次扫描)或Enable(连续扫描) -
DMA Continuous Requests= Enable -
Number of Conversion= 3
-
- 在
Rank列表中按需排序,例如:Rank1=Channel 4,Rank2=Channel 0,Rank3=Channel 1。 - 在
DMA Settings中添加DMA请求:ADC1,方向Peripheral To Memory,优先级High,模式Circular(连续扫描时)或Normal,数据宽度Half Word(12位ADC结果用uint16_t)。 - 生成代码。
三、完整代码示例与陷阱演示
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扫描模式下的采样顺序与缓冲区映射陷阱,确保数据采集准确可靠。
暂无评论,来抢沙发~