# STM32双Bank模式下的Flash擦写并发:OTA升级可靠性的关键实现细节 ## 引言 在嵌入式OTA升级中,Flash擦写操作通常需要暂停CPU执行,导致系统响应延迟,且升级过程中一旦掉电或通信中断,固件可能损坏。STM32的双Bank模式(如STM32F4、F7、H7系列)通过将Flash划分为两个独立Bank,允许在擦写一个Bank时从另一个Bank执行代码,实现并发操作。这一特性不仅提升了升级效率,还通过原子性切换增强了可靠性。本文将深入探讨双Bank模式的原理、配置步骤、代码实现及注意事项。 ## 双Bank模式原理 ### 什么是双Bank? 双Bank模式将Flash存储器分为两个独立的区域(Bank1和Bank2),每个Bank拥有独立的擦写控制逻辑。在单Bank模式下,Flash擦写操作会阻塞CPU,因为代码执行和擦写操作共享同一总线。而在双Bank模式下,CPU可以从一个Bank执行代码,同时擦写另一个Bank,实现真正的并行操作。 ### 并发擦写的优势 - **提升升级速度**:擦写操作不再阻塞CPU,系统可在升级过程中继续处理其他任务(如通信、用户交互)。 - **增强可靠性**:若升级失败,可回滚到另一个Bank中的旧固件,避免设备变砖。 - **支持无缝切换**:通过设置标志位,在复位后引导加载程序选择正确的Bank启动。 ### 硬件支持 并非所有STM32都支持双Bank模式,需查阅具体型号的参考手册。例如,STM32F4系列中,只有Flash容量大于1MB的型号(如STM32F429)支持双Bank;而STM32F7和H7系列则普遍支持。此外,双Bank模式要求Flash的扇区大小对称,且地址映射固定。 ## 配置步骤 ### 1. 检查芯片支持 首先确认芯片是否支持双Bank模式,可通过查看参考手册中的“Flash memory”章节。例如,STM32F429的Flash基地址为0x08000000,Bank1和Bank2各占512KB。 ### 2. 配置Flash选项字节 双Bank模式通过修改选项字节启用。在STM32CubeProgrammer或代码中,设置`FLASH_OPTR`寄存器的`DBANK`位。例如,对于STM32F4: ```c // 启用双Bank模式 FLASH->OPTR |= FLASH_OPTR_DBANK; // 假设DBANK位为1表示双Bank FLASH->CR |= FLASH_CR_OPTSTRT; // 启动选项字节编程 while (FLASH->SR & FLASH_SR_BSY); // 等待完成 ``` 注意:修改选项字节后需复位才能生效。 ### 3. 调整链接脚本 启用双Bank后,Flash地址映射变化。例如,STM32F429的Bank1地址为0x08000000-0x0807FFFF,Bank2为0x08080000-0x080FFFFF。链接脚本需将固件分为两个区域,分别放置于两个Bank。 ```ld MEMORY { FLASH_BANK1 (rx) : ORIGIN = 0x08000000, LENGTH = 512K FLASH_BANK2 (rx) : ORIGIN = 0x08080000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K } ``` ### 4. 编写引导加载程序 引导加载程序(Bootloader)位于Bank1起始地址,负责接收新固件并写入Bank2。升级完成后,设置标志位并复位,Bootloader根据标志位选择启动Bank。 ## 代码实现示例 以下示例基于STM32F429,演示如何在Bank2中擦写固件,同时从Bank1执行代码。 ### 初始化Flash ```c void Flash_Init(void) { // 解锁Flash FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xCDEF89AB; // 等待空闲 while (FLASH->SR & FLASH_SR_BSY); } void Flash_Lock(void) { FLASH->CR |= FLASH_CR_LOCK; } ``` ### 擦除Bank2扇区 ```c void Flash_EraseBank2(void) { // 选择Bank2(例如扇区8-15,每个16KB) for (uint32_t sector = 8; sector <= 15; sector++) { FLASH->CR &= ~FLASH_CR_SNB; // 清除扇区号 FLASH->CR |= (sector << 3) | FLASH_CR_SER; // 设置扇区号和擦除使能 FLASH->CR |= FLASH_CR_STRT; // 开始擦除 while (FLASH->SR & FLASH_SR_BSY); // 等待完成 } } ``` ### 写入数据到Bank2 ```c void Flash_WriteBank2(uint32_t address, uint32_t *data, uint32_t length) { for (uint32_t i = 0; i < length; i++) { // 等待Flash空闲 while (FLASH->SR & FLASH_SR_BSY); // 编程双字(64位) FLASH->CR |= FLASH_CR_PG; *(volatile uint64_t *)address = *(uint64_t *)(data + i); while (FLASH->SR & FLASH_SR_BSY); address += 8; // 每次写入8字节 } FLASH->CR &= ~FLASH_CR_PG; } ``` ### 升级流程示例 ```c void OTA_Upgrade(void) { // 接收新固件到RAM缓冲区(省略通信代码) uint8_t buffer[1024]; // 擦除Bank2 Flash_EraseBank2(); // 写入新固件 Flash_WriteBank2(0x08080000, (uint32_t *)buffer, sizeof(buffer)/8); // 设置升级标志(存储在备份寄存器或Flash选项字节) PWR->CR |= PWR_CR_DBP; // 使能备份域访问 RTC->BKP0R = 0xA5A5; // 示例:备份寄存器存储标志 // 复位 NVIC_SystemReset(); } ``` ### 引导加载程序选择Bank ```c void Bootloader_Jump(void) { if (RTC->BKP0R == 0xA5A5) { // 跳转到Bank2 uint32_t jump_address = *(volatile uint32_t *)(0x08080004); void (*jump)(void) = (void (*)(void))jump_address; __set_MSP(*(volatile uint32_t *)0x08080000); jump(); } else { // 跳转到Bank1 // ... } } ``` ## 注意事项 - **擦写时序**:双Bank模式下,擦写操作仍会占用总线带宽,但CPU可继续执行,需确保中断处理不访问正在擦写的Bank。 - **电源稳定性**:擦写Flash时电流较大,需确保电源稳定,否则可能导致擦写失败。 - **标志位存储**:升级标志建议存储在备份寄存器或独立EEPROM,避免因Flash擦写失败丢失。 - **错误处理**:擦写过程中若发生错误(如电压不足),需重试或回滚。 - **地址对齐**:写入数据需按双字(8字节)对齐,否则触发硬件错误。 - **链接脚本**:确保Bank2的固件链接地址正确,否则跳转后无法执行。 ## 总结 双Bank模式为STM32 OTA升级提供了高效的并发擦写能力,显著提升了升级可靠性和用户体验。通过合理配置选项字节、调整链接脚本并实现健壮的引导加载程序,开发者可以构建支持无缝升级和回滚的嵌入式系统。在实际应用中,需注意电源、时序和错误处理等细节,以充分发挥双Bank的优势。