# 引言 在物联网设备中,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 中断优先级并使能相关中断源。 ```c /* 使能 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。 ```c typedef enum { OTA_IDLE, OTA_ERASING, OTA_PROGRAMMING, OTA_VERIFYING, OTA_SWITCHING, OTA_ROLLBACK } OTA_State; ``` ## 3. 中断处理函数 在中断中仅设置错误标志,不执行复杂操作。 ```c 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. 主升级流程 在主循环或任务中,根据状态执行操作,并检查错误标志。 ```c 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 的无效数据。 ```c 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; } ``` # 完整代码示例(简化) ```c // 全局变量 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 系列,但具体寄存器需参考对应参考手册。在实际项目中,建议结合看门狗和外部存储,构建更健壮的升级机制。