一、引言
在嵌入式系统中,多通道ADC采集是常见需求。STM32的ADC支持扫描模式配合DMA,可自动按顺序转换多个通道并将结果存入内存。但许多开发者在使用HAL库时,常遇到采样数据与通道不对应、缓冲区溢出或数据错位等问题。根源往往在于对扫描顺序和DMA缓冲区对齐的理解不足。本文将系统讲解其原理、配置与陷阱。
二、原理讲解
2.1 ADC扫描模式
- 扫描模式(Scan Conversion Mode)使能后,ADC按规则组序列中预设的顺序依次转换多个通道。
- 序列长度由
NbrOfConversion设定,每个序列项包含通道号、采样时间等。 - 转换顺序严格遵循序列Rank 1 → Rank N,与通道编号无关。
2.2 DMA传输机制
- DMA将ADC数据寄存器(DR)的值搬运到用户缓冲区。
- 在扫描模式下,每次转换完成产生DMA请求,DMA将数据写入缓冲区下一个位置。
- 缓冲区大小必须≥序列长度,否则数据覆盖或溢出。
2.3 采样顺序与缓冲区对齐
- 关键点:DMA写入顺序与ADC转换顺序完全一致。即缓冲区第0个元素对应Rank 1通道,第1个元素对应Rank 2通道,依此类推。
- 若序列配置与缓冲区索引映射错误,将导致通道数据错位。
- 对齐问题:ADC数据为12位右对齐(默认),存入16位变量时高位补零。若使用8位缓冲区将丢失精度。
三、CubeMX配置步骤
- 使能ADC1,选择多通道(如IN0、IN1、IN2)。
- 参数设置:
- Scan Conversion Mode: Enable
- Continuous Conversion Mode: Disable(单次扫描)或Enable(连续扫描)
- DMA Continuous Requests: Enable
- Number Of Conversion: 3
- Rank 1: Channel 0,采样时间;Rank 2: Channel 1;Rank 3: Channel 2(顺序可自定义)
- DMA Settings:添加DMA请求,模式为Circular或Normal,数据宽度Half Word(16位)。
- 生成代码。
四、完整代码示例
#include "main.h"
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
#define ADC_CH_NUM 3
uint16_t adc_buffer[ADC_CH_NUM]; // 缓冲区大小必须≥序列长度
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_CH_NUM);
while (1)
{
// 等待转换完成(循环模式下DMA自动覆盖)
HAL_Delay(100);
// 此时adc_buffer[0]对应Rank1通道,[1]对应Rank2,[2]对应Rank3
// 用户处理数据...
}
}
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
// 配置Rank1: 通道0
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 配置Rank2: 通道1
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 配置Rank3: 通道2
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
五、注意事项与常见陷阱
- 缓冲区大小必须≥序列长度:若小于,DMA会越界写入,导致内存破坏。
- 采样顺序与缓冲区索引严格对应:Rank1→buffer[0],Rank2→buffer[1]……修改序列后必须同步调整缓冲区映射。
-
数据宽度匹配:ADC结果12位,DMA应配置为半字(16位),缓冲区用
uint16_t。若用8位将截断数据。 - 循环模式与正常模式:循环模式下DMA自动重装,适合连续采集;正常模式需手动重启。
-
EOC选择:多通道扫描时,EOCSelection应设为
ADC_EOC_SEQ_CONV(序列结束标志),否则DMA请求可能异常。 -
校准:启动前执行
HAL_ADCEx_Calibration_Start提高精度。 - DMA中断:若使用DMA传输完成中断,注意在回调中处理数据,避免竞争。
六、总结
正确使用STM32 HAL库的ADC多通道DMA扫描模式,关键在于理解采样顺序与缓冲区索引的严格映射关系,并确保缓冲区大小、数据宽度与DMA配置匹配。通过本文的配置步骤和代码示例,开发者可快速实现稳定可靠的多通道采集,避开常见陷阱。
暂无评论,来抢沙发~