# 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 整体流程 1. **固件A(旧版)** 运行在Bank1(地址0x08000000)。 2. 下载固件B(新版)到Bank2(地址0x08080000,以1MB Bank为例)。 3. 对固件B进行CRC校验,若通过,则设置选项字节`BOOT_ADD1`指向Bank2,并复位。 4. 系统从Bank2启动,运行固件B。 5. 固件B运行后,执行自检(如外设初始化、通信握手)。若自检成功,则标记“升级成功”,并可选地将固件B复制到Bank1(或直接以Bank2为运行区)。若自检失败,则软件触发回滚:修改选项字节`BOOT_ADD0`指向Bank1,复位后回到固件A。 ### 3.2 回滚触发条件 - 升级后启动超时(如看门狗复位)。 - 应用层主动上报错误(如关键外设初始化失败)。 - 用户手动强制回滚(通过外部命令)。 ## 4. 配置步骤(基于STM32CubeMX + HAL库) ### 4.1 内存布局规划 - 在链接脚本(.ld)中,为Bank1和Bank2分别定义独立的固件起始地址和大小。例如: ```c /* Bank1: 0x08000000, 长度1MB */ /* Bank2: 0x08080000, 长度1MB */ ``` - 编译时,通过宏或不同工程生成两个固件:`firmware_A.hex`(Bank1)和`firmware_B.hex`(Bank2)。 ### 4.2 选项字节操作函数 ```c #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 ```c 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 升级成功标记与回滚检测 ```c // 在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. 完整代码示例(简化版) ```c // 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升级提供了硬件级的安全网。通过合理设计启动切换和回滚策略,即使升级失败,设备也能自动恢复到上一个稳定版本,极大提升了系统的鲁棒性。本文给出的代码和步骤可直接应用于实际项目,但需根据具体型号和需求调整。记住:硬件保障是基础,软件逻辑是灵魂,二者结合才能打造真正可靠的嵌入式系统。