STM32F4双Bank闪存并行读写:在线升级不掉电的Bank切换实战

一、为什么需要双Bank闪存?

传统单Bank闪存升级流程:

  1. 擦除整个应用区 → 2. 写入新固件 → 3. 跳转重启。

此过程存在致命缺陷:升级期间设备完全停机,且一旦写入失败(如断电),设备变砖。

双Bank架构将闪存划分为两个独立的存储区(Bank1和Bank2),支持并行编程:当CPU从Bank1执行代码时,可以同时对Bank2进行擦写操作。升级完成后,通过修改标志位并复位,Bootloader即可跳转到新Bank运行。整个过程无需断电,且旧固件始终保留,极大提升了可靠性。

二、STM32F4双Bank硬件原理

以STM32F407为例,其内置1MB闪存,默认配置为单Bank(Bank1=512KB,Bank2=512KB,但合并为连续地址0x08000000-0x080FFFFF)。启用双Bank模式后,地址映射发生改变:

  • Bank1:0x08000000 - 0x0807FFFF(512KB)
  • Bank2:0x08080000 - 0x080FFFFF(512KB)

关键寄存器:

  • FLASH_OPTCR:选项字节控制寄存器,其中DBANK位(bit22)用于切换单/双Bank模式。
  • FLASH_CR:闪存控制寄存器,PG(编程)、PER(页擦除)、MER(全擦除)等位。

注意:切换Bank模式需要修改选项字节,并触发系统复位才能生效。

三、双Bank并行读写的核心优势

  • 无缝升级:运行在Bank1时,擦写Bank2,完成后复位跳转,中断时间仅为一个复位周期(毫秒级)。
  • 回滚保护:新固件异常时,Bootloader可回退到旧Bank。
  • 双固件冗余:适合A/B分区升级策略。

四、实战配置步骤

1. 启用双Bank模式

通过修改选项字节使能双Bank。可使用STM32CubeProgrammer或代码方式。以下为代码方式:

void Flash_Enable_DualBank(void) {
    FLASH_OB_Unlock();
    // 设置DBANK位为1(双Bank模式)
    FLASH_OB_Program(OB_DBANK_MODE, 1);
    FLASH_OB_Launch(); // 触发选项字节重载
    FLASH_OB_Lock();
    NVIC_SystemReset(); // 必须复位生效
}

2. 链接脚本调整

将固件分为两个区域,例如:

MEMORY
{
  BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 512K
  BANK2 (rx) : ORIGIN = 0x08080000, LENGTH = 512K
}

3. 并行读写实现

在Bank1运行时,对Bank2进行擦写。核心代码:

// 擦除Bank2的指定页
void Flash_Erase_Bank2_Page(uint32_t page_addr) {
    FLASH_Unlock();
    while (FLASH_GetStatus() == BUSY);
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
    FLASH_ErasePage(page_addr); // 页地址需在Bank2范围内
    FLASH_Lock();
}

// 写入数据到Bank2
void Flash_Write_Bank2(uint32_t addr, uint32_t *data, uint32_t len) {
    FLASH_Unlock();
    for (uint32_t i = 0; i < len; i++) {
        while (FLASH_GetStatus() == BUSY);
        FLASH_ProgramWord(addr + i*4, data[i]);
    }
    FLASH_Lock();
}

4. 升级流程与跳转

升级完成后,写入升级标志(如备份在备份寄存器),然后复位。Bootloader根据标志决定跳转地址:

void Jump_To_App(uint32_t app_addr) {
    // 检查栈顶地址合法性
    if (((*(volatile uint32_t*)app_addr) & 0x2FFE0000) == 0x20000000) {
        uint32_t jump_addr = *(volatile uint32_t*)(app_addr + 4);
        pFunction jump = (pFunction)jump_addr;
        __set_MSP(*(volatile uint32_t*)app_addr);
        jump();
    }
}

五、完整示例:在线升级Demo

假设Bootloader位于Bank1,新固件下载到Bank2。以下为简化流程:

int main(void) {
    // 初始化外设...
    if (Check_Update_Flag()) {
        // 1. 擦除Bank2
        for (uint32_t page = 0x08080000; page < 0x080FFFFF; page += 2048) {
            Flash_Erase_Bank2_Page(page);
        }
        // 2. 从外部存储(如SD卡)读取固件并写入Bank2
        uint32_t buffer[128];
        uint32_t write_addr = 0x08080000;
        while (Read_Firmware_Block(buffer, 128)) {
            Flash_Write_Bank2(write_addr, buffer, 128);
            write_addr += 512;
        }
        // 3. 设置跳转标志并复位
        Set_Boot_Flag(BANK2);
        NVIC_SystemReset();
    } else {
        // 正常启动,根据标志跳转
        if (Get_Boot_Flag() == BANK2) {
            Jump_To_App(0x08080000);
        } else {
            Jump_To_App(0x08000000);
        }
    }
}

六、注意事项与避坑指南

  • 选项字节修改不可逆:一旦启用双Bank,地址映射改变,旧固件无法直接运行,需重新烧录。建议在量产前配置好。
  • 中断向量表重定位:Bank2固件需在启动时重定位向量表:SCB->VTOR = 0x08080000;
  • 擦写期间禁止中断:虽然双Bank支持并行,但擦写操作会阻塞CPU,若需实时性,可考虑使用DMA或RTOS任务调度。
  • 电源稳定性:升级期间掉电可能导致Bank2写入不完整,但Bank1仍可启动,因此务必在Bootloader中增加完整性校验(如CRC)。
  • 使用官方库函数:STM32F4标准外设库或HAL库均支持双Bank操作,但需注意版本差异。

七、总结

双Bank闪存为STM32F4提供了强大的在线升级能力,通过合理的Bank切换和并行读写,可实现近乎零停机的固件更新。本文从原理到代码,完整演示了双Bank模式的配置与使用。在实际项目中,建议结合Bootloader设计、固件签名和回滚机制,构建高可靠的OTA系统。