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 总体流程
- 接收新固件:通过通信接口(如 Wi-Fi、CAN)接收固件数据,暂存于外部存储或 RAM 缓冲区。
- 写入非活动 Bank:当前运行 Bank 为 Bank1,则将新固件写入 Bank2。每次写入后等待操作完成中断,并检查 ECC 错误。
- 校验固件:写入完成后,对整个 Bank2 进行 CRC 或 SHA 校验。
- 切换启动 Bank:设置选项字节(如 BFB2 位)使下次复位从 Bank2 启动。
- 运行新固件:复位后,新固件运行。若在启动阶段检测到异常(如看门狗超时),则回滚到旧 Bank。
3.2 中断驱动的时序设计
传统轮询方式会阻塞 CPU,影响实时任务。利用中断,我们可以将擦写操作交给后台,主程序继续响应实时事件。具体时序如下:
- 启动擦写:调用 HAL_FLASH_Program() 或 HAL_FLASH_Erase(),并启用相关中断。
- 中断处理:在中断回调中设置标志位,通知主循环操作完成。
- 错误处理:若发生 ECC 错误或操作错误,立即停止升级,并触发回滚流程。
四、代码实现
以下基于 STM32H7 HAL 库,演示如何配置 Flash 中断并实现带回滚的 OTA 写入。
4.1 初始化 Flash 中断
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 中断回调处理
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 并等待中断
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。
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 切换和错误检测实现了无缝回滚。本文提供的代码框架可直接应用于实际项目,开发者可根据具体需求调整中断优先级和超时参数。这种设计显著提升了系统的可靠性和用户体验。