# STM32H7 双 Bank 模式下的 OTA 无缝回滚实战设计 ## 1. 为什么需要双 Bank 与擦写中断? 在嵌入式 OTA 升级中,最怕的是升级过程中断电或写入错误,导致设备变砖。STM32H7 的双 Bank Flash 架构(如 H743/H750)将 Flash 分为两个独立的 Bank(Bank1 和 Bank2),每个 Bank 可独立擦写。配合 Flash 擦写中断(EOP 表示操作完成,WRPERR 表示写保护错误),我们可以实现: - **无缝切换**:升级完成后,通过切换 Boot 地址立即运行新固件,无需重启。 - **安全回滚**:若新固件校验失败,自动回退到旧 Bank,保证设备可用。 - **实时监控**:利用中断感知擦写状态,避免轮询阻塞,提升系统响应。 ## 2. 双 Bank 模式原理 STM32H7 的 Flash 控制器(FLASH)支持两种 Bank 模式: - **Single Bank**:整个 Flash 作为一个整体,地址连续。 - **Dual Bank**:Flash 分为两个 1MB(或 512KB,取决于型号)的 Bank,每个 Bank 有独立的擦写单元。 在双 Bank 模式下,通过选项字节(Option Bytes)中的 `DBANK` 位配置。当 `DBANK=1` 时,启用双 Bank。此时,Bank1 的起始地址为 `0x08000000`,Bank2 为 `0x08100000`(以 2MB Flash 为例)。 **关键点**: - 系统始终从 `0x08000000` 启动(除非配置了 Bank 切换)。 - 通过设置 `FLASH_OPTCR` 寄存器的 `SWAP_BANK` 位,可以交换 Bank1 和 Bank2 的映射,使得系统从另一个 Bank 启动。 ## 3. Flash 擦写中断机制 STM32H7 的 Flash 操作(编程/擦除)是异步的,完成或出错时会产生中断。相关中断标志位: - `EOP`(End of Operation):操作成功完成。 - `WRPERR`(Write Protection Error):写入保护区域。 - `PGAERR`(Programming Alignment Error):编程对齐错误。 - `SIZERR`(Size Error):大小错误。 在中断服务函数(ISR)中,我们可以检查这些标志,并清除中断,从而避免轮询等待,提高 CPU 利用率。 ## 4. 系统架构设计 我们的 OTA 方案分为三个部分: 1. **Bootloader**:位于 Bank1 起始区域,负责固件接收、校验和跳转。 2. **App1**:当前运行固件,位于 Bank1 后半部分。 3. **App2**:新固件下载区,位于 Bank2。 升级流程: - App1 接收新固件写入 Bank2。 - 写入完成后,校验 CRC。 - 若成功,设置 `SWAP_BANK` 位,并软复位,Bootloader 从 Bank2 启动新固件。 - 若新固件运行失败(如看门狗复位),Bootloader 检测到标志,清除 `SWAP_BANK`,回滚到 Bank1。 ## 5. 关键代码实现 ### 5.1 配置 Flash 双 Bank 模式 在系统初始化时,检查并配置双 Bank 模式(通常由 Bootloader 完成): ```c void Flash_EnableDualBank(void) { FLASH_OBProgramInitTypeDef obInit; HAL_FLASHEx_OBGetConfig(&obInit); if (obInit.DualBank == 0) { obInit.DualBank = 1; HAL_FLASHEx_OBProgram(&obInit); // 需要复位生效 HAL_FLASH_OB_Launch(); } } ``` ### 5.2 Flash 擦写中断配置 使能 Flash 中断,并编写中断服务函数: ```c void Flash_EnableInterrupt(void) { __HAL_FLASH_ENABLE_IT(FLASH_IT_EOP | FLASH_IT_WRPERR | FLASH_IT_PGAERR); HAL_NVIC_EnableIRQ(FLASH_IRQn); } void FLASH_IRQHandler(void) { uint32_t flags = __HAL_FLASH_GET_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR); if (flags & FLASH_FLAG_EOP) { // 操作完成,可以继续下一步 __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); // 设置事件标志,通知主循环 flash_op_done = 1; } if (flags & FLASH_FLAG_WRPERR) { // 写保护错误,处理错误 __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR); flash_error = FLASH_ERROR_WRPERR; } if (flags & FLASH_FLAG_PGAERR) { __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGAERR); flash_error = FLASH_ERROR_PGAERR; } } ``` ### 5.3 固件写入 Bank2(带中断等待) ```c uint8_t Flash_WriteBank2(uint32_t destAddr, uint8_t *data, uint32_t size) { uint32_t addr = destAddr; uint32_t remaining = size; // 解锁 Flash HAL_FLASH_Unlock(); // 擦除 Bank2(假设 Bank2 起始地址为 0x08100000) FLASH_EraseInitTypeDef eraseInit; eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS; eraseInit.Banks = FLASH_BANK_2; eraseInit.Sector = 0; eraseInit.NbSectors = 8; // 根据实际扇区数调整 eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t sectorError = 0; HAL_FLASHEx_Erase(&eraseInit, §orError); // 等待擦除完成(中断方式) while (!flash_op_done && !flash_error) {} if (flash_error) { HAL_FLASH_Lock(); return 1; } flash_op_done = 0; // 编程数据 while (remaining > 0) { uint32_t word = 0; // 组装 32 位数据(注意对齐) memcpy(&word, data, 4); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, word); while (!flash_op_done && !flash_error) {} if (flash_error) { HAL_FLASH_Lock(); return 1; } flash_op_done = 0; addr += 4; data += 4; remaining -= 4; } HAL_FLASH_Lock(); return 0; } ``` ### 5.4 Bank 切换与回滚 ```c void JumpToBank2(void) { // 设置 SWAP_BANK 位 HAL_FLASH_OB_Unlock(); __HAL_FLASH_SWAP_BANK(); HAL_FLASH_OB_Lock(); // 软复位 NVIC_SystemReset(); } void RollbackToBank1(void) { // 清除 SWAP_BANK 位 HAL_FLASH_OB_Unlock(); __HAL_FLASH_CLEAR_SWAP_BANK(); HAL_FLASH_OB_Lock(); // 复位后从 Bank1 启动 NVIC_SystemReset(); } ``` ### 5.5 Bootloader 中的固件校验与回滚逻辑 ```c void Bootloader_CheckAndRun(void) { // 检查回滚标志(存储在备份寄存器或 Flash 特定区域) if (CheckRollbackFlag()) { RollbackToBank1(); } // 检查 Bank2 固件有效性(如 CRC) if (IsFirmwareValid(0x08100000)) { JumpToBank2(); } else { // 运行 Bank1 固件 JumpToBank1(); } } ``` ## 6. 注意事项 - **中断优先级**:Flash 中断优先级应设置较高,避免在擦写过程中被其他中断打断导致超时。 - **擦写时间**:STM32H7 擦除一个扇区大约需要 1-2 秒,期间系统不能进入低功耗模式,否则可能失败。 - **数据对齐**:Flash 编程必须按 32 位字对齐,否则产生 PGAERR。 - **回滚标志**:建议使用备份寄存器(如 RTC 备份域)存储回滚标志,避免占用 Flash 空间。 - **看门狗**:在擦写过程中喂狗,防止升级过程中复位。 ## 7. 总结 通过利用 STM32H7 的双 Bank 架构和 Flash 擦写中断,我们实现了一个高效、安全的 OTA 升级方案。中断机制避免了轮询等待,提升了系统响应;双 Bank 切换保证了无缝升级和快速回滚。该设计适用于对可靠性要求极高的工业、汽车等嵌入式应用。