问题现象与背景
在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扫描采样的通道数据错位问题,确保数据映射准确可靠。
暂无评论,来抢沙发~