STM32H7 双 Bank 模式下利用 Flash 擦写中断实现 OTA 无缝回滚的实战设计
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 系列中,双 Bank Flash 架构为 OTA 升级提供了硬件级的安全保障。本文深入探讨如何利用 Flash 擦写中断(EOP 和 WRPERR)与双 Bank 切换机制,设计一个支持无缝回滚的 OTA 方案。文章从原理剖析到代码实现,涵盖 Bank 切换、中断处理、固件校验等关键环节,并给出完整的工程示例,帮助开发者构建高可靠性的嵌入式升级系统。
# STM32H7 双 Bank 模式下的 OTA 无缝回滚实战设计
## 1. 为什么需要双 Bank 与擦写中断?
在嵌入式 OTA 升级中,最怕的是升级过程中断电或写入错误,导致设备变砖。STM32H7 的双 Bank Flash 架构(如 H743/H750)将 Flash 分为两个独立的 Bank(Bank1 和 Bank2),每个 Bank 可独立擦写。配合 Flash 擦写中断(EOP 表示操作完成,WRPERR 表示写保护错误),我们可以实现:
- **无缝切换**:升级完成后,通过切换 Boot 地址立即运行新固件,无需重启。
- **安全回滚**:若新固件校验失败,自动回退到旧 Bank,保证设备可用。
- **实时监控**:利用中断感知擦写状态,避免轮询阻塞,提升系统响应。
## 2. 双 Bank 模式原理
STM32H7 的 Flash 控制器(FLASH)支持两种 Bank 模式:
- **Single Bank**:整个 Flash 作为一个整体,地址连续。
- **Dual Bank**:Flash 分为两个 1MB(或 512KB,取决于型号)的 Bank,每个 Bank 有独立的擦写单元。
在双 Bank 模式下,通过选项字节(Option Bytes)中的 `DBANK` 位配置。当 `DBANK=1` 时,启用双 Bank。此时,Bank1 的起始地址为 `0x08000000`,Bank2 为 `0x08100000`(以 2MB Flash 为例)。
**关键点**:
- 系统始终从 `0x08000000` 启动(除非配置了 Bank 切换)。
- 通过设置 `FLASH_OPTCR` 寄存器的 `SWAP_BANK` 位,可以交换 Bank1 和 Bank2 的映射,使得系统从另一个 Bank 启动。
## 3. Flash 擦写中断机制
STM32H7 的 Flash 操作(编程/擦除)是异步的,完成或出错时会产生中断。相关中断标志位:
- `EOP`(End of Operation):操作成功完成。
- `WRPERR`(Write Protection Error):写入保护区域。
- `PGAERR`(Programming Alignment Error):编程对齐错误。
- `SIZERR`(Size Error):大小错误。
在中断服务函数(ISR)中,我们可以检查这些标志,并清除中断,从而避免轮询等待,提高 CPU 利用率。
## 4. 系统架构设计
我们的 OTA 方案分为三个部分:
1. **Bootloader**:位于 Bank1 起始区域,负责固件接收、校验和跳转。
2. **App1**:当前运行固件,位于 Bank1 后半部分。
3. **App2**:新固件下载区,位于 Bank2。
升级流程:
- App1 接收新固件写入 Bank2。
- 写入完成后,校验 CRC。
- 若成功,设置 `SWAP_BANK` 位,并软复位,Bootloader 从 Bank2 启动新固件。
- 若新固件运行失败(如看门狗复位),Bootloader 检测到标志,清除 `SWAP_BANK`,回滚到 Bank1。
## 5. 关键代码实现
### 5.1 配置 Flash 双 Bank 模式
在系统初始化时,检查并配置双 Bank 模式(通常由 Bootloader 完成):
```c
void Flash_EnableDualBank(void) {
FLASH_OBProgramInitTypeDef obInit;
HAL_FLASHEx_OBGetConfig(&obInit);
if (obInit.DualBank == 0) {
obInit.DualBank = 1;
HAL_FLASHEx_OBProgram(&obInit);
// 需要复位生效
HAL_FLASH_OB_Launch();
}
}
```
### 5.2 Flash 擦写中断配置
使能 Flash 中断,并编写中断服务函数:
```c
void Flash_EnableInterrupt(void) {
__HAL_FLASH_ENABLE_IT(FLASH_IT_EOP | FLASH_IT_WRPERR | FLASH_IT_PGAERR);
HAL_NVIC_EnableIRQ(FLASH_IRQn);
}
void FLASH_IRQHandler(void) {
uint32_t flags = __HAL_FLASH_GET_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR);
if (flags & FLASH_FLAG_EOP) {
// 操作完成,可以继续下一步
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP);
// 设置事件标志,通知主循环
flash_op_done = 1;
}
if (flags & FLASH_FLAG_WRPERR) {
// 写保护错误,处理错误
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR);
flash_error = FLASH_ERROR_WRPERR;
}
if (flags & FLASH_FLAG_PGAERR) {
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGAERR);
flash_error = FLASH_ERROR_PGAERR;
}
}
```
### 5.3 固件写入 Bank2(带中断等待)
```c
uint8_t Flash_WriteBank2(uint32_t destAddr, uint8_t *data, uint32_t size) {
uint32_t addr = destAddr;
uint32_t remaining = size;
// 解锁 Flash
HAL_FLASH_Unlock();
// 擦除 Bank2(假设 Bank2 起始地址为 0x08100000)
FLASH_EraseInitTypeDef eraseInit;
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Banks = FLASH_BANK_2;
eraseInit.Sector = 0;
eraseInit.NbSectors = 8; // 根据实际扇区数调整
eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t sectorError = 0;
HAL_FLASHEx_Erase(&eraseInit, §orError);
// 等待擦除完成(中断方式)
while (!flash_op_done && !flash_error) {}
if (flash_error) { HAL_FLASH_Lock(); return 1; }
flash_op_done = 0;
// 编程数据
while (remaining > 0) {
uint32_t word = 0;
// 组装 32 位数据(注意对齐)
memcpy(&word, data, 4);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, word);
while (!flash_op_done && !flash_error) {}
if (flash_error) { HAL_FLASH_Lock(); return 1; }
flash_op_done = 0;
addr += 4;
data += 4;
remaining -= 4;
}
HAL_FLASH_Lock();
return 0;
}
```
### 5.4 Bank 切换与回滚
```c
void JumpToBank2(void) {
// 设置 SWAP_BANK 位
HAL_FLASH_OB_Unlock();
__HAL_FLASH_SWAP_BANK();
HAL_FLASH_OB_Lock();
// 软复位
NVIC_SystemReset();
}
void RollbackToBank1(void) {
// 清除 SWAP_BANK 位
HAL_FLASH_OB_Unlock();
__HAL_FLASH_CLEAR_SWAP_BANK();
HAL_FLASH_OB_Lock();
// 复位后从 Bank1 启动
NVIC_SystemReset();
}
```
### 5.5 Bootloader 中的固件校验与回滚逻辑
```c
void Bootloader_CheckAndRun(void) {
// 检查回滚标志(存储在备份寄存器或 Flash 特定区域)
if (CheckRollbackFlag()) {
RollbackToBank1();
}
// 检查 Bank2 固件有效性(如 CRC)
if (IsFirmwareValid(0x08100000)) {
JumpToBank2();
} else {
// 运行 Bank1 固件
JumpToBank1();
}
}
```
## 6. 注意事项
- **中断优先级**:Flash 中断优先级应设置较高,避免在擦写过程中被其他中断打断导致超时。
- **擦写时间**:STM32H7 擦除一个扇区大约需要 1-2 秒,期间系统不能进入低功耗模式,否则可能失败。
- **数据对齐**:Flash 编程必须按 32 位字对齐,否则产生 PGAERR。
- **回滚标志**:建议使用备份寄存器(如 RTC 备份域)存储回滚标志,避免占用 Flash 空间。
- **看门狗**:在擦写过程中喂狗,防止升级过程中复位。
## 7. 总结
通过利用 STM32H7 的双 Bank 架构和 Flash 擦写中断,我们实现了一个高效、安全的 OTA 升级方案。中断机制避免了轮询等待,提升了系统响应;双 Bank 切换保证了无缝升级和快速回滚。该设计适用于对可靠性要求极高的工业、汽车等嵌入式应用。