# 引言 在工业、医疗等对可靠性要求极高的嵌入式场景中,OTA(Over-The-Air)升级是必备功能,但升级过程中断电、通信错误等意外常导致固件损坏。STM32F4系列(如STM32F407/427)支持双Bank Flash,每个Bank可独立擦写,配合硬件交换机制,可实现“升级失败自动回滚”的稳健启动流程。本文将深入剖析这一设计。 # 原理:双Bank Flash与启动流程 STM32F4的Flash分为两个Bank(Bank1和Bank2),每个Bank容量相等(如1MB器件各512KB)。关键特性是**Flash地址映射可交换**:通过设置选项字节`BOOT_SW`或运行时操作`FLASH_CR`的`SWAP_BANK`位,可将Bank1和Bank2的物理地址对调。这意味着CPU可以从Bank1启动,也可以从Bank2启动,而无需改变向量表地址。 启动流程设计如下: - **Bootloader**(常驻Bank1,不可被覆盖)负责检查App有效性,并决定从哪个Bank启动。 - **App**(运行在Bank2或Bank1)接收新固件,写入非活动Bank,完成后置位标志并复位。 - **Bootloader**在启动时检查标志:若升级完成,则交换Bank并启动新App;若升级失败(标志未置位或校验失败),则继续从旧App启动,实现自恢复。 # 硬件与软件准备 - 硬件:STM32F4系列开发板(如STM32F407-Discovery),外部Flash可选(用于存储固件包)。 - 软件:STM32CubeIDE或Keil,HAL库。 - 关键配置: - 分区Flash:Bank1(0x08000000-0x0807FFFF)存放Bootloader和旧App;Bank2(0x08080000-0x080FFFFF)存放新App。 - 设置选项字节:`BOOT_SW`位(在`FLASH_OBProgramInitTypeDef`中配置)允许软件交换Bank。 # 启动流程设计 ## 1. 状态标志存储 使用Flash的最后一个扇区(如Bank1的最后一个扇区)存储升级状态标志。定义如下: ```c #define APP_FLAG_ADDR 0x0807F000 // 位于Bank1末尾 #define FLAG_UPDATE_PENDING 0xA5A5A5A5 #define FLAG_UPDATE_OK 0x5A5A5A5A #define FLAG_APP_VALID 0x12345678 ``` ## 2. Bootloader启动逻辑 Bootloader上电后执行以下步骤: - 读取标志区,判断是否有升级请求。 - 若有`FLAG_UPDATE_PENDING`,则检查新App(Bank2)的校验和(如CRC32)。 - 校验通过:设置`FLAG_UPDATE_OK`,交换Bank,跳转至新App。 - 校验失败:清除标志,直接跳转至旧App(Bank1)。 - 若无升级请求,则检查当前Bank的App有效性,无效则尝试另一Bank。 关键代码(HAL库): ```c void Bootloader_Start(void) { uint32_t flag = *(volatile uint32_t*)APP_FLAG_ADDR; if (flag == FLAG_UPDATE_PENDING) { // 校验Bank2中的新App if (CheckAppCRC(BANK2_APP_ADDR, APP_MAX_SIZE) == 0) { // 设置升级完成标志 WriteFlag(FLAG_UPDATE_OK); // 交换Bank,使Bank2映射到0x08000000 FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); ob.BOOT_SW = FLASH_BOOT_SW_BANK2; // 根据实际定义 HAL_FLASHEx_OBProgram(&ob); // 复位以重新映射 NVIC_SystemReset(); } else { // 校验失败,清除标志,回滚到旧App WriteFlag(FLAG_APP_VALID); JumpToApp(BANK1_APP_ADDR); } } else { // 正常启动,检查当前Bank的App if (CheckAppCRC(BANK1_APP_ADDR, APP_MAX_SIZE) == 0) { JumpToApp(BANK1_APP_ADDR); } else { // 尝试Bank2 JumpToApp(BANK2_APP_ADDR); } } } ``` ## 3. App升级流程 App在运行中收到新固件,将其写入非活动Bank(若当前从Bank1运行,则写入Bank2)。写入完成后,设置`FLAG_UPDATE_PENDING`并复位。 ```c void OTA_Update(uint8_t *data, uint32_t len) { // 擦除Bank2,写入数据(省略具体Flash操作) Flash_EraseBank(BANK2); Flash_WriteData(BANK2_APP_ADDR, data, len); // 设置升级待定标志 WriteFlag(FLAG_UPDATE_PENDING); NVIC_SystemReset(); } ``` ## 4. 跳转函数实现 跳转至App时,需设置MSP和复位向量: ```c void JumpToApp(uint32_t app_addr) { uint32_t msp = *(volatile uint32_t*)app_addr; void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); __set_MSP(msp); app_reset(); } ``` # 注意事项 - **Bank交换的时序**:交换Bank后,必须复位才能生效,且需确保App的链接地址与Bank基地址匹配。 - **中断向量表重定位**:App中需在`SystemInit`后设置`SCB->VTOR`为当前Bank的基地址。 - **Flash擦写保护**:Bootloader区域应设置读保护或写保护,防止意外覆盖。 - **校验算法**:建议使用CRC32或SHA256,确保固件完整性。 - **测试**:模拟断电场景,验证升级失败后能回滚。 # 总结 利用STM32F4的双Bank特性,结合精心设计的启动流程,可以显著提升OTA升级的可靠性。本文提供的方案在升级失败时能自动回滚至旧版本,避免了设备变砖的风险。开发者可根据实际需求调整标志存储和校验策略,构建更健壮的升级系统。