STM32双Bank切换实现OTA无感升级的坑与规避
为什么需要双Bank?
传统OTA升级通常采用单Bank方案:Bootloader接收固件,写入App区,然后跳转。但这种方式存在致命缺陷——升级过程中断电或写入错误,设备将无法启动,只能依赖外部恢复。
双Bank方案将Flash划分为两个独立的Bank(如Bank0和Bank1),App运行在其中一个Bank,升级时写入另一个Bank,完成后通过切换启动地址实现无缝切换。这样即使升级失败,旧固件仍可运行,实现真正的无感升级。
STM32双Bank的硬件支持
并非所有STM32都支持双Bank。以STM32F7、H7系列为例,其Flash支持双Bank模式,每个Bank可独立擦写。关键寄存器为FLASH_OPTCR中的DBANK位,用于配置双Bank模式。
注意:双Bank模式必须在Flash空载时配置,且配置后Flash地址映射会改变。例如,STM32H743在双Bank模式下,Bank0地址为0x08000000~0x080FFFFF,Bank1地址为0x08100000~0x081FFFFF(若总容量2MB)。
核心原理:启动地址切换
STM32的启动地址由BOOT0引脚和FLASH_OPTCR的BOR_LEV等决定,但双Bank切换通常通过修改FLASH_BOOT位(在FLASH_OPTCR中)实现。当FLASH_BOOT=0时,从Bank0启动;FLASH_BOOT=1时,从Bank1启动。
关键点:切换后,CPU的PC指针会跳转到新Bank的起始地址,但中断向量表(VTOR)仍指向旧Bank,必须手动重映射。
配置步骤
-
使能双Bank模式:在系统初始化时,设置
FLASH_OPTCR的DBANK=1,并执行FLASH_OPTCR_LOCK解锁。 - 规划内存布局:将Bank0和Bank1分别作为App区,Bootloader可放在独立区域(如系统Flash)。
-
编译链接脚本:为每个Bank生成独立的固件,链接脚本中设置
FLASH起始地址为对应Bank地址。 -
实现跳转函数:在Bootloader或App中,通过修改
FLASH_BOOT位并软复位实现切换。
完整代码示例
以下代码演示了从App(Bank0)切换到Bank1的流程:
#include "stm32h7xx_hal.h"
#define BANK0_START_ADDR 0x08000000
#define BANK1_START_ADDR 0x08100000
void JumpToBank1(void) {
// 1. 关闭全局中断
__disable_irq();
// 2. 设置FLASH_BOOT位为1,使能从Bank1启动
FLASH_OBProgramInitTypeDef pOBInit;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
pOBInit.OptionType = OPTIONBYTE_USER;
pOBInit.UserType = OB_USER_BOOT_ADD0;
pOBInit.BOOT_ADD0 = BANK1_START_ADDR; // 设置启动地址为Bank1
HAL_FLASH_OB_Program(&pOBInit);
// 3. 触发系统复位
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
NVIC_SystemReset();
}
// 在App中调用此函数,即可切换到Bank1
注意:跳转前必须确保新Bank的固件已完整写入并校验通过。
常见坑与规避
坑1:中断向量表未重映射
现象:跳转后程序跑飞或死机。
原因:CPU复位后,VTOR仍指向旧Bank的向量表,导致中断处理错误。
规避:在App的启动代码中,立即设置VTOR为新Bank的地址。例如,在SystemInit函数中添加:
#define APP_ADDR 0x08100000
SCB->VTOR = APP_ADDR;
坑2:Flash擦写期间中断干扰
现象:升级过程中,中断触发导致Flash操作失败。
原因:Flash擦写时,若CPU执行中断服务程序,可能访问Flash造成冲突。
规避:在擦写Flash期间,关闭所有可屏蔽中断,或使用HAL_FLASH_Program时确保中断优先级低于Flash操作。
坑3:双Bank模式配置后地址映射错误
现象:代码无法运行,调试器连接不上。
原因:配置DBANK=1后,Flash地址重新映射,但链接脚本未更新。
规避:在配置双Bank前,务必确认链接脚本中的FLASH起始地址与Bank地址一致。若使用STM32CubeIDE,可在*.ld文件中修改。
坑4:跳转后外设状态残留
现象:新固件运行异常,如串口乱码。
原因:跳转前未复位外设,导致新固件初始化时冲突。
规避:在跳转前,调用HAL_RCC_DeInit()复位时钟,并重新初始化必要外设。
坑5:升级失败后无回退机制
现象:新固件损坏,设备无法启动。
原因:未实现回退逻辑。
规避:在Bootloader中增加启动计数,若新固件连续启动失败(如看门狗复位),则自动回退到旧Bank。
总结
双Bank切换是OTA无感升级的基石,但细节决定成败。掌握Flash地址映射、VTOR重映射、中断管理和回退机制,才能避开常见陷阱。建议在实际项目中,先在开发板上验证完整流程,再部署到生产环境。