# STM32双Bank Flash在线升级失败后自动回滚的完整实现与边界条件分析 ## 1. 为什么需要双Bank Flash回滚机制? 在OTA升级场景中,固件写入Flash的过程可能因断电、通信错误或校验失败而中断。若直接覆盖当前运行的程序,设备将无法启动。STM32(如F7、H7系列)的双Bank Flash允许将Flash划分为两个独立的Bank,一个运行当前固件,另一个写入新固件。升级完成后,通过切换启动Bank实现无缝切换。若新固件无法运行,系统可自动回退到旧Bank,确保设备永不砖化。 ## 2. 双Bank Flash原理 - **硬件结构**:STM32的Flash被分为两个对称的Bank(如H743的2MB Flash分为Bank1和Bank2各1MB)。每个Bank可独立擦写,且支持同时操作(双Bank模式)。 - **启动配置**:通过选项字节(Option Bytes)中的`nBOOT1`和`BOOT0`引脚,或软件配置`FLASH_OPTCR`寄存器,可设置从Bank1或Bank2启动。 - **切换机制**:在运行时,通过修改`FLASH_OPTCR`的`BOR_LEV`和`SWAP_BANK`位,可动态切换启动Bank,无需硬件跳线。 ## 3. 系统架构设计 - **Bootloader**:位于固定区域(如Bank1起始地址),负责启动应用、升级管理和回滚决策。 - **App1**:运行在Bank1(地址0x08000000),当前版本。 - **App2**:运行在Bank2(地址0x08040000),新版本。 - **升级流程**: 1. Bootloader接收新固件,写入Bank2。 2. 校验通过后,设置标志位(如备份在Flash的`upgrade_flag`),并切换启动Bank到Bank2,复位。 3. App2启动后,若运行正常(如心跳检测),则清除标志位,完成升级。 4. 若App2启动失败(如看门狗超时),Bootloader检测到标志位未清除,自动回滚到Bank1。 ## 4. 关键实现步骤 ### 4.1 配置Flash双Bank模式 在系统初始化时,确保Flash工作在双Bank模式。以STM32H743为例: ```c void Flash_EnableDualBank(void) { FLASH_OBProgramInitTypeDef ob_init; HAL_FLASHEx_OBGetConfig(&ob_init); if (ob_init.DualBank == 0) { ob_init.DualBank = 1; HAL_FLASHEx_OBProgram(&ob_init); HAL_FLASH_OB_Launch(); // 重启生效 } } ``` ### 4.2 升级标志管理 在Flash的固定区域(如最后一个扇区)存储升级状态: ```c #define UPGRADE_FLAG_ADDR 0x080FF000 // 示例地址 #define FLAG_UPGRADING 0xA5A5A5A5 #define FLAG_UPGRADE_OK 0x5A5A5A5A #define FLAG_ROLLBACK 0xDEADBEEF void WriteFlag(uint32_t addr, uint32_t val); uint32_t ReadFlag(uint32_t addr); ``` ### 4.3 Bootloader启动逻辑 Bootloader在启动时检查标志位: ```c void Bootloader_Start(void) { uint32_t flag = ReadFlag(UPGRADE_FLAG_ADDR); if (flag == FLAG_UPGRADING) { // 上次升级未完成,尝试启动App2,若失败则回滚 if (App2_CheckValid() && App2_Start()) { // App2运行成功,等待其清除标志 } else { // 回滚到App1 WriteFlag(UPGRADE_FLAG_ADDR, FLAG_ROLLBACK); SwitchBank(BANK1); NVIC_SystemReset(); } } else { // 正常启动App1 SwitchBank(BANK1); // 跳转App1 } } ``` ### 4.4 App2运行成功确认 App2启动后,若初始化成功,应清除升级标志: ```c void App2_Init(void) { // 初始化外设、自检等 if (SelfTestPassed()) { WriteFlag(UPGRADE_FLAG_ADDR, FLAG_UPGRADE_OK); // 继续运行 } else { // 触发看门狗复位,让Bootloader回滚 while(1); } } ``` ### 4.5 看门狗保护 在Bootloader和App中启用独立看门狗(IWDG),确保异常时复位: ```c void IWDG_Init(void) { IWDG_HandleTypeDef hiwdg; hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = IWDG_PRESCALER_64; hiwdg.Init.Reload = 4095; HAL_IWDG_Init(&hiwdg); } ``` ## 5. 边界条件分析 - **升级过程中断电**:若在写入Bank2时断电,Bank2可能损坏。但Bank1未受影响,Bootloader检测到标志为`FLAG_UPGRADING`,尝试启动Bank2失败后回滚。 - **App2启动但自检失败**:App2主动清除标志前,若自检失败,应进入死循环触发看门狗,Bootloader回滚。 - **标志位写入失败**:Flash写入可能因电压不稳失败。建议使用双备份标志,或使用Flash的ECC校验。 - **Bank切换失败**:若`SWAP_BANK`操作未生效,需检查选项字节是否被锁定。可在Bootloader中先解锁再操作。 - **回滚后App1数据丢失**:若App1在升级期间修改了共享数据,回滚后可能不一致。建议在升级前保存关键数据到备份区域。 - **多次回滚**:若新固件始终无法运行,系统会反复回滚。可增加回滚计数,超过阈值后停止升级,等待用户干预。 ## 6. 完整代码示例(简化) ```c // main.c - Bootloader #include "main.h" #define APP1_ADDR 0x08000000 #define APP2_ADDR 0x08040000 void SwitchBank(uint8_t bank) { FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); ob.SWAP_BANK = (bank == BANK2) ? 1 : 0; HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); } int main(void) { HAL_Init(); IWDG_Init(); Flash_EnableDualBank(); uint32_t flag = ReadFlag(UPGRADE_FLAG_ADDR); if (flag == FLAG_UPGRADING) { // 尝试启动App2 if (App2_CheckValid()) { SwitchBank(BANK2); NVIC_SystemReset(); } else { WriteFlag(UPGRADE_FLAG_ADDR, FLAG_ROLLBACK); SwitchBank(BANK1); NVIC_SystemReset(); } } else { SwitchBank(BANK1); // 跳转App1 void (*app1_entry)(void) = (void (*)(void)) (APP1_ADDR + 4); __set_MSP(*(uint32_t*)APP1_ADDR); app1_entry(); } while(1); } // app2.c - 新固件 void App2_Main(void) { if (SelfTest()) { WriteFlag(UPGRADE_FLAG_ADDR, FLAG_UPGRADE_OK); // 正常运行 } else { while(1); // 触发看门狗 } } ``` ## 7. 注意事项 - **地址对齐**:确保两个Bank的起始地址正确,且向量表偏移已设置(`SCB->VTOR`)。 - **Flash擦写时间**:擦写Bank2时,应关闭中断或使用Flash操作中断,避免干扰。 - **电源稳定性**:升级期间应确保供电稳定,建议使用外部看门狗或电压监测。 - **测试覆盖**:模拟断电、校验失败、App异常等场景,验证回滚可靠性。 ## 8. 总结 双Bank Flash回滚机制是嵌入式OTA升级的安全基石。通过合理的标志位管理和看门狗配合,能有效应对升级失败。但需注意边界条件,如断电时机、标志位可靠性等。本文提供的实现可作为参考,实际项目中需根据具体芯片和需求调整。