STM32双Bank Flash在线升级失败后自动回滚的完整实现与边界条件分析

1. 为什么需要双Bank Flash回滚机制?

在OTA升级场景中,固件写入Flash的过程可能因断电、通信错误或校验失败而中断。若直接覆盖当前运行的程序,设备将无法启动。STM32(如F7、H7系列)的双Bank Flash允许将Flash划分为两个独立的Bank,一个运行当前固件,另一个写入新固件。升级完成后,通过切换启动Bank实现无缝切换。若新固件无法运行,系统可自动回退到旧Bank,确保设备永不砖化。

2. 双Bank Flash原理

  • 硬件结构:STM32的Flash被分为两个对称的Bank(如H743的2MB Flash分为Bank1和Bank2各1MB)。每个Bank可独立擦写,且支持同时操作(双Bank模式)。
  • 启动配置:通过选项字节(Option Bytes)中的nBOOT1BOOT0引脚,或软件配置FLASH_OPTCR寄存器,可设置从Bank1或Bank2启动。
  • 切换机制:在运行时,通过修改FLASH_OPTCRBOR_LEVSWAP_BANK位,可动态切换启动Bank,无需硬件跳线。

3. 系统架构设计

  • Bootloader:位于固定区域(如Bank1起始地址),负责启动应用、升级管理和回滚决策。
  • App1:运行在Bank1(地址0x08000000),当前版本。
  • App2:运行在Bank2(地址0x08040000),新版本。
  • 升级流程
    1. Bootloader接收新固件,写入Bank2。
    2. 校验通过后,设置标志位(如备份在Flash的upgrade_flag),并切换启动Bank到Bank2,复位。
    3. App2启动后,若运行正常(如心跳检测),则清除标志位,完成升级。
    4. 若App2启动失败(如看门狗超时),Bootloader检测到标志位未清除,自动回滚到Bank1。

4. 关键实现步骤

4.1 配置Flash双Bank模式

在系统初始化时,确保Flash工作在双Bank模式。以STM32H743为例:

void Flash_EnableDualBank(void) {
    FLASH_OBProgramInitTypeDef ob_init;
    HAL_FLASHEx_OBGetConfig(&ob_init);
    if (ob_init.DualBank == 0) {
        ob_init.DualBank = 1;
        HAL_FLASHEx_OBProgram(&ob_init);
        HAL_FLASH_OB_Launch(); // 重启生效
    }
}

4.2 升级标志管理

在Flash的固定区域(如最后一个扇区)存储升级状态:

#define UPGRADE_FLAG_ADDR  0x080FF000  // 示例地址
#define FLAG_UPGRADING     0xA5A5A5A5
#define FLAG_UPGRADE_OK    0x5A5A5A5A
#define FLAG_ROLLBACK      0xDEADBEEF

void WriteFlag(uint32_t addr, uint32_t val);
uint32_t ReadFlag(uint32_t addr);

4.3 Bootloader启动逻辑

Bootloader在启动时检查标志位:

void Bootloader_Start(void) {
    uint32_t flag = ReadFlag(UPGRADE_FLAG_ADDR);
    if (flag == FLAG_UPGRADING) {
        // 上次升级未完成,尝试启动App2,若失败则回滚
        if (App2_CheckValid() && App2_Start()) {
            // App2运行成功,等待其清除标志
        } else {
            // 回滚到App1
            WriteFlag(UPGRADE_FLAG_ADDR, FLAG_ROLLBACK);
            SwitchBank(BANK1);
            NVIC_SystemReset();
        }
    } else {
        // 正常启动App1
        SwitchBank(BANK1);
        // 跳转App1
    }
}

4.4 App2运行成功确认

App2启动后,若初始化成功,应清除升级标志:

void App2_Init(void) {
    // 初始化外设、自检等
    if (SelfTestPassed()) {
        WriteFlag(UPGRADE_FLAG_ADDR, FLAG_UPGRADE_OK);
        // 继续运行
    } else {
        // 触发看门狗复位,让Bootloader回滚
        while(1);
    }
}

4.5 看门狗保护

在Bootloader和App中启用独立看门狗(IWDG),确保异常时复位:

void IWDG_Init(void) {
    IWDG_HandleTypeDef hiwdg;
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
    hiwdg.Init.Reload = 4095;
    HAL_IWDG_Init(&hiwdg);
}

5. 边界条件分析

  • 升级过程中断电:若在写入Bank2时断电,Bank2可能损坏。但Bank1未受影响,Bootloader检测到标志为FLAG_UPGRADING,尝试启动Bank2失败后回滚。
  • App2启动但自检失败:App2主动清除标志前,若自检失败,应进入死循环触发看门狗,Bootloader回滚。
  • 标志位写入失败:Flash写入可能因电压不稳失败。建议使用双备份标志,或使用Flash的ECC校验。
  • Bank切换失败:若SWAP_BANK操作未生效,需检查选项字节是否被锁定。可在Bootloader中先解锁再操作。
  • 回滚后App1数据丢失:若App1在升级期间修改了共享数据,回滚后可能不一致。建议在升级前保存关键数据到备份区域。
  • 多次回滚:若新固件始终无法运行,系统会反复回滚。可增加回滚计数,超过阈值后停止升级,等待用户干预。

6. 完整代码示例(简化)

// main.c - Bootloader
#include "main.h"

#define APP1_ADDR 0x08000000
#define APP2_ADDR 0x08040000

void SwitchBank(uint8_t bank) {
    FLASH_OBProgramInitTypeDef ob;
    HAL_FLASHEx_OBGetConfig(&ob);
    ob.SWAP_BANK = (bank == BANK2) ? 1 : 0;
    HAL_FLASHEx_OBProgram(&ob);
    HAL_FLASH_OB_Launch();
}

int main(void) {
    HAL_Init();
    IWDG_Init();
    Flash_EnableDualBank();
    
    uint32_t flag = ReadFlag(UPGRADE_FLAG_ADDR);
    if (flag == FLAG_UPGRADING) {
        // 尝试启动App2
        if (App2_CheckValid()) {
            SwitchBank(BANK2);
            NVIC_SystemReset();
        } else {
            WriteFlag(UPGRADE_FLAG_ADDR, FLAG_ROLLBACK);
            SwitchBank(BANK1);
            NVIC_SystemReset();
        }
    } else {
        SwitchBank(BANK1);
        // 跳转App1
        void (*app1_entry)(void) = (void (*)(void)) (APP1_ADDR + 4);
        __set_MSP(*(uint32_t*)APP1_ADDR);
        app1_entry();
    }
    while(1);
}

// app2.c - 新固件
void App2_Main(void) {
    if (SelfTest()) {
        WriteFlag(UPGRADE_FLAG_ADDR, FLAG_UPGRADE_OK);
        // 正常运行
    } else {
        while(1); // 触发看门狗
    }
}

7. 注意事项

  • 地址对齐:确保两个Bank的起始地址正确,且向量表偏移已设置(SCB->VTOR)。
  • Flash擦写时间:擦写Bank2时,应关闭中断或使用Flash操作中断,避免干扰。
  • 电源稳定性:升级期间应确保供电稳定,建议使用外部看门狗或电压监测。
  • 测试覆盖:模拟断电、校验失败、App异常等场景,验证回滚可靠性。

8. 总结

双Bank Flash回滚机制是嵌入式OTA升级的安全基石。通过合理的标志位管理和看门狗配合,能有效应对升级失败。但需注意边界条件,如断电时机、标志位可靠性等。本文提供的实现可作为参考,实际项目中需根据具体芯片和需求调整。