# 一、双Bank Flash架构与回滚原理 STM32F4系列(如STM32F407/427)的Flash被划分为两个独立的Bank(Bank1和Bank2),每个Bank容量相等(通常为1MB时各512KB)。硬件支持从任一Bank启动,且可通过`SYSCFG_MEMRMP`寄存器动态切换启动Bank。这一特性为OTA提供了天然的双区备份能力: - **Bank1(App A)**:当前运行的应用固件。 - **Bank2(App B)**:新固件下载区或备份区。 **回滚核心思想**:升级时先写入非活动Bank,校验通过后切换启动地址;若新固件启动失败或运行异常,Bootloader检测到标志位后自动回退到旧Bank。整个过程无需外部存储,且Flash擦写操作不影响当前运行固件。 # 二、启动流程总体设计 系统包含三个角色:Bootloader(固定于Bank0起始区)、App A(Bank1)、App B(Bank2)。启动流程如下: 1. **上电复位**:Bootloader首先执行。 2. **检查升级标志**:读取备份寄存器或Flash特定地址的标志位。 3. **校验App有效性**:对目标Bank的固件进行CRC或Magic Number校验。 4. **选择启动Bank**: - 若升级标志有效且新固件校验通过 → 跳转至新Bank。 - 若新固件校验失败或运行超时 → 清除标志,回滚至旧Bank。 5. **运行App**:App启动后,若需升级,通过Bootloader提供的接口写入另一Bank。 **关键设计点**: - 使用备份寄存器(如`RTC->BKPxR`)存储状态,掉电不丢失。 - 每个App固件头部包含版本号、CRC、长度等元数据。 - 设置看门狗(IWDG)防止App死循环导致无法回滚。 # 三、配置步骤 ## 1. 内存分区规划 以STM32F407ZG(1MB Flash)为例: - **Bootloader**:0x08000000 ~ 0x0800FFFF(64KB) - **App A(Bank1)**:0x08010000 ~ 0x0807FFFF(448KB) - **App B(Bank2)**:0x08080000 ~ 0x080FFFFF(512KB) 注意:Bootloader必须位于Bank1起始地址,且App A和B需分别链接到对应地址。 ## 2. 使能双Bank模式 在系统初始化时,通过选项字节或代码启用双Bank操作(部分型号默认开启): ```c // 启用双Bank模式(以STM32F4为例) FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); if ((ob.USERConfig & FLASH_OPTION_BANK_SWAP) == 0) { ob.USERConfig |= FLASH_OPTION_BANK_SWAP; HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); // 重启生效 } ``` ## 3. Bootloader跳转逻辑 ```c // 跳转至指定Bank的App void jump_to_app(uint32_t app_addr) { uint32_t msp = *(volatile uint32_t*)app_addr; void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); // 关闭全局中断,设置MSP,跳转 __disable_irq(); HAL_RCC_DeInit(); SysTick->CTRL = 0; SCB->VTOR = app_addr; __set_MSP(msp); app_reset(); } ``` ## 4. 升级与回滚标志管理 使用备份寄存器(如BKP1)存储状态: ```c #define APP_A_VALID 0xA5A5 #define APP_B_VALID 0x5A5A #define ROLLBACK_FLAG 0xDEAD void set_boot_flag(uint32_t flag) { HAL_PWR_EnableBkUpAccess(); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, flag); } uint32_t get_boot_flag(void) { HAL_PWR_EnableBkUpAccess(); return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); } ``` ## 5. 完整Bootloader主流程示例 ```c int main(void) { HAL_Init(); SystemClock_Config(); uint32_t flag = get_boot_flag(); uint32_t app_addr; // 默认启动App A if (flag == APP_B_VALID) { // 检查App B的CRC(伪代码,需实现CRC校验函数) if (check_crc(APP_B_ADDR) == PASS) { app_addr = APP_B_ADDR; } else { set_boot_flag(ROLLBACK_FLAG); app_addr = APP_A_ADDR; } } else if (flag == ROLLBACK_FLAG) { // 回滚:清除标志,启动App A set_boot_flag(APP_A_VALID); app_addr = APP_A_ADDR; } else { app_addr = APP_A_ADDR; } // 启动看门狗,超时自动复位 MX_IWDG_Init(); jump_to_app(app_addr); while(1); // 若跳转失败,停留此处 } ``` ## 6. App端升级与回滚配合 App运行时,若收到新固件,写入非活动Bank,然后设置标志并复位: ```c // 假设当前运行在Bank1,升级Bank2 void ota_update(uint8_t* data, uint32_t len) { // 擦写Bank2,写入固件 flash_erase_bank(APP_B_ADDR); flash_write(APP_B_ADDR, data, len); // 设置启动标志为App B set_boot_flag(APP_B_VALID); NVIC_SystemReset(); } ``` # 四、注意事项 - **CRC校验**:必须使用硬件CRC或软件CRC,确保固件完整性。建议在固件头部存放CRC值,Bootloader计算整个固件区域。 - **看门狗**:Bootloader和App都应开启独立看门狗(IWDG),防止App卡死导致无法回滚。 - **中断向量表**:App中必须重定位中断向量表(`SCB->VTOR = APP_ADDR`),否则中断异常。 - **Flash擦写保护**:确保Bootloader区域不被App误擦除,可通过选项字节设置读保护(RDP)或写保护(WRP)。 - **升级失败处理**:若升级过程中断电,标志位可能未更新,Bootloader应能识别无效固件并回滚。建议在写入前先备份旧固件(若空间允许)。 - **双Bank切换**:某些型号需通过`SYSCFG_MEMRMP`切换启动地址,但跳转函数中直接设置VTOR即可,无需硬件切换。 - **测试**:务必在真机上模拟断电、擦写中断等场景,验证回滚可靠性。 # 五、总结 利用STM32F4双Bank Flash,配合Bootloader的智能判断,可以低成本实现高可靠的OTA升级。本文提供的启动流程和代码框架可直接应用于实际项目,只需根据具体型号调整Flash地址和CRC算法。记住:回滚机制的核心是“状态明确、校验严格、超时兜底”,三者缺一不可。