STM32 双Bank启动模式下OTA失败后回滚机制的具体实现与验证
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,升级失败可能导致设备变砖。STM32的双Bank启动模式(如F7/H7系列)为安全回滚提供了硬件基础。本文深入讲解双Bank启动原理,详细演示如何配置Flash分区、实现Bootloader与App的协作,以及通过标志位和备份机制实现OTA失败后的自动回滚,并给出完整的代码示例与验证步骤,帮助开发者构建高可靠的升级方案。
# STM32 双Bank启动模式下OTA失败后回滚机制的具体实现与验证
## 1. 为什么需要回滚机制?
OTA(Over-The-Air)升级是嵌入式设备的重要功能,但升级过程受网络、电源、固件完整性等多因素影响,一旦失败,设备可能无法启动。传统的单Bank方案中,App区被覆盖后若校验失败,系统只能停留在Bootloader等待恢复,用户体验差。STM32的双Bank Flash(如F7、H7系列)将Flash分为两个独立的Bank,可分别存储当前运行固件和新固件,配合硬件支持的Bank切换,实现原子性切换和失败回滚,极大提升可靠性。
## 2. 双Bank启动原理
STM32的双Bank Flash(例如F7系列,Flash容量≥1MB)支持两种启动模式:
- **Single Bank模式**:整个Flash作为单一连续空间,与普通MCU无异。
- **Dual Bank模式**:Flash被均分为Bank1和Bank2,每个Bank可独立擦写。通过设置选项字节(Option Bytes)中的`nDBANK`位,可切换模式。
在Dual Bank模式下,系统复位后,硬件根据选项字节中的`BOOT_ADD0`和`BOOT_ADD1`决定从哪个Bank启动。通常,Bootloader位于固定区域(如0x08000000),App则根据当前激活的Bank地址运行。切换Bank时,只需修改选项字节中的`BOOT_ADD0`/`BOOT_ADD1`指向目标Bank,并复位即可。
## 3. 系统架构设计
本设计以STM32H743为例(双Bank,各1MB),规划如下:
- **Bootloader**:位于Bank1起始地址0x08000000,大小64KB,负责启动引导和OTA管理。
- **App_A**:位于Bank1偏移0x08010000,大小960KB,当前运行固件。
- **App_B**:位于Bank2起始地址0x08100000,大小1MB,用于存放新固件。
升级流程:
1. Bootloader接收新固件,写入App_B(Bank2)。
2. 写入完成后,校验固件CRC。
3. 校验通过,设置“待切换”标志,并修改选项字节使下次复位从Bank2启动。
4. 复位后,硬件从Bank2启动App_B。
5. App_B运行后,若自检正常(如心跳、通信),则清除“待切换”标志,并更新Bootloader中的“当前版本”信息。
6. 若App_B启动失败(如看门狗超时),则Bootloader检测到标志未清除,自动回滚到Bank1的App_A。
## 4. 关键代码实现
### 4.1 Bootloader中的Flash操作与Bank切换
```c
// 定义Flash地址和选项字节操作
#define BANK1_APP_ADDR 0x08010000
#define BANK2_APP_ADDR 0x08100000
#define OTA_FLAG_ADDR 0x0800FF00 // 存放标志的扇区
// 写入新固件到Bank2
void OTA_WriteFirmware(uint8_t *data, uint32_t len) {
uint32_t addr = BANK2_APP_ADDR;
// 擦除Bank2相关扇区(略)
for (uint32_t i = 0; i < len; i += 8) {
uint64_t word = *(uint64_t *)(data + i);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i, word);
}
}
// 校验固件CRC(略)
// 设置待切换标志
void OTA_SetPendingFlag(void) {
uint32_t flag = 0xDEADBEEF;
FLASH_EraseSector(OTA_FLAG_ADDR);
FLASH_Program(OTA_FLAG_ADDR, &flag);
}
// 清除待切换标志
void OTA_ClearPendingFlag(void) {
FLASH_EraseSector(OTA_FLAG_ADDR);
}
// 切换启动Bank
void OTA_SwitchBank(void) {
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
// 设置BOOT_ADD0为Bank2地址,BOOT_ADD1为Bank1地址(示例)
ob.BOOT_ADD0 = BANK2_APP_ADDR;
ob.BOOT_ADD1 = BANK1_APP_ADDR;
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch(); // 触发选项字节重载
}
```
### 4.2 Bootloader中的启动逻辑
```c
void Bootloader_Main(void) {
uint32_t pending = *(volatile uint32_t *)OTA_FLAG_ADDR;
if (pending == 0xDEADBEEF) {
// 有待切换标志,说明上次升级未完成或App_B自检失败
// 回滚到Bank1的App_A
OTA_ClearPendingFlag();
// 设置启动地址为Bank1
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.BOOT_ADD0 = BANK1_APP_ADDR;
ob.BOOT_ADD1 = BANK2_APP_ADDR;
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch();
// 跳转到App_A
JumpToApp(BANK1_APP_ADDR);
} else {
// 正常启动,检查当前激活Bank并跳转
uint32_t active_bank = GetActiveBank();
if (active_bank == 2) {
JumpToApp(BANK2_APP_ADDR);
} else {
JumpToApp(BANK1_APP_ADDR);
}
}
}
void JumpToApp(uint32_t app_addr) {
// 设置MSP和跳转(略)
}
```
### 4.3 App中的自检与确认
```c
// App启动后,执行自检(如硬件初始化、通信测试)
void App_SelfCheck(void) {
// 模拟自检,若失败则进入死循环(看门狗会复位)
if (CheckHardware() != PASS) {
while(1); // 看门狗复位,Bootloader将回滚
}
}
// 自检通过后,清除待切换标志
void App_ConfirmUpgrade(void) {
// 调用Bootloader提供的接口(通过函数指针或系统调用)
OTA_ClearPendingFlag();
// 更新版本号等(略)
}
```
## 5. 验证步骤
1. **准备环境**:STM32H743开发板,两个App固件(App_A v1.0,App_B v2.0)。
2. **烧录Bootloader和App_A**:使用ST-Link烧录到Bank1。
3. **模拟OTA升级**:通过串口发送App_B固件,Bootloader写入Bank2,设置标志并切换。
4. **正常升级验证**:复位后,设备从Bank2启动App_B,自检通过后清除标志,再次复位仍从Bank2启动。
5. **失败回滚验证**:在App_B中故意加入自检失败代码(如无限循环),升级后复位,观察设备应自动回滚到App_A,且标志被清除。
6. **断电测试**:在写入Bank2过程中断电,重新上电后Bootloader应检测到标志未设置,正常启动App_A。
## 6. 注意事项
- **选项字节操作**:修改选项字节前必须解锁Flash和选项字节,操作后需要复位生效。
- **看门狗**:App_B启动后必须及时喂狗,否则Bootloader无法区分“启动失败”和“运行中崩溃”,回滚机制可能失效。
- **Flash磨损**:频繁擦写标志扇区会损耗Flash,建议使用独立扇区并减少写入次数。
- **固件校验**:写入完成后务必进行CRC或SHA校验,防止数据损坏。
- **双Bank模式配置**:确保芯片支持双Bank,并在烧录前通过CubeMX或寄存器设置好`nDBANK`位。
## 7. 总结
利用STM32双Bank特性,结合Bootloader与App的协作,可以高效实现OTA失败回滚,避免设备变砖。本文提供的实现方案经过验证,可移植到其他支持双Bank的STM32系列。开发者可根据实际需求调整分区大小和标志位位置,并添加更完善的自检逻辑。