一、双Bank Flash架构与回滚原理
STM32F4系列(如STM32F407/427)的Flash被划分为两个独立的Bank(Bank1和Bank2),每个Bank容量相等(通常为1MB时各512KB)。硬件支持从任一Bank启动,且可通过SYSCFG_MEMRMP寄存器动态切换启动Bank。这一特性为OTA提供了天然的双区备份能力:
- Bank1(App A):当前运行的应用固件。
- Bank2(App B):新固件下载区或备份区。
回滚核心思想:升级时先写入非活动Bank,校验通过后切换启动地址;若新固件启动失败或运行异常,Bootloader检测到标志位后自动回退到旧Bank。整个过程无需外部存储,且Flash擦写操作不影响当前运行固件。
二、启动流程总体设计
系统包含三个角色:Bootloader(固定于Bank0起始区)、App A(Bank1)、App B(Bank2)。启动流程如下:
- 上电复位:Bootloader首先执行。
- 检查升级标志:读取备份寄存器或Flash特定地址的标志位。
- 校验App有效性:对目标Bank的固件进行CRC或Magic Number校验。
-
选择启动Bank:
- 若升级标志有效且新固件校验通过 → 跳转至新Bank。
- 若新固件校验失败或运行超时 → 清除标志,回滚至旧Bank。
- 运行App:App启动后,若需升级,通过Bootloader提供的接口写入另一Bank。
关键设计点:
- 使用备份寄存器(如
RTC->BKPxR)存储状态,掉电不丢失。 - 每个App固件头部包含版本号、CRC、长度等元数据。
- 设置看门狗(IWDG)防止App死循环导致无法回滚。
三、配置步骤
1. 内存分区规划
以STM32F407ZG(1MB Flash)为例:
- Bootloader:0x08000000 ~ 0x0800FFFF(64KB)
- App A(Bank1):0x08010000 ~ 0x0807FFFF(448KB)
- App B(Bank2):0x08080000 ~ 0x080FFFFF(512KB)
注意:Bootloader必须位于Bank1起始地址,且App A和B需分别链接到对应地址。
2. 使能双Bank模式
在系统初始化时,通过选项字节或代码启用双Bank操作(部分型号默认开启):
// 启用双Bank模式(以STM32F4为例)
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
if ((ob.USERConfig & FLASH_OPTION_BANK_SWAP) == 0) {
ob.USERConfig |= FLASH_OPTION_BANK_SWAP;
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch(); // 重启生效
}
3. Bootloader跳转逻辑
// 跳转至指定Bank的App
void jump_to_app(uint32_t app_addr) {
uint32_t msp = *(volatile uint32_t*)app_addr;
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
// 关闭全局中断,设置MSP,跳转
__disable_irq();
HAL_RCC_DeInit();
SysTick->CTRL = 0;
SCB->VTOR = app_addr;
__set_MSP(msp);
app_reset();
}
4. 升级与回滚标志管理
使用备份寄存器(如BKP1)存储状态:
#define APP_A_VALID 0xA5A5
#define APP_B_VALID 0x5A5A
#define ROLLBACK_FLAG 0xDEAD
void set_boot_flag(uint32_t flag) {
HAL_PWR_EnableBkUpAccess();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, flag);
}
uint32_t get_boot_flag(void) {
HAL_PWR_EnableBkUpAccess();
return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
}
5. 完整Bootloader主流程示例
int main(void) {
HAL_Init();
SystemClock_Config();
uint32_t flag = get_boot_flag();
uint32_t app_addr;
// 默认启动App A
if (flag == APP_B_VALID) {
// 检查App B的CRC(伪代码,需实现CRC校验函数)
if (check_crc(APP_B_ADDR) == PASS) {
app_addr = APP_B_ADDR;
} else {
set_boot_flag(ROLLBACK_FLAG);
app_addr = APP_A_ADDR;
}
} else if (flag == ROLLBACK_FLAG) {
// 回滚:清除标志,启动App A
set_boot_flag(APP_A_VALID);
app_addr = APP_A_ADDR;
} else {
app_addr = APP_A_ADDR;
}
// 启动看门狗,超时自动复位
MX_IWDG_Init();
jump_to_app(app_addr);
while(1); // 若跳转失败,停留此处
}
6. App端升级与回滚配合
App运行时,若收到新固件,写入非活动Bank,然后设置标志并复位:
// 假设当前运行在Bank1,升级Bank2
void ota_update(uint8_t* data, uint32_t len) {
// 擦写Bank2,写入固件
flash_erase_bank(APP_B_ADDR);
flash_write(APP_B_ADDR, data, len);
// 设置启动标志为App B
set_boot_flag(APP_B_VALID);
NVIC_SystemReset();
}
四、注意事项
- CRC校验:必须使用硬件CRC或软件CRC,确保固件完整性。建议在固件头部存放CRC值,Bootloader计算整个固件区域。
- 看门狗:Bootloader和App都应开启独立看门狗(IWDG),防止App卡死导致无法回滚。
-
中断向量表:App中必须重定位中断向量表(
SCB->VTOR = APP_ADDR),否则中断异常。 - Flash擦写保护:确保Bootloader区域不被App误擦除,可通过选项字节设置读保护(RDP)或写保护(WRP)。
- 升级失败处理:若升级过程中断电,标志位可能未更新,Bootloader应能识别无效固件并回滚。建议在写入前先备份旧固件(若空间允许)。
-
双Bank切换:某些型号需通过
SYSCFG_MEMRMP切换启动地址,但跳转函数中直接设置VTOR即可,无需硬件切换。 - 测试:务必在真机上模拟断电、擦写中断等场景,验证回滚可靠性。
五、总结
利用STM32F4双Bank Flash,配合Bootloader的智能判断,可以低成本实现高可靠的OTA升级。本文提供的启动流程和代码框架可直接应用于实际项目,只需根据具体型号调整Flash地址和CRC算法。记住:回滚机制的核心是“状态明确、校验严格、超时兜底”,三者缺一不可。