引言
在物联网设备中,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 中,关键边界包括:
- 擦写中断发生:若在擦写 Bank B 时发生 ECC 错误,固件可能不完整。
- 切换失败:启动地址切换后,若新固件校验失败,需回滚。
- 断电/复位:升级过程中断电,可能导致两个 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 系列,但具体寄存器需参考对应参考手册。在实际项目中,建议结合看门狗和外部存储,构建更健壮的升级机制。