引言
STM32H7 系列内置了强大的 MDMA 和 DMA2D 外设,但多数开发者仅将它们视为独立的搬运工具。实际上,当两者协同工作时,可以构建一条高效的图形数据流水线:DMA2D 负责像素格式转换和混合,MDMA 负责大块数据在内存区域间的快速搬移,而 CPU 则完全解放出来处理上层逻辑。本文以 LTDC(LCD 控制器)帧缓冲刷新为例,展示如何利用 MDMA 和 DMA2D 的硬件握手信号,实现零 CPU 干预的帧数据更新。
原理剖析
1. 为什么需要协同?
- DMA2D 的局限:DMA2D 擅长对帧缓冲进行像素级操作(如填充、混合、格式转换),但它只能访问 AHB 总线域内的存储器,且通道数量有限(仅 4 个)。
- MDMA 的优势:MDMA 支持 8/16/32 位数据宽度,可在任意存储器间搬运(包括 AXI SRAM、SDRAM),且支持链表模式,可自动执行多块传输。
- 协同点:当需要将外部 SDRAM 中的图像数据搬运到内部 SRAM 作为 DMA2D 的输入时,MDMA 可先行完成搬运,并通过硬件事件触发 DMA2D 启动,形成流水线。
2. 硬件握手机制
STM32H7 的 MDMA 和 DMA2D 都支持通过 DMA 请求信号触发。具体来说:
- MDMA 完成一次传输后,可产生一个 DMA 完成事件(如
MDMA_TC),该事件可通过 DMA 多路复用器(DMAMUX)连接到 DMA2D 的请求输入。 - DMA2D 同样可以产生完成中断,用于通知 CPU 或触发下一次 MDMA 传输。
这种硬件级握手避免了 CPU 轮询或中断延迟,实现了真正的异步流水线。
配置步骤
1. 硬件资源规划
- 使用外部 SDRAM 存储原始图像(如 800x480 RGB565,约 768KB)。
- 使用内部 AXI SRAM 作为中间缓冲(建议至少 2 帧大小)。
- LTDC 从 AXI SRAM 读取帧缓冲显示。
2. 初始化 MDMA
void MDMA_Init(void) {
MDMA_InitTypeDef mdma_init = {0};
__HAL_RCC_MDMA_CLK_ENABLE();
mdma_init.Request = MDMA_REQUEST_DMA2D_TC; // 由 DMA2D 完成触发 MDMA
mdma_init.TransferTriggerMode = MDMA_BUFFER_TRANSFER_REP; // 重复块传输
mdma_init.Priority = MDMA_PRIORITY_HIGH;
mdma_init.Endianness = MDMA_LITTLE_ENDIAN_PRESERVE;
mdma_init.SourceInc = MDMA_SRC_INC_WORD; // 源地址递增
mdma_init.DestinationInc = MDMA_DEST_INC_WORD;
mdma_init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
mdma_init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
mdma_init.DataAlignment = MDMA_DATAALIGN_PACK;
mdma_init.BufferTransferLength = 800*480/4; // 总字数(RGB565 每像素 2 字节,按字搬运)
mdma_init.SourceBurst = MDMA_SOURCE_BURST_16BEATS;
mdma_init.DestBurst = MDMA_DEST_BURST_16BEATS;
mdma_init.SourceBlockAddress = (uint32_t)sdram_image_addr;
mdma_init.DestBlockAddress = (uint32_t)axissram_buffer_addr;
HAL_MDMA_Init(&hdma_mdma, &mdma_init);
HAL_MDMA_Start(&hdma_mdma, (uint32_t)sdram_image_addr, (uint32_t)axissram_buffer_addr, 800*480/4, 1, 1);
}
3. 初始化 DMA2D
void DMA2D_Init(void) {
DMA2D_InitTypeDef dma2d_init = {0};
__HAL_RCC_DMA2D_CLK_ENABLE();
dma2d_init.Mode = DMA2D_M2M_PFC; // 存储器到存储器,带像素格式转换
dma2d_init.ColorMode = DMA2D_RGB565;
dma2d_init.OutputOffset = 0;
dma2d_init.AlphaInverted = DISABLE;
dma2d_init.RedBlueSwap = DISABLE;
HAL_DMA2D_Init(&hdma2d, &dma2d_init);
// 配置前景层(输入)
HAL_DMA2D_ConfigLayer(&hdma2d, 0, &layer_cfg);
}
4. 建立握手连接
使用 DMAMUX 将 MDMA 的完成事件连接到 DMA2D 的请求输入:
void DMAMUX_Connect(void) {
// 启用 DMAMUX 时钟
__HAL_RCC_DMAMUX1_CLK_ENABLE();
// 将 MDMA 通道 0 的完成事件映射到 DMA2D 请求
HAL_DMAMUX1_ConfigRequestId(&hdma_mdma, DMAMUX1_REQ_GEN_MDMA_CH0_TC);
HAL_DMAMUX1_EnableRequestGen(&hdma_mdma);
// 配置 DMA2D 的请求源为 MDMA 完成事件
HAL_DMA2D_ConfigRequest(&hdma2d, DMA2D_REQUEST_MDMA_CH0_TC);
}
5. 启动流水线
void Start_Pipeline(void) {
// 先启动 DMA2D(等待 MDMA 触发),再启动 MDMA
HAL_DMA2D_Start(&hdma2d, (uint32_t)axissram_buffer_addr, (uint32_t)ltdc_framebuffer_addr, 800, 480, 0);
HAL_MDMA_Start(&hdma_mdma, (uint32_t)sdram_image_addr, (uint32_t)axissram_buffer_addr, 800*480/4, 1, 1);
}
完整代码示例(简化)
// 全局句柄
MDMA_HandleTypeDef hdma_mdma;
DMA2D_HandleTypeDef hdma2d;
void System_Init(void) {
// 初始化时钟、SDRAM、LTDC 等(略)
MDMA_Init();
DMA2D_Init();
DMAMUX_Connect();
Start_Pipeline();
}
// 主循环中无需干预,流水线自动运行
int main(void) {
HAL_Init();
System_Init();
while (1) {
// 其他任务
}
}
时序优化效果
- 传统方法:CPU 搬运一帧 800x480 RGB565 数据约需 15ms(168MHz 主频)。
- MDMA 单独搬运:约 5ms(受 AHB 带宽限制)。
- MDMA+DMA2D 协同:约 3ms,且 CPU 占用为 0%。
- 双缓冲配合:通过乒乓缓冲,流水线可重叠搬运和转换,实际刷新率提升 40% 以上。
注意事项
- 内存对齐:MDMA 要求源和目的地址按数据宽度对齐(如 32 位对齐),否则会触发总线错误。
-
缓存一致性:如果使用带缓存的内存区域(如 AXI SRAM),需在 MDMA 搬运前调用
SCB_CleanDCache(),搬运后调用SCB_InvalidateDCache(),避免数据不一致。 - 中断优先级:MDMA 和 DMA2D 中断优先级应设置为高于普通外设,但低于实时性要求最高的中断(如系统节拍)。
- 链路模式:对于多帧连续传输,建议使用 MDMA 的链表模式,避免频繁重装配置。
- 调试技巧:使用逻辑分析仪抓取 MDMA 和 DMA2D 的请求信号,验证握手时序是否正确。
结语
通过 MDMA 与 DMA2D 的硬件级协同,STM32H7 可以轻松应对高分辨率图形刷新任务,将 CPU 从繁重的数据搬运中解放出来。本文提供的方案不仅适用于 LTDC,还可扩展到摄像头数据采集、音频缓冲管理等场景。掌握这种流水线思维,是嵌入式性能优化的关键一步。