STM32H7双DMA引擎实战:MDMA+LPDMA构建外设到RAM零拷贝流水线
👁 5 阅读 · 2026-08-27 · 嵌入式
STM32H7系列凭借双核与高带宽总线架构,为高性能嵌入式系统提供了强大支持。然而,传统外设数据搬运常受限于CPU干预与单DMA瓶颈。本文深入解析MDMA与LPDMA的协同工作原理,通过双DMA级联实现外设到RAM的零拷贝流水线设计,显著降低CPU负载并提升数据吞吐率。文章涵盖架构原理、配置步骤、完整代码示例及关键注意事项,助力开发者释放H7的极致性能。
# 引言
在高速数据采集或通信系统中,外设数据频繁涌入内存,若每次中断都唤醒CPU搬运,不仅浪费算力,还可能导致实时性下降。STM32H7内置的MDMA(主DMA)与LPDMA(低功耗DMA)提供了多级DMA链式传输能力,可实现外设→LPDMA→MDMA→RAM的零拷贝流水线,让CPU彻底从数据搬运中解放。
# 一、原理剖析:双DMA如何协同
## 1.1 总线架构与DMA角色
STM32H7采用AXI总线矩阵,MDMA连接在AXI主接口上,可访问全部存储器与外设;LPDMA则挂在APB总线域,适合低速外设(如UART、SPI)。两者通过硬件握手信号(如MDMA的请求信号)可级联工作:
- **LPDMA**:负责从外设接收数据块,存入中间缓冲区(SRAM)。
- **MDMA**:检测到LPDMA传输完成事件后,自动将中间缓冲区数据搬运至目标RAM(如D1域SRAM或外部SDRAM)。
这种设计避免了CPU参与每一字节的搬运,且MDMA的突发传输能力(支持8/16/32字节突发)极大提升了总线利用率。
## 1.2 零拷贝的关键:双缓冲与硬件握手
零拷贝意味着数据从外设到最终RAM不经过CPU寄存器或额外复制。实现要点:
- 使用LPDMA的循环模式或双缓冲模式,确保外设数据连续写入。
- 配置MDMA的Linked List(链表)模式,使MDMA能自动处理多个传输描述符,无需CPU干预。
- 通过LPDMA的传输完成中断触发MDMA启动,或直接使用硬件事件链接(如LPDMA的通道事件输出连接至MDMA的请求输入)。
# 二、硬件与软件准备
- 硬件:STM32H743开发板(或类似),外设如UART或ADC。
- 软件:STM32CubeIDE + HAL库(或LL库)。
- 本文以UART接收为例,演示LPDMA接收数据,MDMA搬运至大缓冲区。
# 三、配置步骤
## 3.1 使能时钟与GPIO
```c
__HAL_RCC_DMA1_CLK_ENABLE(); // LPDMA通常为DMA1
__HAL_RCC_MDMA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
// 配置UART引脚等(略)
```
## 3.2 配置LPDMA(以DMA1为例)
```c
DMA_HandleTypeDef hdma_uart;
hdma_uart.Instance = DMA1_Stream0;
hdma_uart.Init.Request = DMA_REQUEST_USART1_RX;
hdma_uart.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_uart.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_uart.Init.MemInc = DMA_MINC_ENABLE;
hdma_uart.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_uart.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_uart);
__HAL_LINKDMA(&huart1, hdmarx, hdma_uart);
HAL_UART_Receive_DMA(&huart1, intermediate_buf, BUF_SIZE);
```
## 3.3 配置MDMA(链表模式)
```c
MDMA_HandleTypeDef hmdma;
MDMA_LinkNodeTypeDef node1, node2;
// 初始化MDMA
hmdma.Instance = MDMA_Channel0;
hmdma.Init.Request = MDMA_REQUEST_DMA1_TC; // 由DMA1传输完成触发
hmdma.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER;
hmdma.Init.Priority = MDMA_PRIORITY_HIGH;
hmdma.Init.Endianness = MDMA_LITTLE_ENDIANNESS;
hmdma.Init.SourceInc = MDMA_SRC_INC_WORD;
hmdma.Init.DestinationInc = MDMA_DEST_INC_WORD;
hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK;
hmdma.Init.BufferTransferLength = BUF_SIZE/4; // 以字为单位
HAL_MDMA_Init(&hmdma);
// 配置链表节点(双缓冲交替)
node1.SourceAddress = (uint32_t)intermediate_buf;
node1.DestinationAddress = (uint32_t)final_buf;
node1.BlockDataLength = BUF_SIZE/4;
node1.LinkAddress = (uint32_t)&node2;
node2.SourceAddress = (uint32_t)intermediate_buf + BUF_SIZE/2;
node2.DestinationAddress = (uint32_t)final_buf + BUF_SIZE/2;
node2.BlockDataLength = BUF_SIZE/4;
node2.LinkAddress = 0; // 结束
HAL_MDMA_Start_IT(&hmdma, (uint32_t)&node1, NULL, NULL);
```
## 3.4 启动流水线
- 先启动MDMA,等待LPDMA事件。
- 再启动UART的DMA接收。
```c
HAL_MDMA_Start_IT(&hmdma, (uint32_t)&node1, NULL, NULL);
HAL_UART_Receive_DMA(&huart1, intermediate_buf, BUF_SIZE);
```
# 四、完整代码示例(简化)
```c
// main.c 片段
#define BUF_SIZE 1024
uint8_t intermediate_buf[BUF_SIZE];
uint8_t final_buf[BUF_SIZE];
void MDMA_Init(void) {
// 如上配置,略
}
void LPDMA_Init(void) {
// 如上配置,略
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
LPDMA_Init();
MDMA_Init();
// 启动流水线
HAL_MDMA_Start_IT(&hmdma, (uint32_t)&node1, NULL, NULL);
HAL_UART_Receive_DMA(&huart1, intermediate_buf, BUF_SIZE);
while (1) {
// 主循环可处理其他任务,数据搬运由DMA完成
// 当MDMA完成中断触发时,可处理final_buf中的数据
}
}
void MDMA_IRQHandler(void) {
HAL_MDMA_IRQHandler(&hmdma);
}
void DMA1_Stream0_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_uart);
}
```
# 五、注意事项
- **缓冲区对齐**:MDMA要求源和目标地址按字对齐(4字节),否则可能触发总线错误。建议使用`__attribute__((aligned(4)))`。
- **缓存一致性**:若目标RAM在D2域(如SRAM1),需注意CPU缓存与DMA的一致性。可使用`SCB_CleanDCache()`或配置MPU为非缓存区域。
- **中断优先级**:MDMA和LPDMA中断应设置为较高优先级,避免数据覆盖。
- **链表节点内存**:链表节点必须常驻内存,且不能被编译器优化掉,建议定义为全局变量。
- **调试技巧**:使用逻辑分析仪或串口打印MDMA完成标志,验证流水线时序。
# 六、总结
通过MDMA与LPDMA的级联,STM32H7实现了外设到RAM的零拷贝流水线,CPU仅需在最终数据就绪时处理业务逻辑。这种设计特别适合高速通信、音频处理等场景。掌握双DMA协同,是挖掘H7性能的关键一步。