STM32 ADC+DMA 多通道连续采样:实现零 CPU 干预的高速数据采集
👁 5 阅读 · 2026-08-16 · STM32 ADC
在 STM32 数据采集系统中,ADC 多通道采样常因 CPU 频繁中断而浪费算力。本文深入讲解如何利用 DMA 配合 ADC 在连续转换模式下自动搬运多通道数据,无需 CPU 干预。文章涵盖原理分析、CubeMX 配置步骤、完整代码示例及常见陷阱,帮助开发者构建高效、稳定的多通道采集链路。
## 为什么需要 ADC + DMA?
STM32 内置 ADC 支持多通道扫描模式,但若在中断中读取数据,每次转换完成都会触发 CPU 中断。当采样率较高或通道较多时,频繁的中断会严重占用 CPU 时间,甚至导致数据丢失。DMA(Direct Memory Access)可以在 ADC 转换完成后直接硬件搬运结果到内存缓冲区,全程无需 CPU 参与。这样 CPU 只需在缓冲区满时处理数据,或直接使用循环缓冲区的实时数据,极大提升系统效率。
## 原理:ADC 连续转换 + DMA 循环模式
**关键机制**:
- **连续转换模式(Continuous Conversion Mode)**:ADC 自动重复扫描所有配置的通道,无需软件触发。
- **DMA 循环模式(Circular Mode)**:DMA 持续将 ADC 数据寄存器(DR)的值搬运到用户缓冲区,当缓冲区满时自动回卷。
- **数据对齐**:ADC 分辨率通常为 12 位,右对齐存储在 16 位半字中,DMA 传输单位设置为 Half Word(16 位)。
当启动 ADC 后,硬件会按照通道顺序逐个转换,每次转换结束触发 DMA 请求,DMA 将 DR 中的数据按序写入内存。整个过程不产生任何中断,CPU 可以执行其他任务,只需在需要时读取缓冲区。
## 配置步骤(以 STM32F103 为例)
### 1. 使用 STM32CubeMX 初始化
- **ADC1**:勾选 `IN0`、`IN1`、`IN2`(对应 PA0、PA1、PA2),设置扫描模式启用,连续转换启用,通道采样时间可调(如 55.5 Cycles)。
- **DMA**:在 ADC1 设置中启用 DMA 请求,选择 `Circular` 模式,数据宽度为 `Half Word`,内存地址自增。
- **外部时钟**:ADC 时钟通常由 APB2 分频得到,建议设为 12 MHz 左右(PCLK2 6 分频)。
- **生成代码**:生成工程后,ADC 和 DMA 句柄已自动初始化。
### 2. 关键代码说明
在 `main.c` 中,用户只需调用 HAL 库函数启动 ADC 的 DMA 传输:
```c
#define ADC_CHANNELS 3
uint16_t adc_buf[ADC_CHANNELS]; // 缓冲区,存放三个通道的采样值
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
// 启动 ADC 并开启 DMA 传输,连续采样自动填充 adc_buf
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CHANNELS);
while (1)
{
// 主循环中可以随时读取 adc_buf[0] ~ adc_buf[2]
// 无需任何中断或轮询 ADC 状态
float v0 = adc_buf[0] * 3.3f / 4095.0f;
float v1 = adc_buf[1] * 3.3f / 4095.0f;
float v2 = adc_buf[2] * 3.3f / 4095.0f;
printf("V0=%.2f V1=%.2f V2=%.2f\r\n", v0, v1, v2);
HAL_Delay(500);
}
}
```
### 3. 自定义回调处理(可选)
若需要在每轮采样完成时做数据处理,可以重写 `HAL_ADC_ConvCpltCallback`:
```c
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
// 这一轮三个通道数据已全部更新到 adc_buf
// 可以在此处做滤波、阈值判断等操作,但注意保持回调短小
adc_average = (adc_buf[0] + adc_buf[1] + adc_buf[2]) / 3;
}
}
```
在循环模式下,每传输完一轮(DMA 半传输或全传输)都会触发回调,具体取决于 DMA 中断配置。如果不需要回调,可以只依赖主循环读取缓冲区。
## 完整代码示例(HAL 库)
下面是一个完整的可运行示例,使用 ADC1 的通道 0~2,DMA 循环模式,读取三路电压值并通过串口打印:
```c
/* Includes */
#include "main.h"
#include "stdio.h"
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
uint16_t adc_buf[3];
uint8_t dma_ready = 0;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
int fputc(int ch, FILE *f) { ITM_SendChar(ch); return ch; }
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3);
while (1)
{
if (dma_ready) // 在回调中置位
{
dma_ready = 0;
printf("ADC0: %d, ADC1: %d, ADC2: %d\r\n", adc_buf[0], adc_buf[1], adc_buf[2]);
}
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
dma_ready = 1; // 通知主循环处理数据
}
}
static void MX_ADC1_Init(void)
{
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; // 3个通道
HAL_ADC_Init(&hadc1);
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1; // 采样顺序1
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
```
注意:`MX_DMA_Init` 中需要将 DMA 请求绑定到 ADC1,示例略去该函数细节,实际由 CubeMX 生成。
## 注意事项与常见问题
- **缓冲区大小**:DMA 缓冲区大小必须等于扫描通道数(或不小于通道数 * 传输次数),否则会越界或数据错位。
- **DMA 模式**:一定要选择 `Circular` 模式。若用 `Normal` 模式,则只能采集一轮,之后必须重新启动 DMA。
- **数据访问冲突**:主循环和回调都可能访问 `adc_buf`,建议加 `volatile` 修饰,或使用双缓冲机制避免读写撕裂。
- **函数耗时**:`HAL_ADC_ConvCpltCallback` 是在 DMA 中断中执行的,不要在里面做耗时操作(如打印、延时),最好只置标志位。
- **ADC 时钟**:ADC 时钟过大可能导致采样不准确,尤其在高阻抗信号源时,应适当降低 ADC 时钟或增加采样周期。
- **多 ADC 同步**:若需要精确同步采样多个通道,可以考虑使用 ADC 的注入组或双 ADC 模式,而非简单扫描。
- **参考电压**:默认使用 VREF+ 作为参考,如果要高精度采集,建议使用外部基准电压源。
## 总结
STM32 ADC + DMA 多通道连续采样方案,充分利用了硬件自动化的能力,将 CPU 从繁重的数据搬运中解放出来。通过合理配置扫描模式、连续转换和 DMA 循环模式,即可实现稳定可靠的多通道实时采集。此方案在电机控制、传感器采集、音频采样等场景均有广泛应用。掌握这一技术,是嵌入式开发者进阶的重要一步。