STM32H7 双 Bank 模式下利用 Flash 擦写中断实现 Bootloader 无缝跳转
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 系列中,双 Bank Flash 架构允许在运行应用程序的同时擦写另一个 Bank,但传统 Bootloader 跳转常因中断向量表重定位和 Flash 操作阻塞而失败。本文深入解析双 Bank 模式原理,结合 Flash 擦写中断(EOP)与向量表偏移技术,实现从 Bootloader 到 App 的无缝跳转,并提供完整代码示例与工程注意事项,助力开发者构建高可靠性的固件升级系统。
# STM32H7 双 Bank 模式下利用 Flash 擦写中断实现 Bootloader 无缝跳转
## 1. 为什么需要双 Bank 与无缝跳转?
在嵌入式固件升级场景中,传统 Bootloader 通常将 App 写入固定地址,升级时需擦除整个 App 区,导致系统停机。STM32H7 系列(如 H743/H750)支持双 Bank Flash(Bank1 和 Bank2,各 1MB),允许在运行一个 Bank 的同时擦写另一个 Bank,实现“边运行边升级”。但跳转时若处理不当,中断向量表错乱或 Flash 操作未完成,会导致系统崩溃。本文基于 Flash 擦写中断(EOP)确保擦写完成后安全跳转,实现无缝切换。
## 2. 双 Bank 模式与 Flash 擦写中断原理
### 2.1 双 Bank 模式
- STM32H7 Flash 分为 Bank1(地址 0x08000000)和 Bank2(地址 0x08100000),每个 Bank 独立擦写。
- 通过选项字节(Option Bytes)配置 `DBANK` 位,可切换为单 Bank 或双 Bank 模式。默认双 Bank。
- 当运行在 Bank1 时,可对 Bank2 进行擦写操作,反之亦然,但需注意 Flash 控制器(FLASH)的忙状态。
### 2.2 Flash 擦写中断(EOP)
- Flash 操作(编程/擦除)完成后,硬件置位 EOP 标志(FLASH_SR_EOP),若使能中断(FLASH_CR_EOPIE),则触发中断。
- 在中断服务函数中可清除标志,并设置标志位通知主程序操作完成,避免轮询阻塞 CPU。
### 2.3 无缝跳转的关键
- 跳转前需重定位中断向量表(VTOR)到新 App 的起始地址,否则中断服务函数无法正确响应。
- 确保 Flash 擦写操作完全结束(通过 EOP 中断确认),再执行跳转,防止数据损坏。
## 3. 系统架构与配置步骤
### 3.1 内存布局
- Bootloader 位于 Bank1 起始地址(0x08000000),大小 32KB。
- App1 位于 Bank1 偏移 0x8000(0x08008000),App2 位于 Bank2(0x08100000)。
- 本文示例:Bootloader 从 Bank1 启动,升级时擦写 Bank2,完成后跳转到 Bank2 的 App。
### 3.2 配置步骤
1. **使能 Flash 中断**:在 `FLASH_CR` 中设置 `EOPIE` 位。
2. **编写 EOP 中断服务函数**:清除标志并置位全局标志。
3. **擦写目标 Bank**:调用 HAL 库函数擦除扇区并编程数据。
4. **等待 EOP 中断**:主循环等待标志位,超时处理。
5. **跳转准备**:设置 VTOR 到目标地址,关闭全局中断,初始化 MSP,跳转。
## 4. 完整代码示例
以下代码基于 STM32H743,使用 HAL 库。
```c
/* 全局标志:Flash 操作完成 */
volatile uint8_t flash_op_complete = 0;
/* EOP 中断服务函数 */
void FLASH_IRQHandler(void)
{
if (__HAL_FLASH_GET_FLAG(FLASH_FLAG_EOP)) {
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP);
flash_op_complete = 1;
}
}
/* Flash 擦写函数(以擦除 Bank2 为例) */
void Flash_Erase_Bank2(void)
{
FLASH_EraseInitTypeDef erase_init;
uint32_t sector_error = 0;
flash_op_complete = 0;
erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
erase_init.Banks = FLASH_BANK_2; // 选择 Bank2
erase_init.Sector = 0;
erase_init.NbSectors = 8; // 擦除所有扇区(假设每个扇区128KB)
erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&erase_init, §or_error) != HAL_OK) {
// 错误处理
}
HAL_FLASH_Lock();
}
/* 跳转到 App 函数 */
void Jump_To_App(uint32_t app_addr)
{
uint32_t msp_value = *(volatile uint32_t *)app_addr;
void (*app_reset_handler)(void) = (void (*)(void))(*(volatile uint32_t *)(app_addr + 4));
// 关闭全局中断
__disable_irq();
// 重定位中断向量表
SCB->VTOR = app_addr;
// 设置主栈指针并跳转
__set_MSP(msp_value);
app_reset_handler();
}
/* 主函数示例 */
int main(void)
{
HAL_Init();
SystemClock_Config();
// 使能 Flash EOP 中断
HAL_FLASH_Unlock();
__HAL_FLASH_ENABLE_IT(FLASH_IT_EOP);
HAL_FLASH_Lock();
HAL_NVIC_EnableIRQ(FLASH_IRQn);
// 擦除 Bank2(假设升级数据已准备好)
Flash_Erase_Bank2();
// 等待擦除完成(超时 5 秒)
uint32_t timeout = HAL_GetTick() + 5000;
while (!flash_op_complete && HAL_GetTick() < timeout);
if (flash_op_complete) {
// 编程数据到 Bank2(此处省略具体编程代码)
// ...
// 跳转到 Bank2 的 App
Jump_To_App(0x08100000);
} else {
// 超时处理
Error_Handler();
}
while (1);
}
```
## 5. 注意事项
- **中断向量表重定位**:App 工程中必须设置 `VECT_TAB_OFFSET` 为对应偏移(如 0x100000),否则跳转后中断异常。
- **Flash 操作时序**:擦写期间不能访问 Flash 代码区,否则总线忙。建议将擦写函数放在 RAM 中执行(如 `__attribute__((section(".ramfunc")))`)。
- **EOP 中断优先级**:确保其优先级高于 App 中的关键中断,但跳转前应关闭所有中断,避免跳转过程中断。
- **双 Bank 切换**:若 App 需要切换 Bank 运行,需修改选项字节 `DBANK` 并复位,但本文跳转方式无需切换,直接跳转即可。
- **电源稳定性**:擦写 Flash 时电流较大,确保供电稳定,否则可能导致擦写失败。
## 6. 总结
通过利用 STM32H7 双 Bank 架构和 Flash 擦写中断,我们实现了 Bootloader 到 App 的无缝跳转,避免了传统升级的停机问题。关键在于正确配置 EOP 中断、等待操作完成,并重定位向量表。此方案适用于需要在线升级的高可靠性系统,如工业控制器、IoT 设备等。开发者可根据实际需求扩展为 A/B 分区升级策略,进一步提升系统鲁棒性。