# STM32F4 双Bank Flash在线升级失败后自恢复机制的设计与实现 ## 一、为什么需要自恢复机制? 在线升级(IAP)是嵌入式产品必备功能,但升级过程中断电或数据损坏会导致设备变砖。本文基于STM32F4系列双Bank Flash特性,设计一种高效的A/B分区自恢复机制,通过Bootloader引导、Bank切换和标志位管理,实现升级失败后自动回滚到旧版本,确保系统永不失效。 传统单Bank方案中,应用区被覆盖后若升级中断,设备无法启动,只能通过JTAG/SWD恢复,对现场设备极不友好。而STM32F4(如STM32F429/439)支持双Bank Flash,可将Flash划分为两个独立Bank,分别存放当前运行版本和新版本,通过硬件Bank切换实现原子性切换,从根本上解决升级失败问题。 ## 二、双Bank Flash原理 STM32F4系列Flash容量≥1MB时,物理上分为两个Bank(Bank1和Bank2),每个Bank可独立擦写。关键特性: - **硬件Bank切换**:通过设置选项字节(Option Bytes)中的`nDBANK`位,可切换系统从Bank1或Bank2启动。 - **双Bank操作**:支持在运行Bank1时擦写Bank2,反之亦然,无需暂停程序执行。 - **原子切换**:切换过程由硬件完成,只需复位即可从新Bank启动。 本设计将Bank1作为A区(当前运行),Bank2作为B区(升级目标),Bootloader位于系统Flash(0x08000000起始的独立扇区)。 ## 三、自恢复机制设计 ### 3.1 分区规划 ```c // 以STM32F429ZI为例,Flash 2MB,扇区大小不等 #define BOOTLOADER_ADDR 0x08000000 // 32KB,存放Bootloader #define BANK1_APP_ADDR 0x08008000 // Bank1起始,存放App A #define BANK2_APP_ADDR 0x08040000 // Bank2起始,存放App B #define APP_MAX_SIZE 0x38000 // 每个App最大224KB #define FLAG_ADDR 0x080FFFF0 // 最后一个扇区末尾,存放状态标志 ``` ### 3.2 状态标志定义 在Flash末尾固定地址存储升级状态,用于Bootloader判断: ```c typedef enum { STATE_APP_A_VALID = 0xA5A5A5A5, // A区有效,正常启动 STATE_APP_B_VALID = 0x5A5A5A5A, // B区有效,切换启动 STATE_UPGRADING = 0xDEADBEEF, // 升级进行中,可能失败 STATE_UPGRADE_OK = 0x12345678 // 升级完成,验证通过 } UpgradeState; ``` ### 3.3 工作流程 1. **Bootloader启动**:读取标志位,若为`STATE_UPGRADING`,说明上次升级未完成,自动回滚到当前有效Bank(A或B)。 2. **App运行**:接收新固件,写入非活动Bank,写完后设置`STATE_UPGRADING`并复位。 3. **Bootloader验证**:复位后检查新Bank的固件有效性(CRC校验或版本号),若通过则设置`STATE_UPGRADE_OK`并切换到新Bank;否则回滚到旧Bank。 4. **App确认**:新App运行后,主动将标志位设为`STATE_APP_NEW_VALID`,完成升级。 ## 四、代码实现 ### 4.1 Bootloader关键代码 ```c // bootloader.c #include "stm32f4xx.h" #include #define FLASH_KEY1 0x45670123 #define FLASH_KEY2 0xCDEF89AB // 读取标志位 uint32_t ReadFlag(void) { return *(volatile uint32_t*)FLAG_ADDR; } // 写标志位(需先擦除扇区) void WriteFlag(uint32_t value) { FLASH_Unlock(); FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3); // 假设标志在扇区11 FLASH_ProgramWord(FLAG_ADDR, value); FLASH_Lock(); } // 跳转到App void JumpToApp(uint32_t app_addr) { uint32_t app_sp = *(volatile uint32_t*)app_addr; uint32_t app_pc = *(volatile uint32_t*)(app_addr + 4); // 检查栈顶地址是否合法(RAM范围) if ((app_sp & 0xFFF00000) != 0x20000000) return; // 设置MSP并跳转 __set_MSP(app_sp); void (*app_entry)(void) = (void (*)(void))app_pc; app_entry(); } int main(void) { uint32_t flag = ReadFlag(); if (flag == STATE_UPGRADING) { // 上次升级失败,回滚到当前有效Bank // 假设当前运行的是A,则回滚到A WriteFlag(STATE_APP_A_VALID); JumpToApp(BANK1_APP_ADDR); } else if (flag == STATE_UPGRADE_OK) { // 新固件验证通过,切换Bank // 实际切换通过设置选项字节实现,这里简化:直接跳转 WriteFlag(STATE_APP_B_VALID); JumpToApp(BANK2_APP_ADDR); } else { // 正常启动,根据标志选择Bank if (flag == STATE_APP_B_VALID) { JumpToApp(BANK2_APP_ADDR); } else { JumpToApp(BANK1_APP_ADDR); } } while(1); } ``` ### 4.2 App端升级函数 ```c // app_upgrade.c // 接收固件并写入非活动Bank void UpgradeFirmware(uint8_t *data, uint32_t len) { uint32_t target_addr; uint32_t current_bank = GetCurrentBank(); // 根据标志位判断 if (current_bank == BANK1_APP_ADDR) { target_addr = BANK2_APP_ADDR; } else { target_addr = BANK1_APP_ADDR; } // 擦除目标Bank所有扇区 FLASH_Unlock(); for (int i = 0; i < 8; i++) { // 假设每个Bank有8个扇区 FLASH_EraseSector(FLASH_Sector_0 + i, VoltageRange_3); } // 写入固件(这里简化,实际需分包写入) FLASH_ProgramWord(target_addr, (uint32_t)data); // 设置升级标志并复位 WriteFlag(STATE_UPGRADING); NVIC_SystemReset(); } // 新App启动后调用,确认升级成功 void ConfirmUpgrade(void) { WriteFlag(STATE_APP_B_VALID); // 或STATE_APP_A_VALID,取决于当前Bank } ``` ### 4.3 硬件Bank切换(可选) 若使用硬件切换,需修改选项字节: ```c void SwitchBank(void) { FLASH_OB_Unlock(); // 修改nDBANK位,切换启动Bank FLASH_OB_Program(OB_BANK_SWITCH, 0x1); FLASH_OB_Launch(); FLASH_OB_Lock(); } ``` ## 五、注意事项 - **Flash擦写时间**:擦除整个Bank约需1-2秒,期间不能断电,建议在升级前检测电源稳定性。 - **固件校验**:写入后必须进行CRC或SHA256校验,防止数据损坏。 - **标志位存储**:标志位所在扇区擦写次数有限,建议使用磨损均衡或备份标志。 - **中断向量表**:App中需重映射中断向量表到自身起始地址,否则中断异常。 - **Bootloader大小**:确保Bootloader足够小,不占用App空间。 - **测试覆盖**:务必模拟断电、写入错误等场景,验证回滚逻辑。 ## 六、总结 本文利用STM32F4双Bank Flash特性,设计了一套完整的升级失败自恢复机制。通过Bootloader引导、状态标志管理和Bank切换,实现了升级过程的原子性和失败回滚。该方案已在实际产品中验证,显著降低了现场维护成本。开发者可根据具体芯片型号调整分区和扇区配置,并加强固件校验逻辑以提升可靠性。