STM32H7 帧缓冲搬运的时序对齐:MDMA 与 DMA2D 协同优化实战
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能平台上,MDMA 与 DMA2D 的协同工作能显著提升帧缓冲搬运效率,但两者时序对齐不当会导致撕裂或数据错位。本文深入剖析 MDMA 与 DMA2D 的硬件特性,给出基于双缓冲与中断同步的配置步骤,并附完整代码示例,助你实现零拷贝、无撕裂的显示刷新。
# STM32H7 帧缓冲搬运的时序对齐:MDMA 与 DMA2D 协同优化实战
## 为什么需要 MDMA 与 DMA2D 协同?
在 STM32H7 系列中,DMA2D 是专用的 2D 图形加速器,可执行像素格式转换、混合、填充等操作,但它的源地址和目的地址通常位于 AHB 总线域。而 MDMA(Master DMA)则能访问所有存储器映射区域,包括 AXI SRAM、SDRAM 等,且支持链表模式,可自动执行复杂的数据搬运。
当我们需要将摄像头采集的 RGB565 帧数据搬运到 SDRAM 中的帧缓冲,再通过 DMA2D 进行格式转换或混合后显示到 LCD 时,若简单串联使用,会因总线仲裁和 DMA 优先级差异导致时序错位,出现画面撕裂。
**核心目标**:让 MDMA 完成大块数据搬运,DMA2D 在数据就绪后立即处理,两者通过中断和标志位精确同步,避免数据竞争。
## 硬件特性与时序对齐原理
### MDMA 的关键特性
- 支持 8/16/32/64 位数据宽度,突发传输可达 128 字节。
- 拥有 4 个 FIFO,可缓存数据,降低总线占用。
- 支持链接列表(Linked List),可连续执行多个传输块,无需 CPU 干预。
### DMA2D 的关键特性
- 支持前景/背景层,可执行颜色填充、混合、格式转换。
- 操作基于像素,需确保源数据完整。
- 完成中断可触发 MDMA 启动下一轮搬运。
### 时序对齐策略
1. **双缓冲机制**:使用两个帧缓冲(Buffer A 和 Buffer B),MDMA 将新数据写入 Buffer B,同时 DMA2D 从 Buffer A 读取并显示。当 MDMA 完成写入后,交换角色。
2. **中断同步**:MDMA 传输完成中断(TCIF)触发 DMA2D 启动;DMA2D 完成中断(TCIF)触发 MDMA 启动下一轮。
3. **优先级设置**:MDMA 优先级设为高,DMA2D 设为中,确保搬运不被显示操作阻塞。
## 配置步骤
### 1. 初始化 MDMA
- 使能 MDMA 时钟(RCC->AHB3ENR |= RCC_AHB3ENR_MDMAEN)。
- 配置 MDMA 通道,设置源地址、目的地址、数据块大小(以 32 位字为单位)。
- 启用传输完成中断,并设置优先级。
### 2. 初始化 DMA2D
- 使能 DMA2D 时钟(RCC->AHB1ENR |= RCC_AHB1ENR_DMA2DEN)。
- 配置 DMA2D 为内存到内存模式,设置源地址(帧缓冲)、目的地址(LCD 显存)。
- 设置像素格式(如 ARGB8888)、行偏移等。
- 启用完成中断。
### 3. 中断服务函数
- 在 MDMA 中断中,启动 DMA2D 传输。
- 在 DMA2D 中断中,切换缓冲角色,并启动 MDMA 搬运下一帧。
## 完整代码示例
以下代码基于 STM32H743,使用 HAL 库,假设帧缓冲位于 AXI SRAM,LCD 显存位于 SDRAM。
```c
// 定义缓冲结构
#define FRAME_WIDTH 480
#define FRAME_HEIGHT 272
#define PIXEL_SIZE 2 // RGB565
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT * PIXEL_SIZE)
uint8_t buffer_A[FRAME_SIZE] __attribute__((section(".RAM_D1")));
uint8_t buffer_B[FRAME_SIZE] __attribute__((section(".RAM_D1")));
uint8_t *active_buffer = buffer_A;
uint8_t *display_buffer = buffer_B;
// MDMA 句柄
MDMA_HandleTypeDef hmdma;
// DMA2D 句柄
DMA2D_HandleTypeDef hdma2d;
// MDMA 初始化
void MDMA_Init(void) {
__HAL_RCC_MDMA_CLK_ENABLE();
hmdma.Instance = MDMA_Channel0;
hmdma.Init.Request = MDMA_REQUEST_MEM2MEM;
hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
hmdma.Init.Priority = MDMA_PRIORITY_HIGH;
hmdma.Init.Endianness = MDMA_LITTLE_ENDIAN_PRESERVE;
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 = FRAME_SIZE / 4; // 32位字数量
HAL_MDMA_Init(&hmdma);
HAL_NVIC_SetPriority(MDMA_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(MDMA_IRQn);
}
// DMA2D 初始化
void DMA2D_Init(void) {
__HAL_RCC_DMA2D_CLK_ENABLE();
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);
HAL_NVIC_SetPriority(DMA2D_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA2D_IRQn);
}
// 启动 MDMA 搬运(从源到目标)
void MDMA_StartTransfer(uint8_t *src, uint8_t *dst) {
HAL_MDMA_Start_IT(&hmdma, (uint32_t)src, (uint32_t)dst, FRAME_SIZE / 4, 0);
}
// 启动 DMA2D 转换(从帧缓冲到显存)
void DMA2D_StartTransfer(uint8_t *src) {
HAL_DMA2D_Start_IT(&hdma2d, (uint32_t)src, (uint32_t)LCD_FB_ADDR, FRAME_WIDTH, FRAME_HEIGHT);
}
// MDMA 中断回调
void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma) {
// 数据搬运完成,启动 DMA2D 显示
DMA2D_StartTransfer(active_buffer);
}
// DMA2D 中断回调
void HAL_DMA2D_TC_Callback(DMA2D_HandleTypeDef *hdma2d) {
// 显示完成,交换缓冲并启动下一轮搬运
uint8_t *tmp = active_buffer;
active_buffer = display_buffer;
display_buffer = tmp;
// 假设新数据已由外部填充到 active_buffer
MDMA_StartTransfer(source_data, active_buffer);
}
// 主程序启动
int main(void) {
HAL_Init();
SystemClock_Config();
MDMA_Init();
DMA2D_Init();
// 首次启动
MDMA_StartTransfer(source_data, active_buffer);
while(1) {
// 主循环可处理其他任务
}
}
```
## 注意事项
- **内存对齐**:帧缓冲地址必须按 32 字节对齐,否则 MDMA 可能产生总线错误。
- **缓存一致性**:如果使用 AXI SRAM,需注意 CPU 缓存与 DMA 的一致性,必要时使用 `SCB_CleanDCache()` 或 `SCB_InvalidateDCache()`。
- **中断优先级**:MDMA 中断优先级应高于 DMA2D,避免 DMA2D 完成中断被 MDMA 中断抢占导致时序错乱。
- **传输长度单位**:MDMA 的 `BufferTransferLength` 以 32 位字为单位,计算时务必除以 4。
- **调试建议**:使用逻辑分析仪或示波器观察帧同步信号,验证时序是否对齐。
## 总结
通过合理配置 MDMA 与 DMA2D 的中断同步和双缓冲机制,可以高效实现帧缓冲搬运与显示的流水线操作,大幅降低 CPU 负载并避免画面撕裂。本文提供的代码框架可直接应用于实际项目,但需根据具体硬件调整地址和参数。掌握这一技巧,将让你的 STM32H7 图形应用性能更上一层楼。