STM32H7 的 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置要点
👁 3 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能系列中,MDMA 与 DMA2D 是图形加速的两大利器。DMA2D 专为像素搬运与格式转换设计,而 MDMA 则擅长内存到内存的大块数据搬移。本文深入剖析两者协同工作的原理,重点讲解双缓冲机制下的配置要点,包括时钟域同步、描述符链、中断处理及内存对齐等关键细节,并给出完整的初始化与启动代码示例,帮助开发者避开常见陷阱,实现流畅的图形刷新。
# STM32H7 的 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置要点
## 引言
在嵌入式图形系统中,CPU 直接搬运像素数据会严重占用处理周期。STM32H7 系列内置的 DMA2D(Chrom-ART Accelerator)和 MDMA(Master DMA)提供了硬件级的数据搬运能力。DMA2D 擅长像素格式转换、混合和填充,而 MDMA 则能高效地在内存区域间搬移大块数据。将两者结合,可以构建一条从帧缓冲区到显示控制器的自动流水线,实现双缓冲的无缝切换,大幅提升图形刷新率。本文面向有 STM32 开发经验的工程师,聚焦配置要点与代码实现。
## 原理:双缓冲与硬件加速
双缓冲的核心是:后台缓冲区用于绘制新帧,前台缓冲区用于显示。当后台绘制完成后,需要快速交换指针或复制数据。在 STM32H7 上,更高效的方式是使用 DMA2D 将后台缓冲区的像素数据搬运到 LTDC(LCD 控制器)的当前帧地址,或者直接切换 LTDC 的帧地址(但需注意时序)。而 MDMA 可以在此过程中扮演两个角色:
- **预加载**:将外部 SDRAM 中的图像数据块预先搬运到内部 SRAM(如 D1 域),供 DMA2D 快速处理。
- **后处理**:将 DMA2D 处理完的数据快速搬移到 LTDC 的帧缓冲区(可能位于 SDRAM)。
关键点在于,MDMA 和 DMA2D 都挂载在 AXI 总线上,它们可以并行工作,但需要避免访问冲突。通过合理配置 MDMA 的通道优先级和 DMA2D 的 FIFO 阈值,可以最大化吞吐量。
## 配置步骤
### 1. 时钟与总线使能
STM32H7 的 MDMA 和 DMA2D 均位于 AXI 总线域,需确保 RCC 中对应时钟已使能。
```c
__HAL_RCC_MDMA_CLK_ENABLE();
__HAL_RCC_DMA2D_CLK_ENABLE();
```
注意:MDMA 的时钟源可选 AXI 或 AHB,通常使用 AXI(默认),频率可达 240MHz。
### 2. 内存区域规划
双缓冲需要至少两个缓冲区,建议分配在 D1 域(AXI SRAM)或外部 SDRAM,并确保 32 字节对齐(MDMA 要求)。
```c
#define BUFFER_SIZE (800*480*2) // RGB565, 800x480
__attribute__((section(".ARM.__at_0x24000000"))) uint8_t buf_back[BUFFER_SIZE];
__attribute__((section(".ARM.__at_0x24000000+BUFFER_SIZE"))) uint8_t buf_front[BUFFER_SIZE];
```
### 3. MDMA 配置(用于搬运图像块到 SRAM)
MDMA 支持 Linked List 模式,可链式执行多个传输。以下配置一个通道,用于从 SDRAM 搬运 32KB 数据到内部 SRAM。
```c
MDMA_HandleTypeDef hmdma;
void MDMA_Init(void) {
hmdma.Instance = MDMA_Channel0;
hmdma.Init.Request = MDMA_REQUEST_SW; // 软件触发
hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
hmdma.Init.Priority = MDMA_PRIORITY_HIGH;
hmdma.Init.Endianness = MDMA_LITTLE_ENDIAN;
hmdma.Init.SourceInc = MDMA_SRC_INC_WORD;
hmdma.Init.DestinationInc = MDMA_DST_INC_WORD;
hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
hmdma.Init.DestDataSize = MDMA_DST_DATASIZE_WORD;
hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK;
hmdma.Init.BufferTransferLength = 32*1024/4; // 以 word 为单位
hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_32BEATS;
hmdma.Init.DestBurst = MDMA_DEST_BURST_32BEATS;
hmdma.Init.SourceAddress = (uint32_t)src_addr;
hmdma.Init.DestAddress = (uint32_t)buf_back;
HAL_MDMA_Init(&hmdma);
}
```
### 4. DMA2D 配置(用于像素格式转换与搬运)
DMA2D 可以执行内存到内存的搬运,并支持 RGB565 到 RGB888 等转换。以下配置将后台缓冲区内容复制到前台缓冲区(模拟双缓冲切换)。
```c
DMA2D_HandleTypeDef hdma2d;
void DMA2D_Init(void) {
hdma2d.Instance = DMA2D;
hdma2d.Init.Mode = DMA2D_M2M; // 内存到内存
hdma2d.Init.ColorMode = DMA2D_RGB565;
hdma2d.Init.OutputOffset = 0;
hdma2d.LayerCfg[0].InputOffset = 0;
hdma2d.LayerCfg[0].InputColorMode = DMA2D_RGB565;
hdma2d.LayerCfg[0].AlphaMode = DMA2D_NO_MODIF_ALPHA;
hdma2d.LayerCfg[0].InputAlpha = 0xFF;
HAL_DMA2D_Init(&hdma2d);
}
void DMA2D_CopyBuffer(uint32_t src, uint32_t dst, uint16_t width, uint16_t height) {
HAL_DMA2D_Start(&hdma2d, src, dst, width, height);
HAL_DMA2D_PollForTransfer(&hdma2d, 100);
}
```
### 5. 协同工作流程
实际应用中,MDMA 和 DMA2D 可以流水线化:MDMA 从 SDRAM 预加载下一帧图像到 SRAM,同时 DMA2D 处理当前帧。以下是一个简单示例:
```c
void Start_Frame_Transfer(void) {
// 1. 启动 MDMA 搬运下一帧到后台缓冲区
HAL_MDMA_Start_IT(&hmdma, (uint32_t)next_frame_sdram, (uint32_t)buf_back, 32*1024/4);
// 2. 同时启动 DMA2D 将后台缓冲区内容复制到前台(或直接处理)
DMA2D_CopyBuffer((uint32_t)buf_back, (uint32_t)buf_front, 800, 480);
// 3. 等待 MDMA 完成,然后更新 LTDC 帧地址(可选)
}
```
注意:MDMA 和 DMA2D 的完成中断应分别处理,避免资源竞争。
## 完整代码示例(简化)
```c
// 中断回调
void HAL_MDMA_XferCpltCallback(MDMA_HandleTypeDef *hmdma) {
// 通知主循环可以安全使用 buf_back
}
void HAL_DMA2D_XferCpltCallback(DMA2D_HandleTypeDef *hdma2d) {
// 切换 LTDC 帧地址到 buf_front
LTDC->Layer1->CFBAR = (uint32_t)buf_front;
}
int main(void) {
HAL_Init();
SystemClock_Config();
// 初始化 LTDC、SDRAM 等
MDMA_Init();
DMA2D_Init();
// 注册回调
HAL_MDMA_RegisterCallback(&hmdma, HAL_MDMA_XFER_CPLT_CB_ID, MDMA_XferCpltCallback);
HAL_DMA2D_RegisterCallback(&hdma2d, HAL_DMA2D_XFER_CPLT_CB_ID, DMA2D_XferCpltCallback);
// 启动第一次传输
Start_Frame_Transfer();
while(1) {
// 主循环处理其他任务
}
}
```
## 注意事项
- **内存对齐**:MDMA 要求源和目标地址按传输宽度对齐(如 32 位对齐),否则会触发错误。建议使用 32 字节对齐。
- **缓存一致性**:STM32H7 的 D-Cache 可能导致 DMA 看到的数据不一致。需在 DMA 操作前调用 `SCB_CleanDCache()` 或使用 MPU 配置为非缓存区域。
- **中断优先级**:MDMA 和 DMA2D 中断应设置为较高优先级,且避免在回调中执行耗时操作。
- **总线仲裁**:当 MDMA 和 DMA2D 同时访问 SDRAM 时,可能产生带宽竞争。可通过调整 MDMA 的 burst 大小和 DMA2D 的 FIFO 阈值来优化。
- **描述符链**:对于复杂传输,使用 MDMA 的 Linked List 模式可以避免 CPU 干预,但需注意描述符本身的内存放置(建议放在 D2 域 SRAM)。
## 结语
MDMA 与 DMA2D 的协同可以显著降低图形系统的 CPU 负载,实现高效的双缓冲刷新。关键在于合理规划内存、配置正确的触发模式,并处理好缓存一致性问题。本文提供的配置要点和代码框架可作为开发起点,实际项目中还需根据具体屏幕分辨率和刷新率调整参数。掌握这两者,你的 STM32H7 图形性能将得到质的飞跃。