STM32 双 Bank 模式下 Flash 擦写中断实现 OTA 无缝回滚的边界处理
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32 双 Bank 架构下,OTA 升级常面临擦写中断与回滚的边界难题。本文深入解析双 Bank 切换原理、Flash 擦写中断机制,并给出基于中断标志位与状态机的无缝回滚实现方案,涵盖配置步骤、完整代码示例及关键注意事项,助你构建高可靠性的嵌入式升级系统。
# 引言
在物联网设备中,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 系列,但具体寄存器需参考对应参考手册。在实际项目中,建议结合看门狗和外部存储,构建更健壮的升级机制。