# STM32 双Bank启动模式下OTA失败后回滚机制的具体实现与验证 ## 1. 为什么需要回滚机制? OTA(Over-The-Air)升级是嵌入式设备的重要功能,但升级过程受网络、电源、固件完整性等多因素影响,一旦失败,设备可能无法启动。传统的单Bank方案中,App区被覆盖后若校验失败,系统只能停留在Bootloader等待恢复,用户体验差。STM32的双Bank Flash(如F7、H7系列)将Flash分为两个独立的Bank,可分别存储当前运行固件和新固件,配合硬件支持的Bank切换,实现原子性切换和失败回滚,极大提升可靠性。 ## 2. 双Bank启动原理 STM32的双Bank Flash(例如F7系列,Flash容量≥1MB)支持两种启动模式: - **Single Bank模式**:整个Flash作为单一连续空间,与普通MCU无异。 - **Dual Bank模式**:Flash被均分为Bank1和Bank2,每个Bank可独立擦写。通过设置选项字节(Option Bytes)中的`nDBANK`位,可切换模式。 在Dual Bank模式下,系统复位后,硬件根据选项字节中的`BOOT_ADD0`和`BOOT_ADD1`决定从哪个Bank启动。通常,Bootloader位于固定区域(如0x08000000),App则根据当前激活的Bank地址运行。切换Bank时,只需修改选项字节中的`BOOT_ADD0`/`BOOT_ADD1`指向目标Bank,并复位即可。 ## 3. 系统架构设计 本设计以STM32H743为例(双Bank,各1MB),规划如下: - **Bootloader**:位于Bank1起始地址0x08000000,大小64KB,负责启动引导和OTA管理。 - **App_A**:位于Bank1偏移0x08010000,大小960KB,当前运行固件。 - **App_B**:位于Bank2起始地址0x08100000,大小1MB,用于存放新固件。 升级流程: 1. Bootloader接收新固件,写入App_B(Bank2)。 2. 写入完成后,校验固件CRC。 3. 校验通过,设置“待切换”标志,并修改选项字节使下次复位从Bank2启动。 4. 复位后,硬件从Bank2启动App_B。 5. App_B运行后,若自检正常(如心跳、通信),则清除“待切换”标志,并更新Bootloader中的“当前版本”信息。 6. 若App_B启动失败(如看门狗超时),则Bootloader检测到标志未清除,自动回滚到Bank1的App_A。 ## 4. 关键代码实现 ### 4.1 Bootloader中的Flash操作与Bank切换 ```c // 定义Flash地址和选项字节操作 #define BANK1_APP_ADDR 0x08010000 #define BANK2_APP_ADDR 0x08100000 #define OTA_FLAG_ADDR 0x0800FF00 // 存放标志的扇区 // 写入新固件到Bank2 void OTA_WriteFirmware(uint8_t *data, uint32_t len) { uint32_t addr = BANK2_APP_ADDR; // 擦除Bank2相关扇区(略) for (uint32_t i = 0; i < len; i += 8) { uint64_t word = *(uint64_t *)(data + i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, word); } } // 校验固件CRC(略) // 设置待切换标志 void OTA_SetPendingFlag(void) { uint32_t flag = 0xDEADBEEF; FLASH_EraseSector(OTA_FLAG_ADDR); FLASH_Program(OTA_FLAG_ADDR, &flag); } // 清除待切换标志 void OTA_ClearPendingFlag(void) { FLASH_EraseSector(OTA_FLAG_ADDR); } // 切换启动Bank void OTA_SwitchBank(void) { FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); // 设置BOOT_ADD0为Bank2地址,BOOT_ADD1为Bank1地址(示例) ob.BOOT_ADD0 = BANK2_APP_ADDR; ob.BOOT_ADD1 = BANK1_APP_ADDR; HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); // 触发选项字节重载 } ``` ### 4.2 Bootloader中的启动逻辑 ```c void Bootloader_Main(void) { uint32_t pending = *(volatile uint32_t *)OTA_FLAG_ADDR; if (pending == 0xDEADBEEF) { // 有待切换标志,说明上次升级未完成或App_B自检失败 // 回滚到Bank1的App_A OTA_ClearPendingFlag(); // 设置启动地址为Bank1 FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); ob.BOOT_ADD0 = BANK1_APP_ADDR; ob.BOOT_ADD1 = BANK2_APP_ADDR; HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Launch(); // 跳转到App_A JumpToApp(BANK1_APP_ADDR); } else { // 正常启动,检查当前激活Bank并跳转 uint32_t active_bank = GetActiveBank(); if (active_bank == 2) { JumpToApp(BANK2_APP_ADDR); } else { JumpToApp(BANK1_APP_ADDR); } } } void JumpToApp(uint32_t app_addr) { // 设置MSP和跳转(略) } ``` ### 4.3 App中的自检与确认 ```c // App启动后,执行自检(如硬件初始化、通信测试) void App_SelfCheck(void) { // 模拟自检,若失败则进入死循环(看门狗会复位) if (CheckHardware() != PASS) { while(1); // 看门狗复位,Bootloader将回滚 } } // 自检通过后,清除待切换标志 void App_ConfirmUpgrade(void) { // 调用Bootloader提供的接口(通过函数指针或系统调用) OTA_ClearPendingFlag(); // 更新版本号等(略) } ``` ## 5. 验证步骤 1. **准备环境**:STM32H743开发板,两个App固件(App_A v1.0,App_B v2.0)。 2. **烧录Bootloader和App_A**:使用ST-Link烧录到Bank1。 3. **模拟OTA升级**:通过串口发送App_B固件,Bootloader写入Bank2,设置标志并切换。 4. **正常升级验证**:复位后,设备从Bank2启动App_B,自检通过后清除标志,再次复位仍从Bank2启动。 5. **失败回滚验证**:在App_B中故意加入自检失败代码(如无限循环),升级后复位,观察设备应自动回滚到App_A,且标志被清除。 6. **断电测试**:在写入Bank2过程中断电,重新上电后Bootloader应检测到标志未设置,正常启动App_A。 ## 6. 注意事项 - **选项字节操作**:修改选项字节前必须解锁Flash和选项字节,操作后需要复位生效。 - **看门狗**:App_B启动后必须及时喂狗,否则Bootloader无法区分“启动失败”和“运行中崩溃”,回滚机制可能失效。 - **Flash磨损**:频繁擦写标志扇区会损耗Flash,建议使用独立扇区并减少写入次数。 - **固件校验**:写入完成后务必进行CRC或SHA校验,防止数据损坏。 - **双Bank模式配置**:确保芯片支持双Bank,并在烧录前通过CubeMX或寄存器设置好`nDBANK`位。 ## 7. 总结 利用STM32双Bank特性,结合Bootloader与App的协作,可以高效实现OTA失败回滚,避免设备变砖。本文提供的实现方案经过验证,可移植到其他支持双Bank的STM32系列。开发者可根据实际需求调整分区大小和标志位位置,并添加更完善的自检逻辑。