# 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或代码方式。以下为代码方式: ```c 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. 链接脚本调整 将固件分为两个区域,例如: ```ld MEMORY { BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 512K BANK2 (rx) : ORIGIN = 0x08080000, LENGTH = 512K } ``` ### 3. 并行读写实现 在Bank1运行时,对Bank2进行擦写。核心代码: ```c // 擦除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根据标志决定跳转地址: ```c 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。以下为简化流程: ```c 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系统。