进阶:DMA + ADC 多通道连续采集

知识库
知识库文档
/tech-stacks/stm32-hal/examples/进阶:DMA + ADC 多通道连续采集.md

文档

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 要求

信息

路径
/tech-stacks/stm32-hal/examples/进阶:DMA + ADC 多通道连续采集.md
更新时间
2026/5/31