# STM32 双 Bank 模式在线升级失败后自动回滚的 Bootloader 实现细节 ## 一、为什么需要双 Bank 回滚? 传统单 Bank 升级流程中,Bootloader 将新固件写入 Flash 后直接跳转。若新固件损坏或运行异常,设备将无法启动,只能依赖外部烧录器恢复,这在远程维护场景下是灾难性的。 STM32 的双 Bank 模式(如 STM32F7、H7 系列)将 Flash 划分为两个独立的 Bank(Bank0 和 Bank1),每个 Bank 可独立擦写。Bootloader 可运行在 Bank0,同时将新固件写入 Bank1,通过硬件位切换启动 Bank,实现原子级切换。若新固件运行失败,Bootloader 可回切到旧 Bank,保证设备可用。 ## 二、双 Bank 回滚的核心原理 ### 2.1 硬件支持 - **Flash 分区**:以 STM32H743 为例,Flash 总容量 2MB,Bank0 和 Bank1 各 1MB。每个 Bank 的起始地址分别为 0x08000000 和 0x08100000。 - **选项字节**:通过修改选项字节中的 `nDBANK` 位,可配置为单 Bank 或双 Bank 模式。双 Bank 模式下,Flash 编程必须按 Bank 独立操作。 - **启动切换**:通过设置 Flash 控制寄存器(FLASH_CR)中的 `BOR` 位或使用 `FLASH_BOR` 命令,可触发 Bank 切换。切换后,CPU 从新的 Bank 复位启动。 ### 2.2 软件设计框架 Bootloader 位于 Bank0 起始区域,用户应用可位于 Bank0 后半部分或 Bank1。本文采用典型方案: - **Bank0**:Bootloader + 旧应用(App_A) - **Bank1**:新应用(App_B) 升级流程: 1. Bootloader 接收新固件,写入 Bank1。 2. 写入完成后,设置“升级待验证”标志,并触发 Bank 切换,启动 App_B。 3. App_B 运行后,若自检通过,则清除标志,并设置“升级成功”标志。 4. 若 App_B 运行异常(如看门狗超时),系统复位,Bootloader 检测到“升级待验证”标志未清除,自动回滚到 Bank0 的 App_A。 ### 2.3 关键标志管理 标志位存放在 Flash 的独立扇区(如 Bank0 的最后一个扇区),避免被应用覆盖。常用结构: ```c typedef struct { uint32_t magic; // 魔数,如 0xA5A5A5A5 uint32_t state; // 0x01: 待验证, 0x02: 升级成功, 0x03: 回滚 uint32_t version; // 固件版本号 } upgrade_flag_t; ``` ## 三、Bootloader 实现步骤 ### 3.1 初始化与 Bank 检测 ```c void bootloader_init(void) { // 读取当前启动 Bank uint32_t boot_bank = (FLASH->CR & FLASH_CR_BOR) ? 1 : 0; // 读取升级标志 upgrade_flag_t flag; read_flag(&flag); if (flag.magic == 0xA5A5A5A5) { if (flag.state == 0x01) { // 上次升级未验证,回滚 rollback_to_bank0(); } else if (flag.state == 0x02) { // 升级成功,正常启动当前 Bank jump_to_app(boot_bank); } } else { // 无标志,默认启动 Bank0 jump_to_app(0); } } ``` ### 3.2 固件接收与写入 Bank1 使用 YMODEM 或自定义协议接收固件,写入 Bank1 时注意地址偏移。 ```c void write_firmware_to_bank1(uint8_t *data, uint32_t len, uint32_t offset) { uint32_t addr = BANK1_BASE + offset; // 解锁 Flash HAL_FLASH_Unlock(); // 擦除 Bank1(按扇区) FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = FLASH_BANK_1; erase.Sector = 0; erase.NbSectors = 8; // 根据实际扇区数 erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t error; HAL_FLASHEx_Erase(&erase, &error); // 编程数据 for (uint32_t i = 0; i < len; i += 8) { uint64_t temp = 0; memcpy(&temp, data + i, 8); HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, temp); } HAL_FLASH_Lock(); } ``` ### 3.3 触发 Bank 切换 ```c void switch_to_bank1(void) { // 设置升级待验证标志 write_flag(0x01); // 设置 BOR 位,切换 Bank HAL_FLASH_Unlock(); FLASH->CR |= FLASH_CR_BOR; HAL_FLASH_Lock(); // 复位 NVIC_SystemReset(); } ``` ### 3.4 跳转到应用 ```c void jump_to_app(uint32_t bank) { uint32_t app_addr = (bank == 0) ? APP_A_BASE : APP_B_BASE; // 检查栈顶地址是否合法 uint32_t msp = *(volatile uint32_t *)app_addr; if ((msp & 0xFFF00000) != 0x20000000) return; // 设置主栈指针 __set_MSP(msp); // 跳转到复位向量 void (*reset_handler)(void) = (void (*)(void))(*(volatile uint32_t *)(app_addr + 4)); reset_handler(); } ``` ### 3.5 回滚操作 ```c void rollback_to_bank0(void) { // 清除待验证标志,设置为回滚状态 write_flag(0x03); // 确保 BOR 位为 0,即 Bank0 HAL_FLASH_Unlock(); FLASH->CR &= ~FLASH_CR_BOR; HAL_FLASH_Lock(); NVIC_SystemReset(); } ``` ## 四、应用端自检与确认 App_B 启动后,应执行自检(如 CRC 校验、外设测试),通过后调用确认函数: ```c void app_confirm_upgrade(void) { upgrade_flag_t flag; read_flag(&flag); flag.state = 0x02; write_flag(&flag); } ``` 同时,App_B 必须开启独立看门狗(IWDG),若自检失败或运行异常,看门狗超时复位,Bootloader 检测到标志仍为 0x01,执行回滚。 ## 五、注意事项 - **Bank 大小对齐**:确保应用固件不超过 Bank 容量,且链接脚本中 Flash 起始地址正确。 - **中断向量表重定向**:应用代码中需设置 `SCB->VTOR` 为对应 Bank 的起始地址。 - **Flash 编程时序**:双 Bank 模式下,擦写 Bank1 时不影响 Bank0 运行,但需注意电源稳定性。 - **标志存储位置**:建议使用独立扇区,并考虑磨损均衡,避免频繁擦写损坏。 - **回滚条件**:除了看门狗,还可利用 RTC 或外部通信超时作为回滚触发条件。 ## 六、总结 双 Bank 模式为 STM32 提供了硬件级的安全升级方案。通过合理的标志管理和 Bank 切换逻辑,Bootloader 能自动检测升级失败并回滚,极大提升了远程升级的可靠性。本文的实现细节可直接应用于实际项目,开发者可根据具体芯片型号调整 Flash 地址和扇区配置。