4. 基于DMA的连续采样与环形缓冲

任务说明

本案例实现基于STM32F407的ADC多通道连续采样,通过DMA将转换结果循环写入环形缓冲区,主循环无需等待即可读取最新采样数据。适用于需要持续监测多路模拟信号且对CPU占用率敏感的场景。

硬件连接

  • PA0 — ADC1_IN0(电位器分压)
  • PA1 — ADC1_IN1(光敏电阻分压)
  • PA2 — ADC1_IN2(NTC热敏电阻分压)

实现原理

ADC1配置为扫描模式+连续转换,依次转换通道0、1、2。DMA1数据流1以循环模式将ADC转换结果按固定顺序搬运到内存缓冲区。缓冲区大小为3的整数倍,每次DMA传输完成触发中断,在中断中更新读索引。主循环通过读索引获取最新一组采样值,实现数据采集与处理解耦。

代码实现


#include "stm32f4xx.h"

#define ADC_BUF_LEN  30   // 必须是通道数的整数倍
#define CH_NUM       3

volatile uint16_t adc_buf[ADC_BUF_LEN];
volatile uint32_t dma_write_idx = 0;   // DMA当前写入位置
volatile uint32_t read_idx = 0;        // 主循环读取位置

void ADC1_DMA_Init(void) {
    // 使能时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;

    // PA0/PA1/PA2 模拟输入
    GPIOA->MODER |= (3U << 0) | (3U << 2) | (3U << 4);

    // ADC1 配置
    ADC1->CR2 = 0;                         // 先关闭
    ADC1->CR1 = ADC_CR1_SCAN;              // 扫描模式
    ADC1->CR2 = ADC_CR2_ADON |             // 开启ADC
                ADC_CR2_CONT |             // 连续转换
                ADC_CR2_DMA |              // 使能DMA请求
                ADC_CR2_DDS;               // DMA连续请求
    ADC1->SQR1 = (2U << 20);               // 3个通道
    ADC1->SQR3 = 0;                        // 第1个: ch0
    ADC1->SQR3 |= (1U << 5);               // 第2个: ch1
    ADC1->SQR3 |= (2U << 10);              // 第3个: ch2
    ADC1->CR2 |= ADC_CR2_EOCS;             // 每个通道转换结束置EOC

    // DMA1 数据流1 配置
    DMA1_Stream1->CR = 0;
    DMA1_Stream1->PAR = (uint32_t)&ADC1->DR;
    DMA1_Stream1->M0AR = (uint32_t)adc_buf;
    DMA1_Stream1->NDTR = ADC_BUF_LEN;
    DMA1_Stream1->CR = DMA_SxCR_CHSEL_0 |          // 通道0 = ADC1
                       DMA_SxCR_PL_1 |             // 高优先级
                       DMA_SxCR_MSIZE_0 |          // 16位
                       DMA_SxCR_PSIZE_0 |          // 16位
                       DMA_SxCR_MINC |             // 内存递增
                       DMA_SxCR_CIRC |             // 循环模式
                       DMA_SxCR_TCIE;              // 传输完成中断

    // DMA中断
    NVIC_EnableIRQ(DMA1_Stream1_IRQn);

    // 启动
    DMA1_Stream1->CR |= DMA_SxCR_EN;
    ADC1->CR2 |= ADC_CR2_SWSTART;          // 启动转换
}

void DMA1_Stream1_IRQHandler(void) {
    if (DMA1->HISR & DMA_HISR_TCIF1) {
        DMA1->HIFCR |= DMA_HIFCR_CTCIF1;
        dma_write_idx = ADC_BUF_LEN - DMA1_Stream1->NDTR;
    }
}

// 读取最新一组采样值
uint8_t ADC_ReadLatest(uint16_t *ch0, uint16_t *ch1, uint16_t *ch2) {
    uint32_t latest_end = dma_write_idx;
    if (latest_end < CH_NUM) {
        return 0;   // 数据不足一组
    }
    read_idx = latest_end - CH_NUM;
    *ch0 = adc_buf[read_idx];
    *ch1 = adc_buf[read_idx + 1];
    *ch2 = adc_buf[read_idx + 2];
    return 1;
}

int main(void) {
    ADC1_DMA_Init();
    uint16_t v0, v1, v2;

    while (1) {
        if (ADC_ReadLatest(&v0, &v1, &v2)) {
            // 在此处使用v0/v1/v2,例如换算电压或温度
            // 本案例仅演示数据获取,不进行具体业务处理
        }
    }
}

关键机制说明

DMA以循环模式持续将ADC转换结果写入adc_buf,当写满30个半字后自动回卷。每次传输完成中断时,通过NDTR寄存器反推当前写入位置dma_write_idx。主循环读取时,取dma_write_idx前3个位置即为最新一组完整数据,避免读取到正在被DMA更新的半组数据。

验证方法

  1. 编译并烧录程序,确保无错误。
  2. 在调试器中添加变量v0、v1、v2到Watch窗口。
  3. 调节PA0引脚电位器旋钮,观察v0值在0~4095范围内变化。
  4. 用手遮挡光敏电阻,观察v1值明显变化。
  5. 用手指触碰NTC热敏电阻,观察v2值随温度变化。

判定标准:三个通道数值均随外部模拟量实时变化,且主循环中无需等待ADC转换即可持续读取最新数据,说明DMA连续采样与环形缓冲机制工作正常。

ADC1 DMA连续采样环形缓冲数据流 PA0 (CH0) PA1 (CH1) PA2 (CH2) ADC1 扫描+连续模式 3通道循环转换 DR DMA1 Stream1 循环模式 16位传输 adc_buf[30] CH0 CH1 CH2 CH0 CH1 CH2 CH0 CH1 CH2 … 共10组 … dma_write_idx → 传输完成中断更新写索引 主循环读取最新一组 读索引 = 写索引 - 3 保证读取完整一组数据