# STM32 双Bank切换实现OTA零停机:基于F4/F7的Bank交错映射实战 ## 1. 双Bank架构原理 STM32 F4/F7系列(如STM32F427、STM32F746)的Flash被划分为两个独立的Bank,每个Bank大小相等(例如1MB Flash分为两个512KB Bank)。这种设计允许在运行一个Bank时,对另一个Bank进行擦写操作,从而实现OTA升级不中断当前应用。 **关键特性:** - **Bank交错映射**:通过配置选项字节(Option Bytes)中的BOR_LEV和DBANK位,可以切换Flash的映射方式。默认情况下,Flash从地址0x08000000开始连续映射;启用双Bank后,地址空间被交错映射,使得两个Bank的起始地址均为0x08000000,但通过高位地址选择Bank。 - **无缝切换**:升级完成后,通过软件复位或跳转指令,将PC指针切换到新Bank的复位向量,实现快速切换,无需外部干预。 ## 2. 硬件与软件准备 - **硬件**:STM32F4/F7开发板(如Nucleo-F746ZG),外部Flash可选(用于备份固件)。 - **软件**:STM32CubeIDE(或Keil MDK),STM32 HAL库。 ## 3. 配置步骤 ### 3.1 启用双Bank模式 1. 在STM32CubeMX中,选择目标芯片,进入`Option Bytes`配置。 2. 设置`DBANK`位为`1`(双Bank模式),并配置`BOR_LEV`为合适的电平(如3.3V)。 3. 生成初始化代码。 ### 3.2 链接脚本调整 修改链接脚本(.ld文件),将Flash区域划分为两个Bank: ```c /* 假设Flash总大小1MB,每个Bank 512KB */ MEMORY { FLASH_BANK0 (rx) : ORIGIN = 0x08000000, LENGTH = 512K FLASH_BANK1 (rx) : ORIGIN = 0x08080000, LENGTH = 512K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K } ``` ### 3.3 编写双Bank切换代码 ```c #include "stm32f7xx_hal.h" // 定义Bank地址 #define BANK0_START 0x08000000 #define BANK1_START 0x08080000 // 当前运行Bank标志 extern uint32_t _estack; // 栈顶地址 void JumpToBank(uint32_t bank_addr) { // 检查栈顶地址是否合法 if ((*(volatile uint32_t*)bank_addr & 0x2FFE0000) != 0x20000000) { return; } // 设置主栈指针 __set_MSP(*(volatile uint32_t*)bank_addr); // 获取复位向量地址 uint32_t reset_vector = *(volatile uint32_t*)(bank_addr + 4); // 跳转 void (*reset_handler)(void) = (void (*)(void))reset_vector; reset_handler(); } void OTA_Update(void) { // 假设新固件已写入非活动Bank(例如当前运行Bank0,则写入Bank1) uint32_t target_bank = (GetCurrentBank() == BANK0_START) ? BANK1_START : BANK0_START; // 擦除目标Bank(示例:擦除Bank1) FLASH_EraseInitTypeDef erase_init; erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; erase_init.Banks = FLASH_BANK_1; erase_init.Sector = 0; erase_init.NbSectors = 8; // 根据实际扇区数 erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t sector_error = 0; HAL_FLASHEx_Erase(&erase_init, §or_error); // 写入固件(此处省略具体写入逻辑,通常从外部存储或网络读取) // WriteFirmwareToBank(target_bank); // 设置标志位,表示新固件就绪 SetUpdateFlag(target_bank); // 软件复位 NVIC_SystemReset(); } uint32_t GetCurrentBank(void) { // 通过PC指针判断当前运行Bank uint32_t pc = (uint32_t)GetPC(); if (pc >= BANK0_START && pc < BANK0_START + 0x80000) { return BANK0_START; } else { return BANK1_START; } } // 获取PC指针(内联汇编) __attribute__((always_inline)) static inline uint32_t GetPC(void) { uint32_t pc; __asm volatile ("mov %0, pc" : "=r" (pc)); return pc; } ``` ### 3.4 启动代码修改 在系统启动时,检查更新标志,决定跳转到哪个Bank: ```c void SystemInit(void) { // ... 其他初始化 if (CheckUpdateFlag() == BANK1_START) { JumpToBank(BANK1_START); } // 否则继续运行Bank0 } ``` ## 4. 完整示例:双Bank升级流程 以下是一个简化的升级流程示例,展示如何从Bank0切换到Bank1: ```c int main(void) { HAL_Init(); SystemClock_Config(); // 检查是否有待升级固件 if (CheckUpdateFlag() == BANK1_START) { // 验证固件完整性(如CRC) if (VerifyFirmware(BANK1_START)) { // 清除标志 ClearUpdateFlag(); // 跳转到Bank1 JumpToBank(BANK1_START); } else { // 固件无效,回滚到Bank0 JumpToBank(BANK0_START); } } // 正常运行当前Bank应用 while (1) { // 应用逻辑 } } ``` ## 5. 注意事项 - **地址映射**:启用双Bank后,Flash地址映射发生变化,务必确保链接脚本和代码中的地址正确。 - **中断向量表**:每个Bank的应用必须有自己的中断向量表,并在启动时重定位到对应Bank的起始地址。 - **Flash擦写时间**:虽然双Bank允许并行操作,但擦写期间CPU仍可能被阻塞(取决于Flash接口),建议使用中断或DMA优化。 - **固件验证**:切换前必须验证新固件的完整性(如CRC、签名),避免启动失败。 - **回滚机制**:设计回滚逻辑,若新固件启动失败,自动回退到旧Bank。 - **电源稳定性**:升级过程中确保电源稳定,防止Flash写入失败。 ## 6. 总结 通过STM32双Bank架构,我们实现了OTA升级的零停机切换。核心在于利用Bank交错映射,使两个应用独立运行,并通过跳转实现快速切换。本文提供了原理、配置、代码和注意事项,为高可靠性嵌入式系统提供了实用方案。实际项目中,还需结合具体硬件和需求优化细节。