# STM32H7 零等待代码搬运:MDMA 与双 Bank Flash 交替执行的配置细节 ## 1. 背景与原理 STM32H7 系列(如 STM32H743)最高主频可达 480 MHz,但内嵌 Flash 的访问速度受限于等待状态(Wait States)。在 480 MHz 下,Flash 需要 7 个等待周期,导致直接从 Flash 取指时 CPU 性能大打折扣。虽然 ART(Adaptive Real-Time)加速器可缓存指令,但面对大代码段或随机跳转时,缓存命中率下降,性能仍受影响。 **双 Bank Flash 交替执行**是一种硬件级优化方案:STM32H7 的 Flash 分为两个独立的 Bank(Bank1 和 Bank2),每个 Bank 拥有独立的读端口。当 CPU 从 Bank1 执行代码时,MDMA 可同时从 Bank2 读取数据或代码,反之亦然。通过交替搬运,可实现代码在 Bank 间“乒乓”切换,使 CPU 始终从当前无等待状态的 Bank 取指,达到零等待执行效果。 **MDMA(Master DMA)** 是 STM32H7 中的高性能 DMA 控制器,支持内存到内存、外设到内存等传输,具有 64 位带宽、双缓冲和链表模式,非常适合大块数据搬运。 ## 2. 硬件与软件准备 - 硬件:STM32H743 开发板(如 NUCLEO-H743ZI) - 软件:STM32CubeIDE(或 Keil MDK)、STM32CubeH7 固件库 - 关键外设:Flash(双 Bank)、MDMA、SysTick 或定时器(用于中断同步) ## 3. 配置步骤 ### 3.1 Flash Bank 划分与链接脚本调整 首先,将代码分为两个部分:一部分常驻 Bank1(起始地址 0x08000000),另一部分为待搬运代码(放在 Bank2 的固定区域,但实际执行时会被复制到 Bank1 的 RAM 或另一 Bank)。为简化,我们设计如下: - Bank1:主程序(固定执行) - Bank2:存放待搬运的代码块(例如,算法函数) 链接脚本(.ld)需定义两个区域: ```c MEMORY { FLASH_BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 1M FLASH_BANK2 (rx) : ORIGIN = 0x08100000, LENGTH = 1M RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K } SECTIONS { .text_bank1 : { *(.text.main*) *(.text.*) } > FLASH_BANK1 .text_bank2 : { *(.text.func*) } > FLASH_BANK2 } ``` 注意:实际应用中,待搬运代码需编译为位置无关代码(PIC)或固定地址,以便在 Bank 间移动后仍能正确跳转。 ### 3.2 MDMA 配置 MDMA 配置包括通道选择、传输方向、数据宽度、突发模式等。以下代码初始化 MDMA 用于从 Bank2 搬运代码到 Bank1 的 RAM 区域(或另一 Bank 的空闲区)。 ```c #include "stm32h7xx_hal.h" MDMA_HandleTypeDef hmdma; void MDMA_Init(void) { __HAL_RCC_MDMA_CLK_ENABLE(); hmdma.Instance = MDMA_Channel0; hmdma.Init.Request = MDMA_REQUEST_MEM2MEM; hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; hmdma.Init.Priority = MDMA_PRIORITY_HIGH; hmdma.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; hmdma.Init.SourceInc = MDMA_SRC_INC_WORD; hmdma.Init.DestinationInc = MDMA_DEST_INC_WORD; hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD; hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK; hmdma.Init.BufferTransferLength = 0; // 将在传输前设置 hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_16BEATS; hmdma.Init.DestBurst = MDMA_DEST_BURST_16BEATS; hmdma.Init.SourceBlockAddressOffset = 0; hmdma.Init.DestBlockAddressOffset = 0; hmdma.Init.SourceLinkAddress = 0; hmdma.Init.DestLinkAddress = 0; hmdma.Init.SourceTransferConfig = MDMA_SOURCE_TRANSFER_16BEATS; hmdma.Init.DestTransferConfig = MDMA_DEST_TRANSFER_16BEATS; hmdma.Init.SourceAddress = (uint32_t)0x08100000; // Bank2 起始 hmdma.Init.DestinationAddress = (uint32_t)0x24000000; // RAM 目标 hmdma.Init.BlockDataLength = 0; // 将在传输前设置 HAL_MDMA_Init(&hmdma); } ``` ### 3.3 双 Bank 切换与执行流程 核心思想:当 CPU 从 Bank1 执行时,MDMA 将 Bank2 中的代码搬运到 RAM(或 Bank1 的空闲区),搬运完成后通过中断通知 CPU 跳转到新代码执行;同时,CPU 可继续从 Bank1 执行其他任务。 实现步骤: 1. 系统启动后,CPU 从 Bank1 执行主程序。 2. 主程序初始化 MDMA,并配置传输(源为 Bank2 代码区,目标为 RAM 中的执行区)。 3. 启动 MDMA 传输,同时 CPU 继续执行 Bank1 中的其他代码(如初始化外设)。 4. MDMA 传输完成,触发中断。在中断服务函数中,设置一个标志,主循环检测到标志后,通过函数指针跳转到 RAM 中的代码执行。 5. 当 RAM 中的代码执行完毕(或需要切换回),可反向操作:将 Bank1 中的代码搬运到 RAM 的另一区域,实现交替。 ### 3.4 完整代码示例 以下示例演示了如何将 Bank2 中的函数 `my_function` 搬运到 RAM 并执行。 ```c // 定义函数指针类型 void (*func_ptr)(void); // 在 Bank2 中定义的函数(需编译到指定地址) __attribute__((section(".text_bank2"))) void my_function(void) { // 执行一些计算 volatile int i; for (i = 0; i < 1000; i++); } // MDMA 传输完成回调 void HAL_MDMA_IRQHandler(MDMA_HandleTypeDef *hmdma) { HAL_MDMA_IRQHandler(&hmdma); if (hmdma->Instance == MDMA_Channel0) { // 传输完成,设置标志 extern volatile uint8_t mdma_done; mdma_done = 1; } } int main(void) { HAL_Init(); SystemClock_Config(); MDMA_Init(); // 设置传输参数 hmdma.Init.SourceAddress = (uint32_t)&my_function; // 源地址(Bank2) hmdma.Init.DestinationAddress = (uint32_t)0x24000000; // 目标 RAM hmdma.Init.BlockDataLength = 1024; // 假设函数大小为 1KB HAL_MDMA_Start_IT(&hmdma, hmdma.Init.SourceAddress, hmdma.Init.DestinationAddress, 1024); // 同时执行其他任务 while (!mdma_done) { // 可执行其他初始化 } // 跳转到 RAM 中的代码执行 func_ptr = (void (*)(void))0x24000000; func_ptr(); while (1); } ``` ## 4. 注意事项 - **代码重定位**:搬运的代码必须编译为位置无关(PIC)或固定地址,否则跳转时会出现错误。可使用 `-fPIC` 或手动指定地址。 - **缓存一致性**:MDMA 传输后,需确保 CPU 的 I-Cache 和 D-Cache 与 RAM 内容一致。在跳转前执行 `SCB_InvalidateICache()` 和 `SCB_CleanDCache()`。 - **中断优先级**:MDMA 中断应设为高优先级,避免传输完成信号延迟。 - **Flash 擦写限制**:在搬运过程中,避免对源 Bank 进行擦写操作,否则会导致数据损坏。 - **双 Bank 同步**:若使用 Bank 交替,需确保 CPU 当前执行的 Bank 与 MDMA 操作的 Bank 不同,否则会引发总线冲突。 - **性能测试**:实际性能提升取决于代码大小和缓存命中率,建议使用 `DWT->CYCCNT` 测量执行周期。 ## 5. 总结 通过 MDMA 与双 Bank Flash 交替执行,STM32H7 可以显著减少 Flash 等待状态带来的性能损失。本文提供了从链接脚本到 MDMA 配置的完整流程,并给出了可运行示例。开发者可根据实际需求调整搬运策略,实现代码的零等待执行,适用于高速实时控制、信号处理等场景。