STM32双Bank启动切换在OTA升级中的坑与规避策略

一、双Bank启动原理与价值

STM32部分系列(如F7、H7、L4+)支持双Bank Flash架构,即Flash被划分为两个独立的存储区(Bank1和Bank2),每个Bank可独立擦写。在OTA升级中,双Bank模式允许“运行一个Bank,升级另一个Bank”,通过切换启动地址实现无缝更新,避免因升级中断导致的设备变砖。

核心机制:

  • Boot引脚或选项字节:决定从哪个Bank启动。
  • SYSCFG寄存器:控制Flash的映射关系,将目标Bank映射到地址0x08000000。
  • 原子切换:通过设置标志位并复位,系统从新Bank启动,若失败可回退到旧Bank。

二、常见陷阱与规避策略

1. 标志位误用与回滚失效

陷阱:使用普通RAM变量作为升级成功标志,复位后丢失,导致无法判断是否回滚。

规避:使用备份寄存器(如RTC BKP)或Flash专用区域存储标志,并设计三态值(如0xAA55表示新固件有效,0x0000表示无效)。

// 备份寄存器写入标志
void set_boot_flag(uint16_t val) {
    PWR_BackupAccessCmd(ENABLE);
    RTC_WriteBackupRegister(RTC_BKP_DR1, val);
}

uint16_t get_boot_flag(void) {
    return RTC_ReadBackupRegister(RTC_BKP_DR1);
}

2. 中断向量偏移错误

陷阱:新固件编译时未设置正确的VECT_TAB_OFFSET,导致中断向量表错位,系统启动后死机。

规避:在系统初始化早期,根据当前运行Bank设置向量表偏移。

void system_vect_set(void) {
    uint32_t app_addr = 0x08000000;
    if (SYSCFG->MEMRMP & SYSCFG_MEMRMP_MEM_MODE) {
        // 若从Bank2启动,偏移0x08000000+BankSize
        app_addr = FLASH_BANK2_BASE;
    }
    SCB->VTOR = app_addr;
}

3. Flash擦写时序冲突

陷阱:在升级过程中,若当前运行代码位于Bank1,而擦写Bank2时发生中断,中断服务函数可能访问Bank1的Flash,导致总线冲突(因为同一时刻只能访问一个Bank)。

规避:在擦写期间关闭所有可屏蔽中断,或确保中断代码和中断向量表位于SRAM中。

__disable_irq();
FLASH_Unlock();
FLASH_Erase(BANK2_ADDR, BANK_SIZE);
FLASH_Lock();
__enable_irq();

4. 启动切换后外设初始化遗漏

陷阱:新固件启动后,未重新初始化时钟或外设,导致通信失败。

规避:在main函数开头调用SystemInit(),并确保所有外设重新配置。

三、完整实现示例

以下代码演示双Bank升级的核心流程:下载新固件到Bank2,设置标志,切换启动。

#define BOOT_FLAG_ADDR   0x40004000  // 备份寄存器地址(示例)
#define BANK1_START      0x08000000
#define BANK2_START      0x08040000  // 假设Bank大小为256KB

void ota_upgrade(void) {
    // 1. 擦写Bank2
    FLASH_Unlock();
    __disable_irq();
    FLASH_Erase(BANK2_START, 256*1024);
    // 2. 写入新固件(从外部接口接收)
    uint32_t addr = BANK2_START;
    while (receive_data(&buf)) {
        FLASH_Program(addr, buf);
        addr += 4;
    }
    __enable_irq();
    FLASH_Lock();

    // 3. 设置启动标志(表示新固件有效)
    set_boot_flag(0xAA55);

    // 4. 切换启动Bank(通过SYSCFG映射)
    SYSCFG->MEMRMP &= ~SYSCFG_MEMRMP_MEM_MODE;
    SYSCFG->MEMRMP |= SYSCFG_MEMRMP_MEM_MODE; // 选择Bank2
    NVIC_SystemReset();
}

// 在main函数中检查标志
int main(void) {
    HAL_Init();
    system_vect_set(); // 设置向量表偏移
    if (get_boot_flag() != 0xAA55) {
        // 新固件无效,回滚到Bank1
        SYSCFG->MEMRMP &= ~SYSCFG_MEMRMP_MEM_MODE;
        NVIC_SystemReset();
    }
    // 正常初始化外设...
}

四、注意事项

  • Bank大小确认:不同型号的Bank大小不同,需查阅数据手册,避免越界。
  • 固件校验:在切换前对写入的固件进行CRC或SHA校验,防止写入损坏数据。
  • 看门狗:在升级过程中喂狗,防止复位导致升级中断。
  • 测试覆盖:务必在真实硬件上测试掉电、强制复位等异常场景,确保回滚机制可靠。

五、总结

双Bank启动切换是OTA升级的利器,但细节决定成败。通过合理管理标志位、正确设置向量表、规避Flash时序冲突,并辅以完善的错误处理,可以显著提升升级的可靠性。希望本文的实战经验能帮助你在嵌入式开发中少走弯路。