# 引言 在嵌入式 GUI 或摄像头应用中,帧缓冲(Frame Buffer)的搬运效率直接决定系统流畅度。STM32H7 内置 MDMA(Master DMA)和 DMA2D(2D DMA/图形加速器),两者都能访问 AXI SRAM 和 SDRAM,但协同工作时,总线仲裁和缓存一致性(Cache Coherency)问题常被忽视。本文通过实测数据,揭示带宽瓶颈所在,并提供可复用的配置方法。 # 硬件与测试环境 - **MCU**: STM32H743VIT6(主频 480MHz,AXI SRAM 1MB) - **外部存储**: 32-bit SDRAM(16MB,时钟 100MHz) - **测试场景**: 将 800x480 RGB565 帧缓冲(约 768KB)从 SDRAM 搬运到 AXI SRAM,再搬运回 SDRAM(模拟双缓冲切换) - **工具**: 内部 DWT 计数器(CYCCNT)计时,串口打印结果 # 原理:MDMA 与 DMA2D 的差异 - **DMA2D**: 专为图形设计,支持颜色格式转换、混合、填充等,但只能访问 AXI 总线上的存储器(如 SDRAM、AXI SRAM),且无法访问 TCM 或 Flash。 - **MDMA**: 通用 DMA,支持任意存储器间搬运(包括 TCM),但无图形处理能力。MDMA 支持 Linked-List 和 4x4 突发传输,适合大数据块。 - **协同方式**: 通常用 MDMA 将数据从外设或 TCM 搬到 AXI SRAM,再用 DMA2D 做格式转换或混合。但若两者同时操作同一总线,会竞争带宽。 # 实测配置与代码 ## 1. 单独使用 DMA2D 搬运 配置 DMA2D 为内存到内存模式,不启用格式转换(直接复制)。 ```c void DMA2D_Copy(uint32_t srcAddr, uint32_t dstAddr, uint32_t size) { DMA2D->CR = 0; // 复位 DMA2D->FGMAR = srcAddr; DMA2D->BGMAR = dstAddr; DMA2D->FGOR = 0; DMA2D->BGOR = 0; DMA2D->FGPFCCR = DMA2D_INPUT_RGB565; DMA2D->BGPFCCR = DMA2D_INPUT_RGB565; DMA2D->NLR = (size / 2) | (1 << 16); // 每行1像素,总字节数/2 DMA2D->CR = DMA2D_CR_START; while (DMA2D->CR & DMA2D_CR_START); } ``` ## 2. 单独使用 MDMA 搬运 MDMA 配置为单块传输,突发 16 拍,字宽 32 位。 ```c void MDMA_Copy(uint32_t srcAddr, uint32_t dstAddr, uint32_t size) { MDMA_InitTypeDef mdma_init = {0}; __HAL_RCC_MDMA_CLK_ENABLE(); mdma_init.Request = MDMA_REQUEST_SW; mdma_init.TransferTriggerMode = MDMA_BLOCK_TRANSFER; mdma_init.SourceInc = MDMA_SRC_INC_WORD; mdma_init.DestInc = MDMA_DEST_INC_WORD; mdma_init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; mdma_init.DestDataSize = MDMA_DEST_DATASIZE_WORD; mdma_init.SourceBurst = MDMA_SOURCE_BURST_16BEATS; mdma_init.DestBurst = MDMA_DEST_BURST_16BEATS; HAL_MDMA_Init(&mdma_handle, &mdma_init); HAL_MDMA_Start(&mdma_handle, srcAddr, dstAddr, size/4, 0, 0); HAL_MDMA_PollForTransfer(&mdma_handle, HAL_MDMA_FULL_TRANSFER, HAL_MAX_DELAY); } ``` ## 3. MDMA 与 DMA2D 协同(串联) 先用 MDMA 将 SDRAM 数据搬到 AXI SRAM 临时区,再用 DMA2D 从临时区搬到目标 SDRAM 地址(模拟格式转换)。 ```c void MDMA_DMA2D_Coop(uint32_t src, uint32_t dst, uint32_t size) { uint32_t tmp = 0x24000000; // AXI SRAM 临时区 MDMA_Copy(src, tmp, size); DMA2D_Copy(tmp, dst, size); } ``` # 实测结果与瓶颈分析 | 方法 | 耗时 (ms) | 带宽 (MB/s) | 总线利用率 | |------|----------|------------|-----------| | 仅 DMA2D | 2.15 | 357 | 约 45% | | 仅 MDMA | 2.38 | 322 | 约 40% | | MDMA+DMA2D 串联 | 4.62 | 166 | 约 21% | **关键发现**: - 单独使用 DMA2D 或 MDMA 时,带宽接近 350MB/s,但远低于理论峰值(SDRAM 32-bit @100MHz 理论 400MB/s)。 - 串联时总耗时几乎等于两者之和,且带宽减半,说明瓶颈不在 DMA 引擎本身,而在 **总线仲裁** 和 **缓存一致性**。 - 当 DMA2D 和 MDMA 交替访问 SDRAM 时,每次切换都需要重新仲裁,且 SDRAM 的页命中率下降,导致额外延迟。 **瓶颈根源**: 1. **SDRAM 行切换开销**: 两次搬运间,SDRAM 行缓冲失效,导致预充电和激活延迟。 2. **AXI 总线仲裁**: MDMA 和 DMA2D 共享 AXI 总线矩阵,但串联时无法并行,因为 DMA2D 必须等 MDMA 完成。 3. **Cache 一致性问题**: 若 CPU 启用了 D-Cache,搬运后数据可能被缓存,导致 DMA 看到旧数据。本测试中已关闭 D-Cache 以避免干扰,但实际应用需注意。 # 优化建议 - **使用 DMA2D 的 P2P(像素到像素)模式**: 若只需复制,直接用 DMA2D,避免 MDMA 参与。 - **并行化**: 若必须 MDMA+DMA2D,可让 MDMA 搬运下一块数据的同时,DMA2D 处理当前块(流水线),但需双缓冲临时区。 - **调整突发长度**: 实测中 MDMA 用 16 拍突发比 4 拍高约 15% 带宽,但超过 16 拍无提升。 - **关闭 D-Cache 或使用 MPU 配置非缓存区域**: 对 DMA 缓冲区设置 MPU 为 Non-cacheable,避免一致性开销。 - **考虑使用 LTDC 直接读取 SDRAM**: 若目标是显示,LTDC 有专用 FIFO,可减少 CPU/DMA 干预。 # 完整代码示例(简化) 以下为测试主函数核心部分,包含 DWT 计时和串口输出。 ```c #include "stm32h7xx_hal.h" uint32_t DWT_GetCycle(void) { return DWT->CYCCNT; } int main(void) { HAL_Init(); // 初始化时钟、串口、SDRAM、DMA2D、MDMA... uint32_t src = 0xD0000000; // SDRAM 地址 uint32_t dst = 0xD0200000; uint32_t size = 800*480*2; // 测试 DMA2D DWT->CYCCNT = 0; uint32_t t0 = DWT_GetCycle(); DMA2D_Copy(src, dst, size); uint32_t t1 = DWT_GetCycle(); printf("DMA2D: %f ms\n", (t1-t0)/480.0); // 测试 MDMA t0 = DWT_GetCycle(); MDMA_Copy(src, dst, size); t1 = DWT_GetCycle(); printf("MDMA: %f ms\n", (t1-t0)/480.0); // 测试协同 t0 = DWT_GetCycle(); MDMA_DMA2D_Coop(src, dst, size); t1 = DWT_GetCycle(); printf("Coop: %f ms\n", (t1-t0)/480.0); while(1); } ``` # 注意事项 - **MPU 配置**: 若启用 D-Cache,务必对 DMA 缓冲区设置 MPU 为 Non-cacheable,否则会出现数据不一致。 - **SDRAM 时序**: 调整 SDRAM 时序参数(如 CAS 延迟)可能提升带宽,但需确保稳定性。 - **DMA2D 的 NLR 寄存器**: 当每行像素数大于 65535 时需分块,但本测试中 800 像素安全。 - **MDMA 的 Linked-List**: 对于多块搬运,使用链表可减少 CPU 干预,但需注意链表本身位于非缓存区域。 # 总结 STM32H7 的 MDMA 和 DMA2D 协同搬运帧缓冲,实际带宽受限于总线仲裁和 SDRAM 行切换,而非 DMA 引擎本身。单独使用 DMA2D 效率最高,若需格式转换,建议采用流水线设计。合理配置 MPU 和突发长度,可有效减少瓶颈。希望本文的实测数据能帮助你在项目中做出更优选择。