STM32H7 使用 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置细节
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 系列高性能 MCU 上,MDMA 与 DMA2D 是图形加速与数据搬运的两大核心引擎。本文深入讲解如何利用 MDMA 在后台搬运帧缓冲数据,同时使用 DMA2D 执行像素格式转换与混合,实现双缓冲图形渲染的无缝流水线。文章涵盖硬件原理、CubeMX 配置步骤、关键代码实现以及常见陷阱,帮助开发者最大化 H7 的图形性能。
# STM32H7 使用 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置细节
## 一、为什么需要 MDMA 与 DMA2D 协同?
STM32H7 内置 Cortex-M7 内核,主频高达 480MHz,但 CPU 直接搬运大量像素数据会消耗大量时钟周期。DMA2D(Chrom-ART Accelerator)专用于 2D 图形操作,如颜色填充、像素拷贝、混合(Alpha Blending)和格式转换。而 MDMA(Master DMA)则是一种更通用的高速 DMA,支持内存到内存、外设到内存等复杂传输,且拥有独立的 FIFO 和突发传输能力。
双缓冲图形系统通常需要两个帧缓冲(Front Buffer 和 Back Buffer)。渲染时,CPU 或 DMA2D 写入 Back Buffer,完成后交换指针,显示控制器读取 Front Buffer。若由 CPU 负责交换和拷贝,会引入延迟。通过 MDMA 在后台将渲染完成的 Back Buffer 搬运到显示缓冲区,同时 DMA2D 准备下一帧,可实现渲染与传输的流水线并行,显著提升帧率。
## 二、硬件架构与工作原理
- **DMA2D**:支持多种模式(寄存器到内存、内存到内存、内存到内存并 PFC、内存到内存并混合)。其内部包含像素格式转换器(PFC)和 Alpha 混合器,可自动完成 RGB565、ARGB8888 等格式转换。
- **MDMA**:拥有 16 个通道,每个通道支持 8/16/32 位数据宽度,可配置突发长度(1~128 字节),并支持链表模式(Linked List),可连续执行多个传输块。
- **协同流程**:
1. CPU 配置 DMA2D 将渲染数据(如精灵图)混合到 Back Buffer。
2. 渲染完成后,触发 MDMA 将 Back Buffer 搬运到显示控制器的帧缓冲地址。
3. MDMA 传输完成中断中,交换前后缓冲指针,并启动下一帧渲染。
## 三、CubeMX 配置步骤
### 1. 使能 DMA2D
- 在 `Connectivity` 中找到 `DMA2D`,选择 `Memory-to-Memory with PFC` 模式。
- 设置输入/输出颜色模式(如输入 RGB565,输出 ARGB8888)。
- 配置传输方向、行偏移等参数(可在代码中动态修改)。
### 2. 使能 MDMA
- 在 `System Core` 中选择 `MDMA`,添加一个通道(如 MDMA_Channel0)。
- 设置请求为 `Memory To Memory`,数据宽度 32 位,突发长度 16 字节。
- 使能中断(传输完成和错误)。
### 3. 配置 DMA 请求映射
- 在 `NVIC` 中使能 MDMA 中断优先级(建议高于显示刷新中断)。
- 确保 MDMA 的时钟源为 AXI 或 AHB,且优先级高于普通 DMA。
## 四、关键代码实现
以下代码演示了使用 MDMA 搬运帧缓冲,并在传输完成后交换缓冲。
```c
// 定义帧缓冲结构
#define BUFFER_WIDTH 480
#define BUFFER_HEIGHT 272
#define PIXEL_SIZE 2 // RGB565
uint8_t back_buffer[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));
uint8_t front_buffer[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));
// MDMA 传输完成回调
void MDMA_IRQHandler(void)
{
if (LL_MDMA_IsActiveFlag_TC0(MDMA)) {
LL_MDMA_ClearFlag_TC0(MDMA);
// 交换缓冲指针(实际中可更新显示控制器地址)
uint8_t *tmp = front_buffer;
front_buffer = back_buffer;
back_buffer = tmp;
// 触发下一帧渲染(例如通过信号量通知任务)
}
}
// 启动 MDMA 搬运
void MDMA_StartTransfer(void)
{
LL_MDMA_ConfigAddresses(MDMA, MDMA_CHANNEL_0,
(uint32_t)back_buffer, // 源地址
(uint32_t)front_buffer, // 目的地址
LL_MDMA_DATAWIDTH_32, // 数据宽度
LL_MDMA_DATAWIDTH_32);
LL_MDMA_ConfigTransfer(MDMA, MDMA_CHANNEL_0,
BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE / 4, // 传输次数(32位为单位)
LL_MDMA_BURST_16, LL_MDMA_BURST_16);
LL_MDMA_EnableChannel(MDMA, MDMA_CHANNEL_0);
}
// DMA2D 渲染一帧(示例:填充颜色)
void DMA2D_FillBuffer(uint32_t color)
{
DMA2D->CR = DMA2D_CR_MODE_MEM2MEM; // 内存到内存模式
DMA2D->FGMAR = (uint32_t)back_buffer;
DMA2D->FGOR = 0;
DMA2D->BGMAR = (uint32_t)back_buffer;
DMA2D->BGOR = 0;
DMA2D->FGPFCCR = DMA2D_PFCCR_CM_RGB565;
DMA2D->BGPFCCR = DMA2D_PFCCR_CM_RGB565;
DMA2D->OPFCCR = DMA2D_PFCCR_CM_RGB565;
DMA2D->NLR = (BUFFER_HEIGHT << 16) | BUFFER_WIDTH;
DMA2D->OCOLR = color;
DMA2D->CR |= DMA2D_CR_START;
while (DMA2D->CR & DMA2D_CR_START); // 等待完成(实际可用中断)
}
// 主循环示例
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DMA2D_Init();
MX_MDMA_Init();
while (1) {
DMA2D_FillBuffer(0xFFFF); // 填充白色
MDMA_StartTransfer(); // 启动搬运
// 此时 CPU 可执行其他任务,或准备下一帧渲染
// 等待 MDMA 完成中断(通过信号量阻塞)
}
}
```
## 五、注意事项与常见陷阱
- **内存对齐**:MDMA 要求源和目的地址按数据宽度对齐(32 位时 4 字节对齐)。建议使用 `__attribute__((aligned(32)))` 或 `memalign` 分配缓冲。
- **缓存一致性**:STM32H7 的 D-Cache 可能导致 DMA 看到陈旧数据。需要在 DMA 传输前调用 `SCB_CleanDCache()`,传输后调用 `SCB_InvalidateDCache()`。
- **中断优先级**:MDMA 中断优先级应高于显示刷新中断(如 LTDC),否则可能撕裂画面。
- **DMA2D 与 MDMA 并发**:两者都访问 AXI 总线,可能产生仲裁延迟。建议将 DMA2D 优先级设为高,MDMA 设为中,并合理配置突发长度以减少总线占用。
- **传输完成标志**:MDMA 的传输完成标志需在中断中清除,否则会持续触发中断。
- **调试建议**:使用逻辑分析仪或示波器测量帧同步信号,验证双缓冲切换是否与垂直消隐同步。
## 六、总结
通过 MDMA 与 DMA2D 的协同,STM32H7 可以实现高效的图形流水线:DMA2D 负责像素级操作,MDMA 负责大块数据搬运,CPU 仅需配置和响应中断。本文提供的配置步骤和代码可直接应用于实际项目,但需根据具体显示接口(如 LTDC、DSI)调整缓冲地址和时序。掌握这些细节,能显著提升嵌入式 GUI 的流畅度。