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更新的半组数据。
验证方法
- 编译并烧录程序,确保无错误。
- 在调试器中添加变量
v0、v1、v2到Watch窗口。 - 调节PA0引脚电位器旋钮,观察
v0值在0~4095范围内变化。 - 用手遮挡光敏电阻,观察
v1值明显变化。 - 用手指触碰NTC热敏电阻,观察
v2值随温度变化。
判定标准:三个通道数值均随外部模拟量实时变化,且主循环中无需等待ADC转换即可持续读取最新数据,说明DMA连续采样与环形缓冲机制工作正常。