# 引言 STM32H7 系列凭借双 Bank Flash 和 MDMA(Master Direct Memory Access)控制器,成为嵌入式高性能场景的宠儿。MDMA 支持 8/16/32 位数据搬运,可配合双 Bank Flash 实现并行读取,理论上吞吐可翻倍。然而,实际开发中常遇到搬运数据错乱、CPU 卡死或性能不升反降的问题,根源多在于时序细节。本文面向有经验的开发者,剖析 MDMA 与双 Bank Flash 并行搬运的时序陷阱,并给出可落地的规避方案。 # 1. 双 Bank Flash 与 MDMA 的并行机制 ## 1.1 双 Bank Flash 架构 STM32H7 的 Flash 分为 Bank1 和 Bank2,每个 Bank 拥有独立的读端口和总线接口。当 CPU 或 DMA 从不同 Bank 读取数据时,可并行访问,避免总线冲突。例如,Bank1 存储代码,Bank2 存储常量表,MDMA 从 Bank2 搬运数据时,CPU 仍可从 Bank1 取指,互不干扰。 ## 1.2 MDMA 的传输模式 MDMA 支持 Linked List 和 Buffer Transfer 模式,可配置源/目的地址、数据宽度和突发长度。其核心优势在于: - 支持 8/16/32 位数据宽度,可自动对齐。 - 支持突发传输(Burst),减少总线占用次数。 - 可触发中断,实现多块搬运。 并行搬运的关键在于:MDMA 同时从 Bank1 和 Bank2 读取数据(例如源地址分别指向两个 Bank),然后写入目标内存。但 Flash 的读取时序受等待周期(Latency)影响,若配置不当,会导致数据错位。 # 2. 时序陷阱分析 ## 2.1 Flash 等待周期与 MDMA 时钟不同步 STM32H7 的 Flash 读取需要等待周期(WS),取决于 CPU 频率和电压范围。MDMA 的时钟源为 AXI 总线时钟,与 CPU 时钟异步。当 MDMA 发起 Flash 读取时,Flash 控制器会插入等待周期,但 MDMA 不会感知,导致读取数据提前或延后。 **陷阱示例**: - 配置 Flash 为 7 个等待周期,但 MDMA 的突发长度为 16,且未设置对齐,导致 MDMA 在等待周期内重复读取同一地址,数据错乱。 ## 2.2 双 Bank 并行访问的仲裁冲突 虽然双 Bank 可并行,但 MDMA 的突发传输可能跨越 Bank 边界。例如,源地址从 Bank1 末尾跨越到 Bank2 起始,MDMA 会发起两次总线请求,但 Flash 控制器可能因 Bank 切换产生额外延迟,破坏并行性。 ## 2.3 MDMA 触发时序与 Flash 编程冲突 若 MDMA 搬运的同时,CPU 正在执行 Flash 写操作(如擦写),Flash 控制器会暂停读操作,导致 MDMA 超时或数据丢失。 # 3. 规避策略与配置步骤 ## 3.1 正确配置 Flash 等待周期 使用 HAL 库或寄存器设置 Flash 延迟,确保与 MDMA 时钟匹配。 ```c // 设置 Flash 等待周期为 7(假设 CPU 频率 480MHz,电压范围 2.7-3.6V) MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_LATENCY_7); // 使能指令缓存和数据缓存 SET_BIT(FLASH->ACR, FLASH_ACR_ICEN | FLASH_ACR_DCEN); ``` ## 3.2 使用 MDMA 的 32 位对齐和突发限制 MDMA 配置时,强制数据宽度为 32 位,并限制突发长度不超过 8,以减少跨 Bank 概率。 ```c MDMA_InitTypeDef mdma_init = {0}; mdma_init.Request = MDMA_REQUEST_DMA1_Stream0; // 示例请求 mdma_init.Endianness = MDMA_LITTLE_ENDIAN; 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.DataAlignment = MDMA_DATAALIGN_PACK; mdma_init.BufferTransferLength = 1024; // 每次传输 1024 字 mdma_init.SourceBurst = MDMA_SOURCE_BURST_8; // 突发 8 次 mdma_init.DestBurst = MDMA_DEST_BURST_8; HAL_MDMA_Init(&mdma_handle, &mdma_init); ``` ## 3.3 避免跨 Bank 边界 将源数据块限制在单个 Bank 内,或使用两个独立的 MDMA 通道分别搬运不同 Bank,再通过软件合并。 ```c // 示例:从 Bank1 和 Bank2 各搬运 512 字到内存 uint32_t src_bank1 = 0x08000000; // Bank1 起始 uint32_t src_bank2 = 0x08040000; // Bank2 起始(假设偏移 256KB) MDMA_Start(&mdma_handle_bank1, src_bank1, dest, 512); MDMA_Start(&mdma_handle_bank2, src_bank2, dest+512, 512); // 等待两个通道完成 ``` ## 3.4 处理 Flash 编程冲突 在 MDMA 搬运前,确保 Flash 无写操作。若必须并发,使用 Flash 控制器的忙标志轮询,或暂停 MDMA。 ```c while (FLASH->SR & FLASH_SR_BSY); // 等待 Flash 空闲 HAL_MDMA_Start(&mdma_handle, src, dest, size); ``` # 4. 完整代码示例 以下代码演示从双 Bank Flash 并行搬运 1KB 数据到 SRAM,使用两个 MDMA 通道。 ```c #include "stm32h7xx_hal.h" MDMA_HandleTypeDef mdma_bank1, mdma_bank2; void MDMA_Init(void) { __HAL_RCC_MDMA_CLK_ENABLE(); // 配置 Bank1 通道 mdma_bank1.Instance = MDMA_Channel0; mdma_bank1.Init.Request = MDMA_REQUEST_DMA1_Stream0; mdma_bank1.Init.Endianness = MDMA_LITTLE_ENDIAN; mdma_bank1.Init.SourceInc = MDMA_SRC_INC_WORD; mdma_bank1.Init.DestInc = MDMA_DEST_INC_WORD; mdma_bank1.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; mdma_bank1.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD; mdma_bank1.Init.DataAlignment = MDMA_DATAALIGN_PACK; mdma_bank1.Init.BufferTransferLength = 256; // 256 字 = 1KB mdma_bank1.Init.SourceBurst = MDMA_SOURCE_BURST_8; mdma_bank1.Init.DestBurst = MDMA_DEST_BURST_8; HAL_MDMA_Init(&mdma_bank1); // 配置 Bank2 通道(参数类似,略) // ... } void ParallelCopy(void) { uint32_t dest[512]; // 目标缓冲区 uint32_t src1 = 0x08000000; // Bank1 数据 uint32_t src2 = 0x08040000; // Bank2 数据 // 等待 Flash 空闲 while (FLASH->SR & FLASH_SR_BSY); // 启动两个 MDMA 传输 HAL_MDMA_Start(&mdma_bank1, src1, dest, 256); HAL_MDMA_Start(&mdma_bank2, src2, &dest[256], 256); // 等待完成(使用中断或轮询) HAL_MDMA_PollForTransfer(&mdma_bank1, HAL_MAX_DELAY); HAL_MDMA_PollForTransfer(&mdma_bank2, HAL_MAX_DELAY); // 此时 dest 中已合并数据 } ``` # 5. 注意事项 - **缓存一致性**:MDMA 搬运后,若 CPU 读取目标内存,需确保 D-Cache 已失效(`SCB_InvalidateDCache_by_Addr`),否则可能读到旧数据。 - **中断优先级**:MDMA 中断应设为高优先级,避免被其他中断延迟导致超时。 - **功耗模式**:在低功耗模式下,Flash 时钟可能关闭,MDMA 会失败,需在唤醒后重新配置。 - **调试建议**:使用逻辑分析仪观测 AXI 总线时序,验证 Flash 等待周期是否匹配。 # 结语 STM32H7 的 MDMA 与双 Bank Flash 并行搬运潜力巨大,但时序细节决定成败。通过合理配置 Flash 等待周期、限制突发长度、避免跨 Bank 访问,并处理 Flash 编程冲突,可显著提升稳定性与性能。希望本文的剖析与代码示例能帮助你在项目中规避陷阱,发挥 H7 的极致性能。