STM32H7双Bank Flash在线升级失败后自动回滚的硬件级保障机制设计
1. 为什么需要硬件级回滚保障?
传统单Bank Flash升级流程:擦除旧固件 → 写入新固件 → 跳转执行。若在写入过程中断电或校验失败,设备将停留在半成品状态,无法启动。虽然可通过Bootloader二次引导,但Bootloader本身也可能被破坏,且恢复流程复杂。STM32H7的双Bank Flash从硬件层面解决了此问题:两个独立的Bank可分别存储新旧固件,通过选项字节(Option Bytes)原子切换启动地址,确保系统永远有一个可用的固件版本。
2. STM32H7双Bank Flash原理
2.1 硬件架构
- STM32H7(如H743/H750)内置2MB Flash,分为Bank1和Bank2,各1MB(或根据型号不同,如H723为双Bank各512KB)。
- 每个Bank可独立擦写,且支持同时读取一个Bank并写入另一个Bank(RWW,Read-While-Write)。
- 系统启动时,通过选项字节
BOOT_ADD0和BOOT_ADD1决定从哪个Bank启动。默认从Bank1启动(地址0x08000000)。
2.2 切换机制
- 通过修改选项字节中的
BOOT_ADD0(对应Bank1)或BOOT_ADD1(对应Bank2)的地址值,并触发系统复位,即可切换启动Bank。 - 关键点:选项字节的修改需要解锁Flash接口,且写入后必须复位才生效。但复位前,当前Bank仍在运行,因此切换是原子的——要么旧固件继续运行,要么新固件启动,不存在中间状态。
3. 双Bank升级与回滚设计
3.1 整体流程
- 固件A(旧版) 运行在Bank1(地址0x08000000)。
- 下载固件B(新版)到Bank2(地址0x08080000,以1MB Bank为例)。
- 对固件B进行CRC校验,若通过,则设置选项字节
BOOT_ADD1指向Bank2,并复位。 - 系统从Bank2启动,运行固件B。
- 固件B运行后,执行自检(如外设初始化、通信握手)。若自检成功,则标记“升级成功”,并可选地将固件B复制到Bank1(或直接以Bank2为运行区)。若自检失败,则软件触发回滚:修改选项字节
BOOT_ADD0指向Bank1,复位后回到固件A。
3.2 回滚触发条件
- 升级后启动超时(如看门狗复位)。
- 应用层主动上报错误(如关键外设初始化失败)。
- 用户手动强制回滚(通过外部命令)。
4. 配置步骤(基于STM32CubeMX + HAL库)
4.1 内存布局规划
- 在链接脚本(.ld)中,为Bank1和Bank2分别定义独立的固件起始地址和大小。例如:
/* Bank1: 0x08000000, 长度1MB */ /* Bank2: 0x08080000, 长度1MB */ - 编译时,通过宏或不同工程生成两个固件:
firmware_A.hex(Bank1)和firmware_B.hex(Bank2)。
4.2 选项字节操作函数
#include "stm32h7xx_hal.h"
// 解锁Flash接口
void Flash_Unlock(void) {
HAL_FLASH_Unlock();
// 解锁选项字节区域
HAL_FLASH_OB_Unlock();
}
// 设置启动Bank
void Flash_SetBootBank(uint8_t bank) {
FLASH_OBProgramInitTypeDef ob;
ob.OptionType = OPTIONBYTE_USER;
ob.USERType = OB_USER_BOOT_ADD0 | OB_USER_BOOT_ADD1;
if (bank == 1) {
ob.USERConfig = OB_BOOT_ADD0(0x08000000) | OB_BOOT_ADD1(0x08080000);
} else {
ob.USERConfig = OB_BOOT_ADD0(0x08080000) | OB_BOOT_ADD1(0x08000000);
}
HAL_FLASH_OBProgram(&ob);
// 触发复位使选项字节生效
HAL_NVIC_SystemReset();
}
4.3 固件写入Bank2
void Write_Firmware_To_Bank2(uint32_t srcAddr, uint32_t size) {
uint32_t dstAddr = 0x08080000;
uint32_t word;
for (uint32_t i = 0; i < size; i += 8) {
// 从外部存储(如SD卡或网络)读取数据,这里简化
word = *(uint32_t*)(srcAddr + i);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, dstAddr + i, word);
}
}
4.4 升级成功标记与回滚检测
// 在Bank2固件中,启动后执行自检
void Check_And_Confirm_Update(void) {
if (SelfTest_Pass()) {
// 写标记到备份寄存器或Flash特定区域
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0xA5A5); // 示例
// 可选:将Bank2复制到Bank1,实现永久切换
} else {
// 回滚:设置启动Bank为Bank1
Flash_SetBootBank(1);
}
}
5. 完整代码示例(简化版)
// main.c 片段
int main(void) {
HAL_Init();
SystemClock_Config();
// 检查是否首次启动新固件
if (Is_New_Firmware_First_Boot()) {
if (SelfTest_Pass()) {
Confirm_Update(); // 标记成功
} else {
Rollback_To_Old(); // 回滚
}
}
// 正常运行应用
while (1) {
// 应用逻辑
}
}
void Rollback_To_Old(void) {
Flash_Unlock();
Flash_SetBootBank(1); // 强制从Bank1启动
}
6. 注意事项
- 选项字节写入次数:Flash选项字节有擦写寿命(通常1万次),频繁切换会耗尽,建议仅在升级时操作,且避免循环切换。
- 看门狗配合:在升级过程中,务必喂狗,防止升级时间过长导致复位,但回滚时需利用看门狗强制复位。
- 双Bank地址映射:不同型号的H7,Bank大小可能不同(如H723为512KB),需查阅数据手册确认地址。
- 固件大小限制:每个Bank的固件不能超过Bank容量,否则写入会越界。
-
中断向量表重定位:Bank2固件启动后,需在
SystemInit或main开头设置SCB->VTOR为0x08080000,否则中断异常。 - RWW特性:在写入Bank2时,可以继续从Bank1执行代码,但需确保代码不在Bank2区域,否则总线冲突。
7. 总结
STM32H7的双Bank Flash机制为OTA升级提供了硬件级的安全网。通过合理设计启动切换和回滚策略,即使升级失败,设备也能自动恢复到上一个稳定版本,极大提升了系统的鲁棒性。本文给出的代码和步骤可直接应用于实际项目,但需根据具体型号和需求调整。记住:硬件保障是基础,软件逻辑是灵魂,二者结合才能打造真正可靠的嵌入式系统。