# STM32H7 双 Bank 模式下利用 Flash 擦写中断实现 OTA 无缝回滚的时序设计 ## 一、引言 在工业控制、汽车电子等对可靠性要求极高的场景中,OTA(Over-The-Air)升级失败可能导致设备变砖。STM32H7 系列凭借其双 Bank Flash 架构,支持在运行一个 Bank 的同时擦写另一个 Bank,配合中断机制可实现无缝回滚。本文将重点讲解如何利用 Flash 擦写中断(如 ECC 错误中断、操作完成中断)构建时序,确保升级过程安全、可回滚,且不阻塞主程序运行。 ## 二、STM32H7 双 Bank 架构与中断机制 ### 2.1 双 Bank 架构 STM32H7 的 Flash 分为两个 Bank(Bank1 和 Bank2),每个 Bank 可独立擦写。当系统从 Bank1 启动时,可对 Bank2 进行编程,反之亦然。这种设计天然支持 A/B 分区升级:当前运行固件位于 Bank A,新固件写入 Bank B,校验成功后切换启动地址。 ### 2.2 Flash 擦写中断 STM32H7 的 Flash 控制器支持多种中断,关键包括: - **操作完成中断(End of Operation)**:当编程或擦除操作成功完成时触发,可用于确认操作结束。 - **ECC 错误中断**:当 Flash 读取时发生 ECC 错误(单比特或双比特)触发,可提前发现数据损坏。 - **错误中断(Error)**:如编程/擦除操作非法或超时。 利用这些中断,我们可以在不轮询状态寄存器的情况下异步获知操作结果,从而设计高效的时序。 ## 三、OTA 升级与回滚时序设计 ### 3.1 总体流程 1. **接收新固件**:通过通信接口(如 Wi-Fi、CAN)接收固件数据,暂存于外部存储或 RAM 缓冲区。 2. **写入非活动 Bank**:当前运行 Bank 为 Bank1,则将新固件写入 Bank2。每次写入后等待操作完成中断,并检查 ECC 错误。 3. **校验固件**:写入完成后,对整个 Bank2 进行 CRC 或 SHA 校验。 4. **切换启动 Bank**:设置选项字节(如 BFB2 位)使下次复位从 Bank2 启动。 5. **运行新固件**:复位后,新固件运行。若在启动阶段检测到异常(如看门狗超时),则回滚到旧 Bank。 ### 3.2 中断驱动的时序设计 传统轮询方式会阻塞 CPU,影响实时任务。利用中断,我们可以将擦写操作交给后台,主程序继续响应实时事件。具体时序如下: - **启动擦写**:调用 HAL_FLASH_Program() 或 HAL_FLASH_Erase(),并启用相关中断。 - **中断处理**:在中断回调中设置标志位,通知主循环操作完成。 - **错误处理**:若发生 ECC 错误或操作错误,立即停止升级,并触发回滚流程。 ## 四、代码实现 以下基于 STM32H7 HAL 库,演示如何配置 Flash 中断并实现带回滚的 OTA 写入。 ### 4.1 初始化 Flash 中断 ```c void Flash_Interrupt_Init(void) { // 使能 Flash 全局中断 HAL_NVIC_SetPriority(FLASH_IRQn, 5, 0); HAL_NVIC_EnableIRQ(FLASH_IRQn); // 使能操作完成中断和错误中断 __HAL_FLASH_ENABLE_IT(FLASH_IT_EOP | FLASH_IT_ERR); // 注意:ECC 中断默认使能,但可通过 FLASH_ECCR 寄存器配置 } // 中断服务函数 void FLASH_IRQHandler(void) { HAL_FLASH_IRQHandler(); } ``` ### 4.2 中断回调处理 ```c volatile uint8_t flash_op_done = 0; volatile uint8_t flash_error = 0; void HAL_FLASH_OperationComplete_Callback(void) { flash_op_done = 1; } void HAL_FLASH_Error_Callback(uint32_t error) { flash_error = error; // 可在此触发回滚 } ``` ### 4.3 写入 Bank2 并等待中断 ```c uint8_t Flash_Write_Bank2(uint32_t address, uint64_t *data, uint32_t length) { uint32_t i; for (i = 0; i < length; i += 8) { flash_op_done = 0; flash_error = 0; if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address + i, data[i/8]) != HAL_OK) { return 1; } // 等待操作完成中断(超时保护) uint32_t timeout = 100000; while (!flash_op_done && !flash_error && timeout--); if (flash_error || timeout == 0) { return 1; } } return 0; } ``` ### 4.4 回滚机制 在系统启动时,检查当前运行 Bank 的启动标志。若新固件运行失败(如看门狗复位),则切换回旧 Bank。 ```c void Check_And_Rollback(void) { if (System_Is_New_Firmware() && Watchdog_Timeout_Occurred()) { // 切换回 Bank1 HAL_FLASH_OB_Unlock(); // 设置 BFB2 位为 0,从 Bank1 启动 HAL_FLASHEx_OBProgram(&config); HAL_FLASH_OB_Lock(); NVIC_SystemReset(); } } ``` ## 五、注意事项 - **中断优先级**:Flash 中断优先级应低于实时性要求高的中断(如定时器),但高于普通任务,避免长时间阻塞。 - **超时处理**:即使使用中断,也需设置超时机制,防止硬件故障导致死等。 - **ECC 错误**:在写入后读取校验时,务必检查 ECC 状态,若出现双比特错误,应立即回滚。 - **选项字节编程**:切换启动 Bank 需解锁选项字节,操作后重新上锁,并注意编程电压要求。 - **双 Bank 映射**:确保在写入 Bank2 时,Bank1 的代码运行不受影响,注意 Flash 访问冲突(如同时读取和写入不同 Bank 时无冲突,但同 Bank 操作会暂停)。 ## 六、总结 通过利用 STM32H7 的 Flash 擦写中断,我们设计了一套高效的 OTA 升级时序,既保证了升级过程的实时性,又通过双 Bank 切换和错误检测实现了无缝回滚。本文提供的代码框架可直接应用于实际项目,开发者可根据具体需求调整中断优先级和超时参数。这种设计显著提升了系统的可靠性和用户体验。