文档
STM32 HAL 进阶:DMA + ADC 多通道连续采集
目标
使用 STM32F407 的 ADC1 + DMA 实现 4 通道连续采样(无需 CPU 参与传输),配合 TIM2 触发实现精确采样率。
硬件配置(CubeMX)
- MCU:STM32F407VGT6
- ADC1:4 通道(PA0=CH0, PA1=CH1, PA2=CH2, PA3=CH3)
- DMA2 Stream0:外设到内存,循环模式,半字(16bit)
- TIM2:PWM 输出触发 ADC(10kHz 采样率)
- USART2:115200bps,DMA 发送
完整代码
/* adc_dma.c — ADC + DMA 多通道连续采样 */
#include "main.h"
/* ── 常量 ── */
#define ADC_CHANNELS 4
#define ADC_BUFFER_SIZE (ADC_CHANNELS * 256) /* 每通道 256 个样本 */
#define SAMPLE_RATE_HZ 10000
/* ── DMA 缓冲区 ── */
static __attribute__((aligned(4))) uint16_t adc_buffer[ADC_BUFFER_SIZE];
static volatile uint8_t adc_half_complete = 0;
static volatile uint8_t adc_full_complete = 0;
/* ── 处理后的数据(mV) ── */
static float channel_voltage[ADC_CHANNELS];
/* ══════════════════════════════════════
* 回调:DMA 半传输完成
* ══════════════════════════════════════ */
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
adc_half_complete = 1;
}
}
/* ══════════════════════════════════════
* 回调:DMA 全传输完成
* ══════════════════════════════════════ */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
adc_full_complete = 1;
}
}
/* ══════════════════════════════════════
* 处理 ADC 数据(在 main 中调用)
* ══════════════════════════════════════ */
static void process_adc_data(uint16_t *buf, uint32_t len) {
float sum[ADC_CHANNELS] = {0};
/* 对每通道数据累加 */
uint32_t count_per_channel = len / ADC_CHANNELS;
for (uint32_t ch = 0; ch < ADC_CHANNELS; ch++) {
for (uint32_t i = 0; i < count_per_channel; i++) {
sum[ch] += buf[i * ADC_CHANNELS + ch];
}
/* 平均 + 转换为 mV(12bit ADC,Vref=3.3V) */
float avg_raw = sum[ch] / count_per_channel;
channel_voltage[ch] = (avg_raw * 3300.0f) / 4095.0f;
}
}
/* ══════════════════════════════════════
* 打印采样结果
* ══════════════════════════════════════ */
static void print_results(void) {
char msg[256];
int len = snprintf(msg, sizeof(msg),
"CH0=%.1fmV | CH1=%.1fmV | CH2=%.1fmV | CH3=%.1fmV | Fs=%dHz\r\n",
channel_voltage[0], channel_voltage[1],
channel_voltage[2], channel_voltage[3],
SAMPLE_RATE_HZ);
/* DMA 发送到串口(非阻塞) */
HAL_UART_Transmit_DMA(&huart2, (uint8_t *)msg, len);
}
/* ══════════════════════════════════════
* ADC 初始化(CubeMX 生成的基础上补充)
* ══════════════════════════════════════ */
static void adc_dma_init(void) {
/* 校准 ADC */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
/* 启动 ADC DMA 连续模式 */
HAL_ADC_Start_DMA(&hadc1,
(uint32_t *)adc_buffer,
ADC_BUFFER_SIZE);
/* 启动 TIM2 PWM 触发 ADC */
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
printf("ADC + DMA 初始化完成 | %d 通道 @ %d Hz | 缓冲区 %d 样本\r\n",
ADC_CHANNELS, SAMPLE_RATE_HZ, ADC_BUFFER_SIZE);
}
/* ══════════════════════════════════════
* 主函数
* ══════════════════════════════════════ */
int main(void) {
HAL_Init();
SystemClock_Config(); /* CubeMX */
MX_GPIO_Init(); /* CubeMX */
MX_DMA_Init(); /* CubeMX */
MX_ADC1_Init(); /* CubeMX */
MX_TIM2_Init(); /* CubeMX — TIM2 CH1 → ADC Trigger */
MX_USART2_UART_Init(); /* CubeMX */
adc_dma_init();
while (1) {
/* 处理半缓冲区(无缝处理,不丢数据) */
if (adc_half_complete) {
adc_half_complete = 0;
/* 处理前半部分 */
process_adc_data(adc_buffer, ADC_BUFFER_SIZE / 2);
print_results();
}
if (adc_full_complete) {
adc_full_complete = 0;
/* 处理后半部分 */
process_adc_data(
adc_buffer + ADC_BUFFER_SIZE / 2,
ADC_BUFFER_SIZE / 2);
print_results();
}
/* 用户按键等其他任务... */
}
}
/* ══════════════════════════════════════
* 错误处理
* ══════════════════════════════════════ */
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc) {
if (hadc == &hadc1) {
uint32_t err = HAL_ADC_GetError(hadc);
printf("ADC 错误: 0x%08lx\r\n", err);
/* 重新启动 */
HAL_ADC_Stop_DMA(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, ADC_BUFFER_SIZE);
}
}
关键 CubeMX 设置
| 外设 | 配置 |
|---|---|
| ADC1 | Scan Conversion Mode: Enabled, Continuous Conversion: Disabled, DMA Continuous Requests: Enabled, Number of Conversions: 4 |
| ADC1 Rank 1-4 | Channel 0-3, Sampling Time: 480 Cycles |
| TIM2 | Period: 8399 (84MHz / 8400 = 10kHz), CH1: PWM Mode 1, Pulse: 4200 |
| ADC Trigger | Timer 2 Capture Compare 1 event |
| DMA2 Stream0 | Direction: Peripheral to Memory, Circular, Half Word, Priority: High |
预期行为
- CPU 零参与 ADC 数据搬运(全部 DMA 自动完成)
- 串口以约 20Hz 频率输出 4 通道的电压平均值
- 双缓冲机制:处理前半时 DMA 继续填充后半,零数据丢失
关键点
- 双缓冲(Half/Full callback)是 DMA 连续采集的关键模式——处理 & 采集并行
- TIM 触发 ADC 确保精确等间隔采样(采样抖动 < 1 个时钟周期)
HAL_ADC_Start_DMA启动后无需 CPU 干预,直到回调触发__attribute__((aligned(4)))确保缓冲区对齐满足 DMA 要求