STM32H7 零等待代码搬运:MDMA 与双 Bank Flash 交替执行的配置细节
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能 MCU 上,从 Flash 执行代码常受等待状态影响,尤其在 480 MHz 主频下。本文深入探讨利用 MDMA(Master DMA)与双 Bank Flash 交替执行技术,实现代码在 Flash Bank 间无缝搬运,达到零等待执行效果。文章详细解析 MDMA 配置、Bank 切换机制、链接脚本调整及中断同步策略,并提供完整代码示例,帮助开发者突破 Flash 性能瓶颈,适用于实时性要求极高的嵌入式应用。
# 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 配置的完整流程,并给出了可运行示例。开发者可根据实际需求调整搬运策略,实现代码的零等待执行,适用于高速实时控制、信号处理等场景。