# STM32H7 双 Bank Flash 交替执行与 MDMA 零等待代码搬运:配置细节深度解析 ## 1. 为什么需要零等待代码搬运? STM32H7 系列主频高达 480MHz(甚至 550MHz),但内嵌 Flash 的访问速度受限于其物理特性,通常需要插入等待状态(如 7 个等待周期)。这意味着直接从 Flash 取指时,CPU 会频繁停顿,严重降低实时性。虽然 ART(自适应实时加速器)和缓存能缓解,但在确定性要求极高的场景(如电机控制、音频处理)中,仍可能出现抖动。 **解决方案**:将关键代码(如中断服务函数、实时算法)搬运到 RAM 中执行。但传统 DMA 搬运需要 CPU 介入,且搬运期间代码无法执行。利用 STM32H7 的双 Bank Flash 架构,我们可以实现**交替执行**:CPU 从 Bank1 执行代码,同时 MDMA 将 Bank2 中的新代码搬运至 RAM;完成后切换执行源,实现无缝更新。 ## 2. 双 Bank Flash 架构基础 STM32H7 的 Flash 分为两个 Bank(Bank1 和 Bank2),每个 Bank 容量可达 1MB(部分型号)。关键特性: - **独立读访问**:两个 Bank 可同时被访问(如 CPU 读 Bank1,MDMA 读 Bank2)。 - **编程/擦除并行**:对一个 Bank 进行写操作时,另一个 Bank 可正常读。 - **映射切换**:通过选项字节或寄存器,可将 Bank2 映射到地址 0x08000000,实现无缝切换。 **交替执行原理**: 1. 初始状态:CPU 从 Bank1(地址 0x08000000)执行代码。 2. MDMA 将 Bank2 中的新代码(地址 0x08100000)搬运至 RAM(如 AXI SRAM)。 3. 搬运完成后,设置一个标志,CPU 跳转到 RAM 中的代码执行(或通过函数指针调用)。 4. 同时,MDMA 可将 Bank1 中的另一段代码搬运至另一块 RAM,准备下一次切换。 这样,CPU 始终执行 RAM 中的“最新”代码,而 Flash 的等待状态被完全规避。 ## 3. MDMA 配置关键点 MDMA(Master DMA)是 STM32H7 的高性能 DMA,支持 128 位数据传输、链接列表、4GB 地址空间。配置步骤: ### 3.1 时钟使能 ```c __HAL_RCC_MDMA_CLK_ENABLE(); ``` ### 3.2 配置 MDMA 通道 使用 HAL 库或 LL 库。以下为 HAL 示例: ```c MDMA_HandleTypeDef hmdma; void MDMA_Config(void) { hmdma.Instance = MDMA_Channel0; hmdma.Init.Request = MDMA_REQUEST_SW; // 软件触发,便于控制 hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; // 块传输 hmdma.Init.Priority = MDMA_PRIORITY_HIGH; hmdma.Init.Endianness = MDMA_LITTLE_ENDIAN; hmdma.Init.SourceInc = MDMA_SRC_INC_WORD; // 源地址递增 hmdma.Init.DestinationInc = MDMA_DEST_INC_WORD; // 目的地址递增 hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; // 32位 hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD; hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK; hmdma.Init.BufferTransferLength = 0; // 稍后设置 hmdma.Init.SyncMode = MDMA_SYNC_FULL; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; // 注意:以上重复字段仅示意,实际只需设置一次。 // 简化配置: hmdma.Init.Request = MDMA_REQUEST_SW; hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; hmdma.Init.Priority = MDMA_PRIORITY_HIGH; 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.SyncMode = MDMA_SYNC_FULL; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; hmdma.Init.SyncPolarity = MDMA_SYNC_RISING; hmdma.Init.SyncRequest = MDMA_SYNC_SW; hmdma.Init.SyncSignals = MDMA_SYNC_SIGNAL_0; hmdma.Init.SyncValue = 0; hmdma.Init.SyncOrder = MDMA_SYNC_ORDER_0; hmdma.Init.SyncEndianness = MDMA_SYNC_LITTLE_ENDIAN; hmdma.Init.SyncDataSize = MDMA_SYNC_DATASIZE_WORD; hmdma.Init.SyncInc = MDMA_SYNC_INC_OFF; hmdma.Init.SyncAlignment = MDMA_SYNC_DATAALIGN_PACK; hmdma.Init.SyncTransferMode = MDMA_SYNC_BUFFER_TRANSFER; hmdma.Init.SyncBurst = MDMA_SYNC_BURST_1BEAT; // 实际配置请精简,此处为示意。 HAL_MDMA_Init(&hmdma); } ``` **注意**:MDMA 配置项较多,务必参考参考手册逐一核对。建议使用 CubeMX 生成基础配置,再手动调整。 ### 3.3 启动传输 ```c void MDMA_StartTransfer(uint32_t src, uint32_t dst, uint32_t size) { hmdma.Init.BufferTransferLength = size / 4; // 以字为单位 HAL_MDMA_ConfigTransfer(&hmdma, src, dst, size); HAL_MDMA_Start(&hmdma, src, dst, size); } ``` ## 4. 双 Bank 交替执行实现步骤 ### 4.1 链接脚本设置 将需要搬运的代码段放入独立区域,例如: ```c /* 在链接脚本中定义 */ __RAM_EXEC_START = 0x24000000; /* AXI SRAM 起始地址 */ __RAM_EXEC_SIZE = 0x10000; /* 64KB */ SECTIONS { .ram_exec : { *(.ram_exec) } > RAM_EXEC } ``` 在代码中,使用 `__attribute__((section(".ram_exec")))` 标记关键函数。 ### 4.2 初始化流程 ```c void SystemInit_Exec(void) { // 1. 使能 MDMA 时钟 __HAL_RCC_MDMA_CLK_ENABLE(); MDMA_Config(); // 2. 将 Bank2 中的代码搬运到 RAM uint32_t src = 0x08100000; // Bank2 起始地址 uint32_t dst = (uint32_t)&__RAM_EXEC_START; uint32_t size = (uint32_t)&__RAM_EXEC_SIZE; MDMA_StartTransfer(src, dst, size); // 3. 等待传输完成 while (HAL_MDMA_GetState(&hmdma) != HAL_MDMA_STATE_READY); // 4. 跳转到 RAM 中的函数(例如 main_ram) void (*ram_main)(void) = (void (*)(void))dst; ram_main(); } ``` ### 4.3 交替执行策略 - 使用两个 RAM 缓冲区(如 AXI SRAM 和 D2 SRAM),交替作为执行区。 - 当 CPU 执行缓冲区 A 时,MDMA 将下一段代码搬运到缓冲区 B;完成后切换。 - 通过中断或轮询标志实现同步。 ## 5. 完整示例:搬运一个中断服务函数 ```c // 定义在 Flash Bank2 中的函数 __attribute__((section(".ram_exec"))) void ISR_Handler(void) { // 实时处理代码 GPIOE->ODR ^= (1 << 1); } // 主程序 int main(void) { HAL_Init(); SystemClock_Config(); // ... 初始化外设 // 搬运 ISR_Handler 到 RAM extern uint32_t __ram_exec_start; extern uint32_t __ram_exec_end; uint32_t size = (uint32_t)&__ram_exec_end - (uint32_t)&__ram_exec_start; MDMA_StartTransfer((uint32_t)ISR_Handler, (uint32_t)&__ram_exec_start, size); while (HAL_MDMA_GetState(&hmdma) != HAL_MDMA_STATE_READY); // 设置中断向量表指向 RAM(可选) SCB->VTOR = (uint32_t)&__ram_exec_start; // 使能中断,ISR 将从 RAM 执行 HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI1_IRQn); while (1) { // 主循环 } } ``` ## 6. 注意事项与常见陷阱 - **缓存一致性**:MDMA 搬运后,需确保 CPU 缓存与 RAM 内容一致。使用 `SCB_CleanDCache()` 和 `SCB_InvalidateDCache()` 或配置 MPU 区域为非缓存。 - **对齐要求**:MDMA 要求源和目的地址按 32 位对齐,传输长度也需为 4 的倍数。 - **中断安全**:在搬运过程中,避免中断访问正在被写入的 RAM 区域,否则可能读到半更新数据。 - **Flash 编程冲突**:如果同时进行 Flash 编程(如写入日志),需确保 MDMA 读操作不与编程冲突,建议使用互斥机制。 - **链接脚本**:确保 `.ram_exec` 段正确放置,且起始地址在 RAM 区域内。 - **调试**:使用调试器时,注意断点可能影响时序,建议在发布版本中测试。 ## 7. 总结 通过双 Bank Flash 交替执行与 MDMA 搬运,STM32H7 可以完全规避 Flash 等待状态,实现确定性的零等待代码执行。本文详细介绍了架构原理、MDMA 配置、实现步骤及注意事项,为高性能嵌入式应用提供了有力参考。实际项目中,可根据需求调整缓冲区大小和切换策略,以达到最佳性能。