一、引言
在嵌入式开发中,使用STM32的ADC采集多路模拟信号是常见需求。为了高效、不占用CPU,通常会启用ADC的扫描模式(Scan Mode)配合DMA进行多通道数据搬运。然而,很多开发者在使用HAL库时,会遇到数据顺序错乱、通道与数据不对应、甚至数据完全错误的问题。这往往源于对ADC扫描序列、DMA传输对齐以及数据宽度匹配的理解不够深入。本文将系统讲解这些陷阱,并提供完整的解决方案。
二、ADC扫描模式与采样顺序原理
2.1 规则组序列
STM32的ADC规则组支持最多16个通道的序列转换。通过ADC_RegularChannelConfig()(HAL库中为HAL_ADC_ConfigChannel())可以配置每个序列位置对应的通道和采样时间。序列的转换顺序由ADC_SQRx寄存器中的SQ1[4:0]到SQ16[4:0]决定,SQ1最先转换。
关键点:DMA搬运的数据顺序严格遵循规则组的转换顺序,即第一个数据对应SQ1配置的通道,第二个对应SQ2,依此类推。
2.2 扫描模式与连续转换
- 扫描模式(Scan Mode):使能后,ADC依次转换规则组中的所有通道。
- 连续转换(Continuous Conversion):使能后,ADC在完成一轮扫描后自动开始下一轮,无需软件触发。
- DMA循环模式(Circular):使能后,DMA在传输完指定数据量后自动重新开始,适合连续采样。
三、数据对齐陷阱
3.1 数据对齐方式
ADC转换结果可以左对齐或右对齐(12位分辨率下)。HAL库中通过ADC_InitTypeDef的DataAlign成员配置:
-
ADC_DATAALIGN_RIGHT:右对齐,低12位有效,高4位为0。 -
ADC_DATAALIGN_LEFT:左对齐,高12位有效,低4位为0。
陷阱:若使用DMA搬运16位数据(uint16_t),右对齐时直接读取即可;但若配置为左对齐,读取的值需要右移4位才能得到真实电压值。许多开发者忘记移位,导致数据看似“放大”了16倍。
3.2 DMA数据宽度匹配
DMA外设数据宽度(Peripheral Data Size)必须与ADC数据寄存器宽度(16位)一致,通常设置为DMA_PDATAALIGN_HALFWORD。内存数据宽度(Memory Data Size)也应为半字(DMA_MDATAALIGN_HALFWORD),且目标数组类型应为uint16_t。若内存宽度设置为字节(BYTE),则每个16位ADC结果会被拆分成两个字节,导致数据错位。
陷阱:DMA传输完成中断中,若数组定义为uint8_t,数据将无法正确解析。
四、配置步骤与完整代码示例
4.1 硬件与软件环境
- STM32F407ZGT6
- STM32CubeMX + HAL库
- ADC1,通道0(PA0)、通道1(PA1)、通道2(PA2)
- DMA2 Stream0 Channel0
4.2 CubeMX配置要点
- 使能ADC1,勾选Scan Mode、Continuous Conversion Mode、DMA Continuous Requests。
- 在规则组中按顺序添加通道0、1、2,设置采样时间。
- 使能DMA,模式为Circular,数据宽度均为Half Word。
- 生成代码。
4.3 代码实现
#include "main.h"
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
#define ADC_CHANNELS 3
uint16_t adc_buffer[ADC_CHANNELS]; // 必须为uint16_t
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
// 校准ADC
HAL_ADCEx_Calibration_Start(&hadc1);
// 启动ADC DMA传输
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_CHANNELS);
while (1)
{
// 主循环可处理其他任务,DMA自动搬运数据
HAL_Delay(100);
}
}
static void MX_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.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐
hadc1.Init.NbrOfConversion = 3; // 3个转换
hadc1.Init.DMAContinuousRequests = ENABLE; // DMA连续请求
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV; // 序列转换完成标志
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
// 配置通道0为序列1
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 配置通道1为序列2
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 配置通道2为序列3
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_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_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}
4.4 数据读取与验证
在adc_buffer中,索引0对应通道0,索引1对应通道1,索引2对应通道2。可通过串口打印验证:
printf("CH0: %d, CH1: %d, CH2: %d\n", adc_buffer[0], adc_buffer[1], adc_buffer[2]);
五、常见陷阱与注意事项
-
陷阱1:DMA内存地址递增未使能。若
MemInc为DISABLE,所有通道数据会覆盖同一地址,导致只有最后一个通道数据。务必使能DMA_MINC_ENABLE。 -
陷阱2:数据宽度不匹配。ADC数据寄存器为16位,DMA内存宽度必须为半字,目标数组必须为
uint16_t。若使用uint8_t数组,数据将错位。 - 陷阱3:采样顺序与数组索引不对应。务必确保规则组序列配置顺序与代码中数组索引的预期一致。建议使用宏定义或枚举明确通道顺序。
-
陷阱4:未校准ADC。启动前调用
HAL_ADCEx_Calibration_Start()可提高精度。 - 陷阱5:DMA传输完成中断处理不当。若使用中断,注意在回调函数中及时处理数据,避免覆盖。
-
陷阱6:连续转换模式下EOC标志选择。
EOCSelection应设置为ADC_EOC_SEQ_CONV(序列转换完成),否则DMA可能提前触发。 - 陷阱7:左对齐数据未移位。若配置为左对齐,读取值需右移4位(12位分辨率)才能得到正确结果。
六、总结
多通道ADC+DMA扫描模式是STM32嵌入式开发中的高效方案,但采样顺序与数据对齐的细节极易出错。掌握规则组序列的配置逻辑、DMA数据宽度匹配、对齐方式的影响,并遵循本文的配置步骤与注意事项,可以避免绝大多数隐蔽bug。建议在调试时先用单通道验证,再逐步增加通道,并利用调试器观察adc_buffer内容,确保数据与通道一一对应。