# STM32H7双Bank Flash在线升级失败后自动回滚的硬件级保障机制实现 ## 1. 为什么需要硬件级回滚? 传统单Bank Flash升级流程中,新固件直接覆盖旧固件。若升级过程中断电、通信中断或固件校验失败,设备将无法启动,只能通过JTAG/SWD等外部接口恢复,这在现场环境下几乎不可行。STM32H7的双Bank Flash架构(如STM32H743/H750)将Flash分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写和启动。利用这一特性,我们可以实现: - **固件冗余**:旧固件保留在非活动Bank,新固件写入活动Bank。 - **硬件自动回滚**:若新固件启动失败(如看门狗超时),芯片可自动切换至旧Bank启动,无需软件干预。 ## 2. 原理剖析:双Bank启动与回滚机制 ### 2.1 双Bank Flash布局 STM32H7的Flash容量可达2MB,分为两个1MB的Bank。每个Bank可独立配置为启动区域。通过选项字节(Option Bytes)中的`nBOOT1`和`nBOOT0`引脚状态,或软件配置`FLASH_OPTCR`寄存器,可控制芯片从哪个Bank启动。 ### 2.2 硬件自动回滚流程 1. **升级触发**:系统运行在Bank1(旧固件),接收新固件数据。 2. **写入Bank2**:将新固件写入Bank2(非活动Bank),写入完成后置位`FLASH_CR`中的`B2SB`位(Bank2 Swap)。 3. **切换启动**:设置`FLASH_OPTCR`的`SWAP_BANK`位,并触发选项字节重载,芯片重启后将从Bank2启动。 4. **运行新固件**:新固件运行后,需在启动早期(如main函数开头)执行“启动成功”标记(如写特定标志到备份寄存器或Flash)。 5. **失败回滚**:若新固件启动失败(如硬件错误、看门狗复位),芯片在复位后检测到上次启动未成功标记,硬件自动将启动地址切换回Bank1,并清除Swap标志,实现回滚。 > **关键点**:硬件回滚依赖于“启动成功标记”的软件配合。若新固件在标记前崩溃,硬件将自动回滚;若标记后崩溃,则视为新固件已正常启动,不再回滚(避免死循环)。 ## 3. 实现步骤与代码示例 ### 3.1 硬件配置 - **MCU**:STM32H743ZI(双Bank 2MB Flash) - **开发环境**:STM32CubeIDE + HAL库 - **关键外设**:UART(用于接收固件)、IWDG(独立看门狗) ### 3.2 启动成功标记机制 在备份寄存器(RTC Backup Register)中定义两个标志:`BOOT_SUCCESS_FLAG`和`BOOT_ATTEMPTS`。 ```c #define BOOT_SUCCESS_FLAG_ADDR 0x01 // 备份寄存器索引 #define BOOT_ATTEMPTS_ADDR 0x02 #define BOOT_SUCCESS_MAGIC 0xA5A5 // 成功标记魔数 #define MAX_BOOT_ATTEMPTS 3 // 最大启动尝试次数 void Boot_MarkSuccess(void) { HAL_RTCEx_BKUPWrite(&hrtc, BOOT_SUCCESS_FLAG_ADDR, BOOT_SUCCESS_MAGIC); } void Boot_ClearSuccess(void) { HAL_RTCEx_BKUPWrite(&hrtc, BOOT_SUCCESS_FLAG_ADDR, 0); } uint8_t Boot_IsSuccess(void) { return (HAL_RTCEx_BKUPRead(&hrtc, BOOT_SUCCESS_FLAG_ADDR) == BOOT_SUCCESS_MAGIC); } void Boot_IncrementAttempts(void) { uint32_t attempts = HAL_RTCEx_BKUPRead(&hrtc, BOOT_ATTEMPTS_ADDR); HAL_RTCEx_BKUPWrite(&hrtc, BOOT_ATTEMPTS_ADDR, attempts + 1); } void Boot_ClearAttempts(void) { HAL_RTCEx_BKUPWrite(&hrtc, BOOT_ATTEMPTS_ADDR, 0); } ``` ### 3.3 固件升级与Bank切换 升级函数负责接收固件、写入非活动Bank,并设置Swap标志。 ```c void Firmware_Upgrade(uint8_t *data, uint32_t len) { // 1. 解锁Flash HAL_FLASH_Unlock(); // 2. 擦除非活动Bank(假设当前从Bank1启动,则擦除Bank2) FLASH_EraseInitTypeDef erase = {0}; erase.TypeErase = FLASH_TYPEERASE_MASS; // 注意:此处应针对Bank2,但H7的MASS擦除会擦除所有Bank,需使用BANK擦除 erase.Banks = FLASH_BANK_2; erase.NbPages = 255; // 1MB / 4KB页 uint32_t error = 0; HAL_FLASHEx_Erase(&erase, &error); // 3. 写入新固件到Bank2基地址(0x08100000) uint32_t addr = 0x08100000; for (uint32_t i = 0; i < len; i += 8) { uint64_t data64 = *(uint64_t *)(data + i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, addr, data64); addr += 8; } // 4. 设置Swap标志,使下次启动从Bank2开始 HAL_FLASH_OB_Unlock(); FLASH_OBProgramInitTypeDef ob = {0}; ob.OptionType = OPTIONBYTE_USER; ob.USERType = OB_USER_B2SB; ob.USERConfig = OB_B2SB_ENABLE; // 使能Bank2启动 HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Lock(); // 5. 触发系统复位 HAL_FLASH_Lock(); NVIC_SystemReset(); } ``` ### 3.4 启动流程中的回滚逻辑 在main函数开头,执行以下逻辑: ```c int main(void) { HAL_Init(); SystemClock_Config(); // 检查当前启动Bank uint32_t current_bank = (FLASH->OPTCR & FLASH_OPTCR_SWAP_BANK) ? 2 : 1; if (current_bank == 2) { // 从Bank2启动(新固件) if (Boot_IsSuccess()) { // 上次启动成功,本次正常,清除尝试计数 Boot_ClearAttempts(); } else { // 上次启动未成功,增加尝试次数 Boot_IncrementAttempts(); if (Boot_GetAttempts() > MAX_BOOT_ATTEMPTS) { // 超过最大尝试,回滚到Bank1 HAL_FLASH_OB_Unlock(); FLASH_OBProgramInitTypeDef ob = {0}; ob.OptionType = OPTIONBYTE_USER; ob.USERType = OB_USER_B2SB; ob.USERConfig = OB_B2SB_DISABLE; // 禁用Bank2启动,回滚 HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_OB_Lock(); NVIC_SystemReset(); } } } else { // 从Bank1启动(旧固件) // 正常启动,清除尝试计数 Boot_ClearAttempts(); } // 启动成功标记(在系统初始化完成后调用) Boot_MarkSuccess(); // 用户应用代码... } ``` ### 3.5 看门狗配合 为了检测新固件是否“卡死”,启用独立看门狗(IWDG),并在主循环中定期喂狗。若新固件崩溃,看门狗复位,从而触发回滚逻辑。 ```c void IWDG_Init(void) { // 配置IWDG,超时1秒 IWDG_HandleTypeDef hiwdg = {0}; hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = IWDG_PRESCALER_64; hiwdg.Init.Reload = 4095; // 约1秒 HAL_IWDG_Init(&hiwdg); } // 在主循环中 HAL_IWDG_Refresh(&hiwdg); ``` ## 4. 注意事项 - **Bank大小匹配**:确保新固件大小不超过Bank容量(1MB)。若固件超过,需使用外部Flash或压缩。 - **擦除操作**:H7的擦除必须按Bank进行,且擦除期间不可中断,否则可能导致Flash损坏。建议在升级前关闭中断。 - **选项字节重载**:设置Swap后,必须触发系统复位(或调用`HAL_FLASH_OB_Launch()`)才能生效。 - **启动成功标记时机**:务必在系统初始化完成、外设就绪后标记成功,避免过早标记导致回滚失效。 - **备份寄存器供电**:备份寄存器在VDD掉电后数据丢失,若需长期保存,需使用外部电池或超级电容。 - **调试注意事项**:在调试时,若使用ST-Link,可能干扰选项字节,建议在调试前备份选项字节。 ## 5. 总结 通过STM32H7的双Bank Flash和硬件Swap机制,我们实现了一个健壮的OTA升级回滚方案。该方案无需外部存储,仅依赖芯片内部资源,即可在升级失败时自动恢复至旧固件,极大提升了产品的可靠性和可维护性。开发者可根据实际需求调整尝试次数、标记时机等参数,以适应不同场景。