STM32 HAL库ADC多通道DMA扫描模式:采样时序配置与溢出排查实战
一、为什么多通道ADC要配DMA
STM32的ADC在扫描(Scan)模式下会按规则序列依次转换多个通道,每转换完一个通道就产生一次EOC并发出DMA请求。若用中断逐通道读取,CPU需在每次转换后及时搬运DR寄存器,通道多、采样率高时极易丢数据。DMA循环模式可自动把每个通道结果搬到内存数组,CPU零参与,这是多通道采集的标准做法。
但“自动”不等于“无脑”:ADC转换速度与DMA搬运速度之间若出现时序失配,就会触发溢出(OVR),表现为数据错位、数组值跳变。理解时序是排查的前提。
二、采样时序原理
一次ADC转换的总时间由两部分组成:
- 采样时间(Sampling Time):采样电容充电时间,由SMPx位配置,单位ADC时钟周期。
- 转换时间(Conversion Time):12位分辨率固定为12.5个ADC时钟周期。
总转换时间 = (采样周期 + 12.5) / f_ADC。例如f_ADC=12MHz、采样周期=84.5时,单通道约8.1µs。
扫描模式下,序列中每个通道依次转换,每个通道转换结束都会产生一次DMA请求。DMA必须在下一个通道转换完成前把DR读走;若DMA被更高优先级外设长时间占用,或ADC时钟过快而DMA通道优先级过低,DR未被及时读取,新结果覆盖旧结果即产生OVR。
关键配置原则:
- ADC时钟不要超过手册上限(如F1系列14MHz、F4系列36MHz)。
- DMA通道优先级设为High或VeryHigh,避免被抢占。
- 采样时间按信号源内阻选取,内阻大则加长采样时间,兼顾精度与吞吐。
- 使用定时器触发(如TIM_TRGO)而非软件触发,保证采样间隔稳定。
三、CubeMX/HAL配置步骤
- ADC配置为Scan Conversion Mode = Enabled,Continuous Conversion Mode按需(定时触发时关闭)。
- 规则序列填入通道及各自采样时间,Number Of Conversion设为通道数。
- DMA Settings添加ADC请求,Mode选Circular,Data Width选Half Word(12位结果)。
- 触发源选定时器TRGO,配置定时器更新频率为期望采样率。
- 生成代码后调用
HAL_ADC_Start_DMA()启动。
四、完整代码示例
#define ADC_CH_NUM 3
static uint16_t adc_buf[ADC_CH_NUM];
void ADC_DMA_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE; /* 定时器触发 */
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = ADC_CH_NUM;
hadc1.Init.DMAContinuousRequests = ENABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); }
/* 通道0:采样84.5周期 */
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
/* 通道1:内阻较大,采样加长 */
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
sConfig.SamplingTime = ADC_SAMPLETIME_144CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
/* 通道4 */
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = 3;
sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
/* 启动DMA循环采集 */
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_CH_NUM);
}
/* 定时器2产生触发,频率即采样率 */
void TIM2_Trigger_Init(uint32_t sample_rate_hz)
{
__HAL_RCC_TIM2_CLK_ENABLE();
TIM2->PSC = 72 - 1; /* 72MHz -> 1MHz */
TIM2->ARR = (1000000 / sample_rate_hz) - 1;
TIM2->CR2 |= TIM_CR2_MMS_1; /* Update -> TRGO */
TIM2->CR1 |= TIM_CR1_CEN;
}
五、OVR溢出排查清单
出现OVR(HAL_ADC_GetState含ADC_STATE_ERROR,或ADC_ISR_OVR置位)时按以下顺序排查:
- DMA优先级:确认ADC对应DMA通道优先级不低于其他高频外设。
- DMA模式:误配为Normal会在搬完一轮后停止,后续转换全部溢出,必须Circular。
- 采样率与DMA带宽:计算单通道转换时间,确认DMA能在该时间内完成一次搬运。
- 触发频率:定时器ARR过小导致触发过密,降低采样率验证。
- 中断抢占:高优先级中断长时间关中断会延迟DMA响应,检查临界区。
- 数据宽度:DMA配成Word而ADC为12位右对齐,虽不直接致OVR但会错位。
-
清除标志:溢出后需
__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_OVR)并重启DMA,否则卡死。
六、注意事项
- 多通道结果在
adc_buf中按Rank顺序排列,与通道号无直接对应,读取时按序列索引。 - 采样时间过短会导致高内阻信号源采样不足,读数偏小且不稳定,优先加长采样时间而非提高时钟。
- 若需通道间同步,考虑双ADC交替/同步模式,单ADC扫描本质是串行转换。
- 调试时可用
__HAL_ADC_GET_FLAG在DMA完成回调中检查OVR,尽早暴露时序问题。
掌握“转换时间 vs DMA搬运时间”这条主线,多通道DMA采集的时序配置与溢出问题就能系统化定位,而非盲目试参数。
暂无评论,来抢沙发~