# STM32H7双Bank Flash在线升级失败后自动回滚的启动流程设计 ## 一、为什么需要双Bank Flash? 在传统的单Bank Flash方案中,升级过程中一旦发生断电、写入错误或校验失败,设备将因固件损坏而无法启动,即“变砖”。STM32H7系列(如H743、H750)提供双Bank Flash,将Flash划分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写。通过硬件支持的Bank切换机制,系统可以在两个固件镜像之间无缝切换,从而在升级失败时自动回滚到上一个稳定版本。 ## 二、双Bank启动原理 ### 2.1 Flash布局 STM32H7的Flash通常为1MB或2MB,分为两个Bank,每个Bank大小相等。例如,H743的1MB Flash分为Bank1(0x08000000-0x0807FFFF)和Bank2(0x08080000-0x080FFFFF)。每个Bank可独立存放一个完整的固件镜像。 ### 2.2 硬件交换机制 STM32H7的Flash控制器(FLASH)提供`FLASH_CR`寄存器中的`BKSWAP`位。当该位被置1时,硬件会将Bank1和Bank2的地址映射互换。这意味着: - 物理Bank1的地址变为0x08080000,物理Bank2的地址变为0x08000000。 - CPU从0x08000000启动时,实际执行的是物理Bank2中的代码。 这一机制使得启动时无需复制代码,只需切换映射即可选择运行哪个Bank的固件。 ### 2.3 启动流程设计 设计目标: - 正常启动:从当前活动Bank启动。 - 升级过程:将新固件写入非活动Bank,校验成功后切换活动Bank。 - 升级失败:启动时检测到固件无效,自动切换到另一个Bank(回滚)。 具体流程如下: 1. **上电启动**:CPU从0x08000000读取向量表,执行启动代码(Bootloader)。 2. **检查标志**:Bootloader读取备份寄存器(如RTC备份寄存器)或Flash特定区域,获取当前活动Bank信息及升级状态。 3. **验证固件**:对活动Bank中的固件进行CRC或SHA校验。若校验通过,跳转到该Bank的应用程序;若失败,则切换Bank并再次校验。 4. **升级流程**:应用程序收到新固件后,将其写入非活动Bank,写入完成后设置“待切换”标志,并触发软复位。 5. **切换确认**:Bootloader检测到“待切换”标志,执行Bank切换(置位BKSWAP),并清除标志。若新固件启动后自检失败,可再次切换回旧Bank。 ## 三、配置步骤 ### 3.1 内存布局配置 在STM32CubeIDE中,为Bootloader和两个应用程序分别设置链接脚本。Bootloader占用Flash起始区域(如0x08000000,大小32KB),应用程序A位于Bank1(0x08008000),应用程序B位于Bank2(0x08088000)。注意:当BKSWAP置位后,Bank2的物理地址映射到0x08000000,因此应用程序B的链接地址应设为0x08000000(但实际物理地址是0x08080000)。 ### 3.2 使能双Bank模式 STM32H7的Flash默认是单Bank模式,需通过选项字节或运行时配置切换为双Bank。在系统初始化时,调用以下代码: ```c void Flash_EnableDualBank(void) { FLASH_OBProgramInitTypeDef ob; HAL_FLASHEx_OBGetConfig(&ob); if (ob.Banks != FLASH_BANK_BOTH) { ob.Banks = FLASH_BANK_BOTH; HAL_FLASH_Unlock(); HAL_FLASHEx_OBProgram(&ob); HAL_FLASH_Lock(); // 需要复位才能生效 NVIC_SystemReset(); } } ``` ### 3.3 Bootloader启动代码 Bootloader的核心逻辑如下: ```c #define APP_A_ADDR 0x08008000 // Bank1 应用程序A #define APP_B_ADDR 0x08088000 // Bank2 应用程序B(物理地址) #define BACKUP_REG RTC->BKP0R // 使用备份寄存器存储状态 void JumpToApp(uint32_t app_addr) { uint32_t msp_val = *(volatile uint32_t*)app_addr; if ((msp_val & 0xFFF00000) != 0x20000000) return; // 栈顶地址无效 void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4)); __set_MSP(msp_val); app_reset(); } int main(void) { HAL_Init(); // 读取状态标志 uint32_t state = BACKUP_REG; uint32_t active_bank = (state & 0x1) ? 2 : 1; // 1表示Bank1,2表示Bank2 uint32_t app_addr = (active_bank == 1) ? APP_A_ADDR : APP_B_ADDR; // 校验当前Bank固件 if (CRC_Check(app_addr) == OK) { JumpToApp(app_addr); } else { // 校验失败,切换Bank并尝试 if (active_bank == 1) { // 切换到Bank2 FLASH->CR |= FLASH_CR_BKSWAP; app_addr = APP_B_ADDR; } else { FLASH->CR &= ~FLASH_CR_BKSWAP; app_addr = APP_A_ADDR; } if (CRC_Check(app_addr) == OK) { BACKUP_REG = (active_bank == 1) ? 0 : 1; // 更新状态 JumpToApp(app_addr); } else { // 双Bank均失败,进入错误处理 Error_Handler(); } } } ``` ### 3.4 应用程序升级代码 在应用程序中,接收新固件并写入非活动Bank: ```c void OTA_Update(uint8_t *data, uint32_t len) { uint32_t target_bank = (GetActiveBank() == 1) ? 2 : 1; uint32_t target_addr = (target_bank == 1) ? APP_A_ADDR : APP_B_ADDR; // 擦除目标Bank FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = (target_bank == 1) ? FLASH_BANK_1 : FLASH_BANK_2; erase.Sectors = 0; erase.NbSectors = 8; erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; HAL_FLASHEx_Erase(&erase, &error); // 写入数据 HAL_FLASH_Unlock(); for (uint32_t i = 0; i < len; i += 8) { uint64_t val = *(uint64_t*)(data + i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, target_addr + i, val); } HAL_FLASH_Lock(); // 设置切换标志并复位 BACKUP_REG = (target_bank == 1) ? 0 : 1; NVIC_SystemReset(); } ``` ## 四、注意事项 - **向量表重定位**:应用程序编译时需将向量表偏移到对应Bank的起始地址(通过`VECT_TAB_OFFSET`宏),并在启动时调用`SCB->VTOR = APP_ADDR`。 - **CRC校验**:建议使用硬件CRC单元或软件CRC32,在固件末尾附加校验值,Bootloader在跳转前校验。 - **备份寄存器初始化**:确保RTC备份域已使能,且备份寄存器在复位后不丢失(需VBAT供电)。 - **Bank切换时序**:BKSWAP操作必须在无Flash访问时进行,建议在复位后立即执行,避免中断干扰。 - **调试注意**:使用ST-Link调试时,若切换了Bank,调试器可能无法识别,需手动复位或使用`ST-LINK Utility`设置。 - **双Bank容量**:确保每个Bank足够容纳固件,否则需压缩或分块传输。 ## 五、总结 通过STM32H7的双Bank Flash和硬件交换机制,我们可以实现高效的OTA升级回滚方案。Bootloader负责校验和切换,应用程序负责接收和写入新固件,整个过程无需外部存储,且回滚速度极快。该设计显著提升了设备的可靠性和用户体验,是工业级产品的理想选择。