STM32H7 使用 MDMA 与双 Bank Flash 并行搬运的时序陷阱与规避
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能应用中,MDMA 与双 Bank Flash 的并行搬运能大幅提升数据吞吐,但若忽视总线仲裁、Flash 等待周期及 MDMA 触发时序,极易陷入数据错乱或性能骤降的陷阱。本文深入剖析 MDMA 与双 Bank Flash 交互时的时序瓶颈,结合寄存器配置与代码示例,给出实用的规避策略,帮助开发者稳定榨取 H7 的搬运性能。
# 引言
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 的极致性能。