# 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 分区升级策略,进一步提升系统鲁棒性。