STM32H7 双 Bank 无缝 OTA 回滚的边界条件深度剖析

一、双 Bank 架构与 OTA 基础原理

STM32H7 系列(如 H743/H750)内置双 Bank Flash,每个 Bank 容量相等(如 1MB 时各 512KB),支持硬件级 Bank 切换。OTA 升级时,新固件写入非活动 Bank,完成后通过设置选项字节或软件复位切换启动 Bank,实现原子性切换。无缝回滚则依赖在切换前保留旧固件,若新固件启动失败(如校验错误、看门狗超时),系统自动回退至旧 Bank。

关键硬件特性:

  • Bank 独立性:两个 Bank 可同时执行读操作,但擦写操作只能针对非当前执行 Bank(否则总线冲突)。
  • 中断向量重定位:切换 Bank 后,需将 VTOR 寄存器指向新 Bank 的向量表起始地址。
  • 擦写中断:Flash 控制器在擦写完成或错误时触发中断,可用于非阻塞式升级流程,但中断处理需严格遵循时序。

二、边界条件分类与风险分析

1. 擦写中断的时序边界

Flash 擦写操作耗时较长(如页擦除约 20ms),期间 CPU 可继续执行其他任务(如通信),但若在擦写过程中触发中断,需确保:

  • 中断服务程序(ISR)不得访问正在擦写的 Flash 区域(包括读取指令或数据),否则产生总线错误(HardFault)。
  • 擦写操作对中断延迟敏感:若在擦写关键阶段(如写入最后字节)被高优先级中断打断,可能导致擦写失败或 Flash 状态机异常。

典型陷阱:在擦写中断 ISR 中调用 HAL_FLASH_Program 或读取 Flash 状态寄存器,可能引发死锁。

2. Bank 切换的一致性边界

切换 Bank 的瞬间,系统需保证:

  • 所有待保存数据已写入非易失存储(如备份寄存器或 Flash 标志位)。
  • 新固件完整性校验通过(如 CRC 或签名验证),否则切换后无法启动。
  • 切换操作本身是原子的,但复位后启动代码需能识别当前 Bank 并正确跳转。

边界情况:若切换过程中发生掉电,系统可能停留在中间状态(如选项字节写入一半),导致无法启动。需设计冗余标志位。

3. 回滚触发的逻辑边界

回滚条件通常包括:

  • 新固件启动后,在预设时间内未完成初始化或心跳上报。
  • 看门狗复位次数超过阈值。
  • 应用层主动请求回滚。

关键边界:回滚必须发生在旧固件仍有效时,若旧固件已被新固件覆盖(如单 Bank 模式),则无法回滚。双 Bank 模式下,旧固件保留在另一 Bank,但需确保擦写新 Bank 时未误擦旧 Bank。

三、配置步骤与代码实现

1. 硬件与软件准备

  • 使用 STM32H743 开发板,Flash 大小 2MB(双 Bank 各 1MB)。
  • 配置 Flash 延迟周期(等待状态)以匹配 CPU 频率。
  • 启用 Flash 全局中断(FLASH_IRQn)。

2. 双 Bank 模式配置

// 使能双 Bank 模式(在系统初始化时调用)
void Flash_DualBank_Enable(void) {
    FLASH_OBProgramInitTypeDef pOBInit;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    
    pOBInit.OptionType = OPTIONBYTE_BANK;
    pOBInit.BANK = FLASH_BANK_1; // 或 FLASH_BANK_2
    pOBInit.BankType = FLASH_BANK_TYPE_DUAL; // 双 Bank
    HAL_FLASH_OBProgram(&pOBInit);
    
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    // 需复位生效
    NVIC_SystemReset();
}

3. 非阻塞擦写与中断处理

volatile uint32_t flash_operation_done = 0;
volatile uint32_t flash_error_code = 0;

// 擦写中断回调(由 HAL 库调用)
void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue) {
    flash_error_code = ReturnValue;
    flash_operation_done = 1;
}

void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue) {
    flash_operation_done = 1;
}

