# STM32双Bank Flash在OTA升级中的原子性切换与回滚策略 ## 1. 为什么需要双Bank Flash? 传统单Bank OTA升级流程:擦除旧固件 → 写入新固件 → 校验 → 跳转。若在写入过程中断电或校验失败,设备将无法启动,只能依赖外部恢复手段(如Bootloader+串口)。双Bank Flash通过硬件将Flash分为两个独立区域(Bank0和Bank1),每个Bank可独立擦写和启动,从而实现原子性切换:新固件写入非活动Bank,完成后通过切换启动地址一次性生效,失败时仍可回退到旧Bank。 ## 2. STM32双Bank原理 以STM32H7系列为例,Flash容量通常为1MB或2MB,支持双Bank模式(需配置选项字节)。每个Bank拥有独立的地址空间和擦除单元(扇区)。关键特性: - **硬件切换**:通过设置选项字节`nBOOT1`和`BOOT_ADD1`,或运行时修改`FLASH_CR`寄存器的`BANK_SW`位,可切换启动Bank。 - **原子性**:切换操作是单次写寄存器,不会因中断或异常导致半切换状态。 - **回滚支持**:若新固件运行异常,可软件触发切换回旧Bank,或由硬件看门狗复位后根据标志位选择启动Bank。 ## 3. 系统架构设计 OTA升级通常包含三个角色: - **Bootloader**:位于固定地址(如0x08000000),负责启动引导和升级控制。 - **App0**:运行在Bank0(如0x08020000)。 - **App1**:运行在Bank1(如0x08040000)。 升级流程: 1. Bootloader接收新固件,写入非活动Bank。 2. 写入完成后,设置升级标志(如备份区中的魔数)。 3. 触发软件复位,Bootloader检查标志,切换Bank并启动新App。 4. 新App运行后,上报状态;若超时未上报,Bootloader自动回滚。 ## 4. 配置步骤 ### 4.1 使能双Bank模式 使用STM32CubeProgrammer或代码设置选项字节: ```c // 以STM32H743为例,设置双Bank模式(DBANK=1) HAL_FLASH_Unlock(); FLASH_OBProgramInitTypeDef ob; ob.OptionType = OPTIONBYTE_BANK; ob.BANK = FLASH_BANK_1; ob.BankConfig = FLASH_BANK_1_2; // 双Bank模式 HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_Lock(); // 需要复位生效 ``` ### 4.2 链接脚本调整 为每个Bank分配独立地址段,例如: ```ld /* Bank0 App */ FLASH_APP0 (rx) : ORIGIN = 0x08020000, LENGTH = 512K /* Bank1 App */ FLASH_APP1 (rx) : ORIGIN = 0x08040000, LENGTH = 512K ``` 编译时通过宏选择目标Bank。 ### 4.3 固件写入非活动Bank 在Bootloader中,根据当前启动Bank选择目标Bank: ```c uint32_t get_target_bank(void) { if (__HAL_FLASH_GET_BANK() == FLASH_BANK_1) { return FLASH_BANK_2; } else { return FLASH_BANK_1; } } void write_firmware(uint32_t target_bank, uint8_t *data, uint32_t size) { uint32_t addr = (target_bank == FLASH_BANK_1) ? APP0_BASE : APP1_BASE; for (uint32_t i = 0; i < size; i += 8) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, *(uint64_t*)(data+i)); } } ``` ### 4.4 原子性切换与回滚 切换Bank的核心操作: ```c void switch_bank_and_reset(void) { // 设置升级标志(备份寄存器或Flash末尾) HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A5); // 表示新固件就绪 // 切换Bank(以H7为例,通过选项字节) FLASH_OBProgramInitTypeDef ob; ob.OptionType = OPTIONBYTE_BOOTADDR; ob.BOOT_ADD0 = (target_bank == FLASH_BANK_1) ? APP0_BASE : APP1_BASE; HAL_FLASHEx_OBProgram(&ob); // 软复位 NVIC_SystemReset(); } ``` 回滚策略:Bootloader启动时检查升级标志,若新App在指定时间内未清除标志(表示运行正常),则自动切换回旧Bank: ```c void check_and_rollback(void) { uint32_t flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); if (flag == 0xA5A5) { // 等待新App上报,若超时则回滚 if (wait_for_app_ready(5000) == TIMEOUT) { // 切换回旧Bank switch_bank(previous_bank); NVIC_SystemReset(); } } } ``` ## 5. 完整代码示例(Bootloader核心逻辑) ```c int main(void) { HAL_Init(); SystemClock_Config(); // 初始化Flash、RTC等 uint32_t current_bank = __HAL_FLASH_GET_BANK(); uint32_t upgrade_flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); if (upgrade_flag == 0xA5A5) { // 尝试启动新App uint32_t new_app_addr = (current_bank == FLASH_BANK_1) ? APP1_BASE : APP0_BASE; if (validate_app(new_app_addr)) { // 清除标志,表示启动成功 HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x0000); jump_to_app(new_app_addr); } else { // 校验失败,回滚 switch_bank(current_bank == FLASH_BANK_1 ? FLASH_BANK_2 : FLASH_BANK_1); NVIC_SystemReset(); } } else { // 正常启动当前Bank uint32_t app_addr = (current_bank == FLASH_BANK_1) ? APP0_BASE : APP1_BASE; jump_to_app(app_addr); } } void jump_to_app(uint32_t addr) { // 设置MSP和跳转 uint32_t msp = *(volatile uint32_t*)addr; __set_MSP(msp); void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(addr+4)); app_reset(); } ``` ## 6. 注意事项 - **地址对齐**:App中断向量表需重定位,在App代码中设置`SCB->VTOR`为对应Bank基地址。 - **擦除限制**:双Bank模式下,擦除操作不能跨越Bank边界,需按扇区操作。 - **电源稳定性**:升级过程中掉电可能导致Bank切换标志未写入,需使用备份寄存器或Flash末尾区域,并确保写入顺序。 - **看门狗**:建议启用独立看门狗,防止新App卡死导致无法回滚。 - **版本兼容**:新旧App的Bootloader接口需保持一致,否则回滚后可能无法再次升级。 ## 7. 总结 双Bank Flash为OTA升级提供了硬件级的原子切换和回滚保障,配合合理的软件策略,可显著提升设备升级的可靠性。本文从原理到实现,展示了完整的配置和代码流程,开发者可根据具体芯片型号调整细节,构建健壮的升级系统。