STM32H7 零 CPU 干预 ADC 连续采样:MDMA 与双缓冲的深度配置指南
👁 2 阅读 · 2026-08-27 · 嵌入式
在高速数据采集系统中,CPU 被中断频繁打扰会严重影响实时性。STM32H7 的 MDMA(主 DMA)配合双缓冲机制,可实现 ADC 连续采样数据在内存间的无缝搬运,全程无需 CPU 干预。本文将深入解析 MDMA 与双缓冲的工作原理,详细讲解从 CubeMX 到寄存器级的配置步骤,并给出完整的代码示例与常见陷阱,助你打造高效、低延迟的数据采集系统。
# STM32H7 零 CPU 干预 ADC 连续采样:MDMA 与双缓冲的深度配置指南
## 为什么需要 MDMA + 双缓冲?
STM32H7 的 ADC 支持高达 3.6 Msps 的采样率,若使用普通 DMA 将数据搬运至单一缓冲区,CPU 仍需在每次缓冲区满时处理数据,期间无法执行其他任务。而 MDMA 具备内存到内存的搬运能力,且支持双缓冲(即乒乓缓冲),可让 ADC 数据持续写入一个缓冲区,同时 MDMA 将另一个已满的缓冲区数据搬运至用户指定区域,整个过程无需 CPU 介入,极大提升系统实时性。
## 核心原理:MDMA 与双缓冲的协同
- **MDMA 特点**:支持 8/16/32 位数据宽度,可配置突发传输,拥有独立的 FIFO,能实现内存到内存、外设到内存的搬运,且不占用 CPU 周期。
- **双缓冲机制**:ADC 通过 DMA 请求将数据写入缓冲区 A,当 A 写满后自动切换至缓冲区 B,同时触发 MDMA 将 A 中的数据搬运至目标内存。如此交替,实现数据无缝流转。
- **零 CPU 干预**:MDMA 搬运完成后可产生中断,但 CPU 仅在需要处理数据时被唤醒,而非每次采样都触发中断。
## 配置步骤(基于 STM32H743 + CubeMX)
### 1. ADC 配置
- 开启 ADC1,选择连续转换模式,通道采样时间根据信号源阻抗设置(如 2.5 周期)。
- 在 DMA 设置中,添加 DMA 请求,模式选择 **Circular**,数据宽度设为 **Half Word**(16 位),内存地址递增。
- 注意:ADC 的 DMA 请求必须连接到 MDMA,而非普通 DMA。在 CubeMX 中,将 DMA 请求映射到 MDMA 通道。
### 2. MDMA 配置
- 在 MDMA 设置中,添加一个通道,请求源选择 **ADC1**(或对应 DMA 请求)。
- 设置源地址为 ADC 数据寄存器地址(如 `ADC1->DR`),目标地址为双缓冲区数组首地址。
- 传输模式:**Double Buffer**,缓冲区大小设为 ADC 采样点数(如 1024)。
- 数据宽度:源和目标均为 **Half Word**,突发大小建议设为 4 或 8,以提升效率。
- 使能 **Transfer Complete 中断**,用于通知 CPU 处理数据。
### 3. 双缓冲区内存定义
在链接脚本或代码中定义两个缓冲区,需对齐到 32 字节(MDMA 要求)。
```c
// 双缓冲区,每个 1024 个 16 位数据
__attribute__((aligned(32))) uint16_t buffer_a[1024];
__attribute__((aligned(32))) uint16_t buffer_b[1024];
```
## 完整代码示例
以下代码基于 HAL 库,展示初始化与中断处理。
```c
// 初始化函数
void ADC_MDMA_Init(void)
{
// 1. 启动 ADC DMA 请求(使用 MDMA 通道)
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)buffer_a, 1024);
// 2. 配置 MDMA 双缓冲
MDMA_HandleTypeDef hmdma;
hmdma.Instance = MDMA_Channel0;
hmdma.Init.Request = MDMA_REQUEST_ADC1; // 根据实际映射
hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
hmdma.Init.SourceInc = MDMA_SRC_INC_HALFWORD;
hmdma.Init.DestInc = MDMA_DEST_INC_HALFWORD;
hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_HALFWORD;
hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_HALFWORD;
hmdma.Init.BufferTransferLength = 1024;
hmdma.Init.DoubleBufferMode = MDMA_DOUBLE_BUFFER_MODE_ENABLE;
hmdma.Init.DoubleBufferBaseAddress = (uint32_t)buffer_a;
hmdma.Init.DoubleBufferCurrentBuffer = 0; // 0 表示使用 buffer_a
hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS;
hmdma.Init.DestBurst = MDMA_DEST_BURST_4BEATS;
hmdma.Init.SourceAddress = (uint32_t)&hadc1.Instance->DR;
hmdma.Init.DestAddress = (uint32_t)buffer_a;
HAL_MDMA_Init(&hmdma);
// 3. 使能 MDMA 中断
HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(MDMA_IRQn);
// 4. 启动 MDMA
HAL_MDMA_Start(&hmdma, (uint32_t)&hadc1.Instance->DR, (uint32_t)buffer_a, 1024);
}
// MDMA 中断回调
void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma)
{
// 判断当前缓冲区,处理数据
if (hmdma->Init.DoubleBufferCurrentBuffer == 0)
{
// buffer_a 已填满,处理 buffer_a 数据
ProcessData(buffer_a, 1024);
}
else
{
ProcessData(buffer_b, 1024);
}
}
// 注意:MDMA 中断处理函数中需调用 HAL_MDMA_IRQHandler
void MDMA_IRQHandler(void)
{
HAL_MDMA_IRQHandler(&hmdma);
}
```
## 关键细节与注意事项
- **缓冲区对齐**:MDMA 要求缓冲区地址按 32 字节对齐,否则可能触发总线错误。
- **数据宽度匹配**:ADC 数据寄存器为 16 位,MDMA 源和目标数据宽度必须一致,否则数据错位。
- **中断优先级**:MDMA 中断优先级应高于 ADC 中断,但低于实时性要求更高的任务中断,避免数据覆盖。
- **启动顺序**:务必先启动 ADC DMA,再启动 MDMA,否则 MDMA 可能因无请求而挂起。
- **双缓冲切换**:MDMA 在传输完成时自动切换缓冲区,但需在回调中确认当前缓冲区,避免处理旧数据。
- **缓存一致性**:若使用 D-Cache,需在 MDMA 搬运前执行 `SCB_CleanDCache`,搬运后执行 `SCB_InvalidateDCache`,否则可能读到脏数据。
- **调试技巧**:使用逻辑分析仪观察 MDMA 请求信号,确认 ADC 触发频率与 MDMA 传输速率匹配。
## 总结
通过 MDMA 与双缓冲的结合,STM32H7 能实现 ADC 连续采样数据的零 CPU 干预搬运,显著降低系统负载。本文的配置细节和代码示例可直接应用于实际项目,但需根据具体硬件调整参数。掌握这一技术,你将能轻松应对高速数据采集场景,为后续信号处理留出充足 CPU 资源。