// 启动非阻塞擦除(以 Bank2 为例)
void Flash_Erase_Bank2_NonBlocking(void) {
    FLASH_EraseInitTypeDef eraseInit;
    uint32_t page_error = 0;
    
    HAL_FLASH_Unlock();
    eraseInit.TypeErase = FLASH_TYPEERASE_MASS; // 或页擦除
    eraseInit.Banks = FLASH_BANK_2;
    eraseInit.Page = 0;
    eraseInit.NbPages = 256; // 根据实际页大小
    
    flash_operation_done = 0;
    if (HAL_FLASHEx_Erase_IT(&eraseInit, &page_error) != HAL_OK) {
        // 启动失败处理
    }
    // 注意:擦除期间禁止访问 Bank2 区域
}

// 中断服务函数
void FLASH_IRQHandler(void) {
    HAL_FLASH_IRQHandler();
}

4. 无缝回滚逻辑实现

#define APP1_ADDR 0x08000000 // Bank1 起始
#define APP2_ADDR 0x08100000 // Bank2 起始(假设偏移 1MB)
#define ROLLBACK_FLAG_ADDR 0x080FF000 // 备份区存储标志

// 启动时检查是否需要回滚
void Check_And_Rollback(void) {
    uint32_t current_bank = (FLASH->OPTCR & FLASH_OPTCR_SWAP_BANK) ? 2 : 1;
    uint32_t app_addr = (current_bank == 1) ? APP1_ADDR : APP2_ADDR;
    
    // 读取回滚标志(存储于固定 Flash 地址)
    uint32_t rollback_flag = *(volatile uint32_t*)ROLLBACK_FLAG_ADDR;
    
    if (rollback_flag == 0xA5A5A5A5) {
        // 新固件启动失败,执行回滚
        if (current_bank == 2) {
            // 切换回 Bank1
            FLASH_OB_SwapBank(); // 自定义函数,切换启动 Bank
            NVIC_SystemReset();
        } else {
            // 已回滚,清除标志
            Flash_Erase_Flag();
        }
    } else {
        // 正常启动,若为新固件则设置标志
        if (current_bank == 2) {
            Flash_Write_Flag(0xA5A5A5A5);
        }
    }
}

// 自定义 Bank 切换函数(需操作选项字节)
void FLASH_OB_SwapBank(void) {
    FLASH_OBProgramInitTypeDef pOBInit;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    pOBInit.OptionType = OPTIONBYTE_BANK;
    pOBInit.BANK = FLASH_BANK_1; // 目标 Bank
    pOBInit.BankType = FLASH_BANK_TYPE_SWAP; // 执行切换
    HAL_FLASH_OBProgram(&pOBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
}

四、边界条件处理策略与注意事项

  • 中断优先级:Flash 擦写中断优先级应设置为低于实时性要求高的中断(如定时器),但高于可延迟中断,避免 ISR 中访问 Flash 区域。
  • 禁止缓存与预取:在擦写期间,建议关闭 Flash 预取和指令缓存(通过修改 FLASH_ACR 寄存器),防止 CPU 从正在擦写的 Bank 取指。
  • 双标志位机制:使用两个独立标志(如标志 A 和 B)记录升级状态,避免单标志位在掉电时损坏。
  • 看门狗配合:在升级过程中定期喂狗,但回滚判断需基于复位计数,而非单纯看门狗超时。
  • 地址映射:确保链接脚本中向量表重定位正确,且中断函数不引用绝对地址(使用相对跳转)。
  • 测试边界:模拟掉电发生在擦写中断、Bank 切换、标志写入等各个阶段,验证系统恢复能力。

五、总结

STM32H7 双 Bank OTA 的无缝回滚并非简单切换,而是涉及 Flash 时序、中断安全、状态一致性的系统工程。开发者需深入理解硬件约束,严格设计边界条件处理逻辑,并通过充分的异常注入测试,才能确保升级过程的万无一失。本文提供的代码框架可作为起点,实际项目中还需结合具体需求细化。