引言

在工业、医疗等对可靠性要求极高的嵌入式场景中,OTA(Over-The-Air)升级是必备功能,但升级过程中断电、通信错误等意外常导致固件损坏。STM32F4系列(如STM32F407/427)支持双Bank Flash,每个Bank可独立擦写,配合硬件交换机制,可实现“升级失败自动回滚”的稳健启动流程。本文将深入剖析这一设计。

原理:双Bank Flash与启动流程

STM32F4的Flash分为两个Bank(Bank1和Bank2),每个Bank容量相等(如1MB器件各512KB)。关键特性是Flash地址映射可交换:通过设置选项字节BOOT_SW或运行时操作FLASH_CRSWAP_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的最后一个扇区)存储升级状态标志。定义如下:

#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库):

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并复位。

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和复位向量:

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升级的可靠性。本文提供的方案在升级失败时能自动回滚至旧版本,避免了设备变砖的风险。开发者可根据实际需求调整标志存储和校验策略,构建更健壮的升级系统。