一、双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操作(部分型号默认开启):

// 启用双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跳转逻辑

// 跳转至指定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)存储状态:

#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主流程示例

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,然后设置标志并复位:

// 假设当前运行在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算法。记住:回滚机制的核心是“状态明确、校验严格、超时兜底”,三者缺一不可。