引言

在物联网设备中,OTA(空中升级)是必备功能,但升级失败或断电可能导致设备变砖。STM32 的双 Bank Flash 架构(如 F4/F7/H7 系列)允许在运行一个 Bank 时擦写另一个 Bank,从而实现无缝升级。然而,擦写中断(如 ECC 错误、写保护冲突)或升级中途异常,会破坏回滚的原子性。本文聚焦双 Bank 模式下的边界处理,提供一套基于中断标志和状态机的回滚策略,确保系统在任何异常下都能恢复至可用固件。

双 Bank 模式与 Flash 擦写中断原理

双 Bank 架构

  • STM32 双 Bank 设备将 Flash 分为两个独立区域(如 Bank1 和 Bank2),每个 Bank 可独立擦写。
  • 通过选项字节(如 nBOOT1)或软件配置,系统可从任一 Bank 启动。
  • 升级时,应用运行在 Bank A,同时擦写 Bank B;完成后切换启动地址,实现无缝更新。

Flash 擦写中断

  • Flash 操作(擦除/编程)可能触发中断,如:
    • ECC 错误(单/双位错误)
    • 写保护违规
    • 操作超时(如 Flash 忙)
  • STM32 的 Flash 接口(FLASH_IRQHandler)可配置中断使能,用于实时捕获异常。
  • 中断处理需极短,避免影响实时性,通常仅设置标志位。

边界问题分析

在双 Bank OTA 中,关键边界包括:

  1. 擦写中断发生:若在擦写 Bank B 时发生 ECC 错误,固件可能不完整。
  2. 切换失败:启动地址切换后,若新固件校验失败,需回滚。
  3. 断电/复位:升级过程中断电,可能导致两个 Bank 均不可用。

无缝回滚的核心是:确保至少一个 Bank 始终有效。因此,需设计状态机管理升级流程,并在中断中记录错误状态。

配置步骤

1. 启用 Flash 中断

在 STM32CubeMX 或代码中,配置 Flash 中断优先级并使能相关中断源。

/* 使能 Flash ECC 中断和写保护中断 */
FLASH_ITConfig(FLASH_IT_ECC | FLASH_IT_WRPROT, ENABLE);
NVIC_EnableIRQ(FLASH_IRQn);
NVIC_SetPriority(FLASH_IRQn, 1); // 高优先级

2. 定义升级状态机

使用枚举定义状态:IDLE、ERASING、PROGRAMMING、VERIFYING、SWITCHING、ROLLBACK。

typedef enum {
    OTA_IDLE,
    OTA_ERASING,
    OTA_PROGRAMMING,
    OTA_VERIFYING,
    OTA_SWITCHING,
    OTA_ROLLBACK
} OTA_State;

3. 中断处理函数

在中断中仅设置错误标志,不执行复杂操作。

volatile uint32_t flash_error_flags = 0;

void FLASH_IRQHandler(void) {
    if (FLASH_GetITStatus(FLASH_IT_ECC)) {
        flash_error_flags |= FLASH_ERROR_ECC;
        FLASH_ClearITPendingBit(FLASH_IT_ECC);
    }
    if (FLASH_GetITStatus(FLASH_IT_WRPROT)) {
        flash_error_flags |= FLASH_ERROR_WRPROT;
        FLASH_ClearITPendingBit(FLASH_IT_WRPROT);
    }
}

4. 主升级流程

在主循环或任务中,根据状态执行操作,并检查错误标志。

void OTA_Process(void) {
    switch (ota_state) {
        case OTA_ERASING:
            if (FLASH_Erase(BANK_B_ADDR, BANK_SIZE) == FLASH_COMPLETE) {
                ota_state = OTA_PROGRAMMING;
            } else {
                ota_state = OTA_ROLLBACK;
            }
            break;
        case OTA_PROGRAMMING:
            // 写入固件数据
            if (write_firmware() == 0) {
                ota_state = OTA_VERIFYING;
            } else {
                ota_state = OTA_ROLLBACK;
            }
            break;
        case OTA_VERIFYING:
            if (verify_firmware()) {
                ota_state = OTA_SWITCHING;
            } else {
                ota_state = OTA_ROLLBACK;
            }
            break;
        case OTA_SWITCHING:
            // 设置启动地址到 Bank B
            set_boot_bank(BANK_B);
            NVIC_SystemReset(); // 复位生效
            break;
        case OTA_ROLLBACK:
            // 回滚:确保 Bank A 有效,清除错误标志
            rollback_to_bank_a();
            ota_state = OTA_IDLE;
            break;
        default:
            break;
    }
    // 检查中断标志,若出错则强制回滚
    if (flash_error_flags) {
        ota_state = OTA_ROLLBACK;
        flash_error_flags = 0;
    }
}

5. 回滚实现

回滚的核心是保持 Bank A 不变,并清除 Bank B 的无效数据。

void rollback_to_bank_a(void) {
    // 确保启动地址指向 Bank A
    set_boot_bank(BANK_A);
    // 擦除 Bank B(可选,释放空间)
    FLASH_Erase(BANK_B_ADDR, BANK_SIZE);
    // 清除错误标志
    flash_error_flags = 0;
}

完整代码示例(简化)

// 全局变量
volatile OTA_State ota_state = OTA_IDLE;
volatile uint32_t flash_error_flags = 0;

// 中断服务函数
void FLASH_IRQHandler(void) {
    if (FLASH_GetITStatus(FLASH_IT_ECC)) {
        flash_error_flags |= FLASH_ERROR_ECC;
        FLASH_ClearITPendingBit(FLASH_IT_ECC);
    }
    if (FLASH_GetITStatus(FLASH_IT_WRPROT)) {
        flash_error_flags |= FLASH_ERROR_WRPROT;
        FLASH_ClearITPendingBit(FLASH_IT_WRPROT);
    }
}

// 主函数
int main(void) {
    // 初始化 Flash 中断
    FLASH_ITConfig(FLASH_IT_ECC | FLASH_IT_WRPROT, ENABLE);
    NVIC_EnableIRQ(FLASH_IRQn);

    while (1) {
        OTA_Process();
        // 其他任务
    }
}

注意事项

  • 中断优先级:Flash 中断应设为高优先级,但避免在中断中执行 Flash 操作(会阻塞)。
  • 状态机原子性:状态切换需在临界区(如关中断)进行,防止竞态。
  • 校验机制:升级前必须校验固件完整性(如 CRC32),并在切换前再次验证。
  • 双 Bank 启动配置:确保选项字节正确设置,否则切换无效。
  • 断电保护:建议在升级前备份关键参数到独立区域,或使用双备份策略。
  • 测试边界:模拟擦写中断(如人为触发 ECC 错误)验证回滚逻辑。

结语

通过合理利用 Flash 擦写中断和状态机,STM32 双 Bank OTA 可以实现无缝回滚,极大提升系统可靠性。本文提供的方案适用于大多数 STM32 系列,但具体寄存器需参考对应参考手册。在实际项目中,建议结合看门狗和外部存储,构建更健壮的升级机制。