# STM32双Bank Flash在线升级失败后自动回滚的Bootloader设计要点 ## 一、为什么需要双Bank Flash? 传统单Bank升级方案中,新固件直接覆盖旧固件,一旦写入过程中断电或校验失败,设备将无法启动。双Bank Flash(如STM32F7/H7系列)将Flash分为两个独立Bank(Bank0和Bank1),可分别存储当前运行固件和新固件。升级时,Bootloader将新固件写入非活动Bank,完成后通过标志位切换启动Bank。若新固件启动失败,Bootloader可自动回滚到旧Bank,确保设备始终可用。 ## 二、双Bank升级与回滚原理 ### 2.1 双Bank内存映射 以STM32H743为例,Flash共2MB,分为Bank0(0x08000000-0x080FFFFF)和Bank1(0x08100000-0x081FFFFF)。默认从Bank0启动,通过设置选项字节或操作Flash控制寄存器(FLASH_CR)的BANK_SWAP位,可切换启动Bank。 ### 2.2 升级流程 1. **Bootloader启动**:检查升级标志(如备份寄存器或Flash特定地址)。 2. **接收固件**:通过UART/CAN/以太网接收新固件,写入非活动Bank。 3. **校验固件**:计算CRC或哈希,与固件头比对。 4. **切换Bank**:设置BANK_SWAP位,并置位升级完成标志。 5. **跳转App**:复位并启动新Bank中的App。 ### 2.3 回滚触发条件 - **App启动失败**:App在启动后需在限定时间内(如5秒)向备份寄存器写入“运行正常”标志,否则Bootloader判定为启动失败。 - **硬件看门狗**:若App未及时喂狗,看门狗复位,Bootloader检测到异常标志后回滚。 - **显式回滚命令**:App可主动请求回滚(如检测到严重错误)。 回滚操作:清除BANK_SWAP位,恢复原Bank启动,并清除升级标志。 ## 三、Bootloader代码实现 以下基于STM32H743,使用HAL库,展示核心逻辑。 ### 3.1 内存映射与标志定义 ```c #define APP_BANK0_ADDR 0x08020000 // Bank0 App起始(跳过Bootloader) #define APP_BANK1_ADDR 0x08120000 // Bank1 App起始 #define UPGRADE_FLAG_ADDR 0x0801FF00 // 升级标志存储地址(Bootloader尾部) #define RUN_OK_FLAG_ADDR 0x0801FF04 // App运行正常标志地址 // 升级标志值 #define UPGRADE_PENDING 0xA5A5A5A5 // 等待升级完成 #define UPGRADE_DONE 0x5A5A5A5A // 升级完成,需验证App #define UPGRADE_FAILED 0xDEADBEEF // 升级失败 ``` ### 3.2 检查并执行回滚 ```c void CheckAndRollback(void) { uint32_t upgrade_flag = *(volatile uint32_t*)UPGRADE_FLAG_ADDR; uint32_t run_ok_flag = *(volatile uint32_t*)RUN_OK_FLAG_ADDR; if (upgrade_flag == UPGRADE_DONE) { // 上次升级完成,但本次启动未收到App运行正常标志 if (run_ok_flag != 0x12345678) { // 回滚:切换回Bank0 FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); ob.Banks = FLASH_BANK_1; // 当前从Bank1启动,需切回Bank0 ob.OptionType = OPTIONBYTE_BANK; ob.Bank = FLASH_BANK_1; ob.BankSwap = FLASH_BANK_SWAP_DISABLE; // 禁用Bank交换 HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); // 触发选项字节重载,复位 // 复位后将从Bank0启动 } else { // App运行正常,清除升级标志 WriteFlash(UPGRADE_FLAG_ADDR, 0); } } } ``` ### 3.3 跳转到App ```c typedef void (*pFunction)(void); void JumpToApp(uint32_t app_addr) { uint32_t app_stack = *(volatile uint32_t*)app_addr; pFunction app_entry = (pFunction)*(volatile uint32_t*)(app_addr + 4); if ((app_stack & 0x2FFE0000) != 0x20000000) return; // 检查栈顶地址合法性 HAL_RCC_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; __disable_irq(); // 设置MSP为App的栈顶 __set_MSP(app_stack); app_entry(); } ``` ### 3.4 主流程 ```c int main(void) { HAL_Init(); SystemClock_Config(); // 检查是否需要回滚 CheckAndRollback(); // 判断当前启动Bank(通过选项字节) FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); uint32_t app_addr = (ob.BankSwap == FLASH_BANK_SWAP_ENABLE) ? APP_BANK1_ADDR : APP_BANK0_ADDR; // 检查App有效性(如校验头部) if (IsAppValid(app_addr)) { JumpToApp(app_addr); } else { // 无效则进入Bootloader升级模式 EnterBootloader(); } while(1); } ``` ### 3.5 App端配合(关键) App在main函数开头必须写入运行正常标志,并启动看门狗。 ```c // App main.c #define RUN_OK_FLAG_ADDR 0x0801FF04 int main(void) { // 写入运行正常标志 *(volatile uint32_t*)RUN_OK_FLAG_ADDR = 0x12345678; // 初始化看门狗,若主循环卡死则复位 MX_IWDG_Init(); while(1) { // 业务逻辑 IWDG_Refresh(); } } ``` ## 四、注意事项 - **Flash写入保护**:Bootloader和App区域需设置读写保护,防止意外擦写。 - **固件头设计**:建议在固件起始处包含魔数、版本号、长度、CRC等,便于Bootloader校验。 - **升级标志存储**:使用备份寄存器(如RTC备份域)更安全,避免Flash磨损,但需注意备份域复位条件。 - **看门狗超时**:App启动后必须在看门狗超时前喂狗,否则回滚机制会误触发。 - **双Bank切换时序**:切换Bank需操作选项字节,注意解锁和重载流程,避免死锁。 - **中断向量表**:App中需设置VTOR寄存器指向对应Bank的起始地址,否则中断异常。 - **测试回滚**:在开发阶段故意制造升级失败(如写入错误固件),验证回滚逻辑。 ## 五、总结 利用STM32双Bank Flash,结合标志位和看门狗,可设计出高可靠的OTA升级方案。本文提供的代码框架覆盖了核心回滚逻辑,开发者可根据具体芯片型号和需求调整。记住:回滚机制的关键在于“快速检测、可靠切换”,务必在真机上反复测试。