LV025-MCU有BankSwap落地方案
一、 硬件 Bank Swap 的原理
1. 什么是 Bank Swap
上一篇(LV022-MCU无BankSwap落地方案)的核心难题是"链接地址绑定":镜像编译时链接到某个固定地址,放到另一个槽就跑不起来。硬件 Bank Swap 正是为解决这个问题而设计的机制:
Flash 在物理上分成两个 Bank,芯片内部有一套地址映射电路,可以把两个 Bank 与"低位地址 / 高位地址"的对应关系整体对调——而且是硬件完成的对调,软件看到的地址永远不变。
2. 地址映射交换
- 正常模式:Bank 1 占据
0x08000000(运行地址),Bank 2 占据高位地址供写入 - 交换模式:置位交换请求并复位后,硬件把 Bank 2 换到
0x08000000,Bank 1 退到高位地址
关键在于:应用固件永远链接并运行在 0x08000000,它完全不感知自己实际存在于哪个物理 Bank。中断向量表、常量池、跳转地址全部有效,因此只需要编译一份镜像。
3. 与无 Bank Swap 方案的本质差异
| 维度 | 无 Bank Swap(F103) | 有 Bank Swap(L4/H5/G4) |
|---|---|---|
| 镜像份数 | 两个地址变体 | 一份 |
| 切换动作 | 改 VTOR 指向不同槽位(地址不同) | 改硬件映射(地址不变) |
| 回滚实现 | Bootloader 按标志选槽 | 再次交换映射即可 |
| Bootloader 复杂度 | 需理解两套地址 | 只需处理一个运行地址 |
| 升级包体积 | 可能双倍(双变体) | 单份 |
二、 STM32L4 / L5 的实现要点
1. 双 Bank 结构与 BFB2
以 STM32L4 系列(如 STM32L471、STM32L476)为例:
- Flash 分为 Bank 1 与 Bank 2,各占一半容量(如 1 MB 型号各 512 KB)
- 选项字节(Option Bytes)中有一位
BFB2(Boot from Bank 2):BFB2 = 0:从0x08000000开始执行(Bank 1 位于低位)BFB2 = 1:硬件交换两个 Bank 的地址映射,从 Bank 2 开始执行
- 页大小 2 KB(L4),Bank 内可按页擦写
升级流程因此变得非常简洁:
(1)运行在当前 Bank 的 App 下载新固件,写入另一个 Bank(正被执行的 Bank 无法自我擦写,天然隔离)
(2)校验新固件 CRC 与签名
(3)置位 BFB2(指向新固件所在 Bank),并置"待确认"标志与计数
(4)复位,芯片从新 Bank 启动,运行地址仍是 0x08000000
(5)新固件自检通过 → 清除待确认标志;失败或超限 → Bootloader 复位 BFB2 切回旧 Bank
2. 代码实现
// bank_ota.c —— STM32L4 双 Bank 升级关键操作(HAL 实现)
#include "stm32l4xx_hal.h"
#include <stdbool.h>
#define BANK1_START 0x08000000u
#define BANK2_START 0x08040000u /* 512 KB 型号:Bank 2 起始 */
#define FLASH_PAGE_SZ 2048u
/* 判断当前运行在哪个 Bank,返回目标写入 Bank 的起始地址 */
uint32_t ota_target_bank_addr(void)
{
uint32_t pc = (uint32_t)ota_target_bank_addr; /* 取当前代码地址 */
return (pc < BANK2_START) ? BANK2_START : BANK1_START;
}
/* 写入新固件到目标 Bank */
int ota_write_firmware(const uint8_t *image, uint32_t len)
{
uint32_t dst = ota_target_bank_addr();
uint32_t bank = (dst == BANK2_START) ? FLASH_BANK_2 : FLASH_BANK_1;
FLASH_EraseInitTypeDef erase = {0};
uint32_t page_error = 0;
HAL_FLASH_Unlock();
erase.TypeErase = FLASH_TYPEERASE_PAGES;
erase.Banks = bank;
erase.Page = 0;
erase.NbPages = (len + FLASH_PAGE_SZ - 1u) / FLASH_PAGE_SZ;
if (HAL_FLASHEx_Erase(&erase, &page_error) != HAL_OK) {
HAL_FLASH_Lock();
return -1;
}
for (uint32_t i = 0; i < len; i += 8u) {
uint64_t dword = 0xFFFFFFFFFFFFFFFFull; /* 不足 8 字节补 0xFF */
uint32_t chunk = (len - i >= 8u) ? 8u : (len - i);
for (uint32_t k = 0; k < chunk; k++) {
dword &= ~((uint64_t)0xFFu << (k * 8u));
dword |= ((uint64_t)image[i + k] << (k * 8u));
}
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,
dst + i, dword) != HAL_OK) {
HAL_FLASH_Lock();
return -2;
}
}
HAL_FLASH_Lock();
return 0;
}
/* 切换启动 Bank:改写选项字节 BFB2,然后复位 */
int ota_switch_boot_bank(bool boot_from_bank2)
{
FLASH_OBProgramInitTypeDef ob = {0};
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBGetConfig(&ob);
ob.OptionType = OPTIONBYTE_USER;
ob.USERType = OB_USER_BFB2;
ob.USERConfig = boot_from_bank2 ? OB_BFB2_ENABLE : OB_BFB2_DISABLE;
if (HAL_FLASHEx_OBProgram(&ob) != HAL_OK) {
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
return -1;
}
HAL_FLASH_OB_Launch(); /* 触发选项字节重载(内部会复位) */
return 0; /* 不会执行到这里 */
}【注意】
HAL_FLASH_OB_Launch() 会触发系统复位使新选项字节生效。在双 Bank 结构下,切换 Bank 本质上就是"改配置 + 复位",比无 Bank Swap 方案的"改判断逻辑 + 跳转"更接近硬件原生行为,出错概率更低。
3. 回滚实现
回滚只需把 BFB2 改回去。工程上通常这样组织:
- Bootloader 位于两个 Bank 的起始页(双 Bank 芯片支持把前几页设为"安全区/共享区",或分别在两个 Bank 各放一份 Bootloader)
- 参数区记录
active_bank与pending_bank、试用计数,逻辑与 LV016-选槽与回滚机制 完全一致,只是"选槽"变成"选 Bank 并置 BFB2"
三、 STM32 其它系列的差异
| 系列 | 机制 | 说明 |
|---|---|---|
| STM32L4 / L4+ | 双 Bank + BFB2 选项位 | 最典型的 Bank Swap 实现 |
| STM32L5 / U5 | 双 Bank + SWAP_BANK 位 + TrustZone | 支持安全区/非安全区,可与安全启动组合 |
| STM32H5 | 双 Bank + SWAP_BANK,产品状态生命周期(PSA) | 引入 CLOSED / LOCKED 等状态,量产级安全 |
| STM32G4 | 部分型号支持双 Bank,DBANK 选项位 | 需按具体型号手册确认 |
| STM32G0B1 / G0C1 | 双 Bank + SWAP_BANK 选项位 | G0 系列中支持双 Bank 的型号,性价比之选 |
| STM32H7 | 双 Bank(部分型号)+ SWAP_BANK | 大容量型号支持,注意 AXI/ITCM 别名地址 |
| STM32F1 / F4(多数型号) | 不支持 | 走 LV022-MCU无BankSwap落地方案 |
选型时的核查清单:芯片手册里是否有 SWAP_BANK / BFB2 选项位?双 Bank 是否支持独立擦写(RWW)?两个 Bank 是否存在容量不对称?这些决定了方案能否落地。
四、 特例:i.MX RT 片内无 Flash
i.MX RT(如 RT1052 / RT1060)是跨界 MCU,片内没有 Flash,固件存放在外部 QSPI NOR 或 eMMC 中,运行时拷贝到 SRAM / SDRAM:
外部 NOR Flash ──(BootROM + 二级 Bootloader)──▶ 内部 SRAM / 外部 SDRAM 运行它天然具备"存储地址与运行地址解耦"的特性,因此:
- 镜像只链接到 RAM 运行地址,放哪个存储槽都能跑
- A/B 就是外部 Flash 里的两个槽,切换只需让 Bootloader 从另一个槽拷贝
- 升级包不需要双变体,回滚只需切回原槽
代价是每次上电都要搬运一次(几十毫秒到几百毫秒),以及 RAM/SDRAM 的容量约束。
五、 厂商与开源现成方案
1. ST X-CUBE-SBSFU
ST 官方的安全启动与安全固件更新套件(Secure Boot & Secure Firmware Update),支持:
- 双槽(Dual Slot)或单槽 + 外部存储
- 固件签名验证(ECDSA)
- 防回滚(版本计数)
- YMODEM 串口升级通道
它把 Bootloader、验签、回滚状态机全部实现好了,可直接作为工程起点,省掉自研 Bootloader 的坑。
2. 跨厂商开源 Bootloader
MCUboot 与 wolfBoot 不绑定单一厂商,在有 Bank Swap 的芯片上可直接用硬件换位实现双槽,比本篇的选项位操作更省事;它们在没有 Bank Swap 的芯片上如何模拟 Swap 语义、各自的代价是什么,见 LV022-MCU无BankSwap落地方案 第四节。
3. NXP SBL(Secure Bootloader)
面向 i.MX RT / LPC 系列,支持:
- 从外部 Flash 启动的 SB 文件格式(含签名与元数据的镜像容器)
- 通过 UART/USB 进行 In-Application 升级
- 与 MCUXpresso 工具链集成
六、 对比与选型建议
| 维度 | 无 Bank Swap(F103 方案) | 有 Bank Swap(L4/L5 等) | 片内无 Flash(i.MX RT) |
|---|---|---|---|
| 镜像份数 | 两个地址变体 | 一份 | 一份 |
| 切换方式 | VTOR + 跳转 | 选项位 + 复位 | Bootloader 换槽拷贝 |
| 回滚成本 | 改参数区标志 | 改选项位 | 换槽拷贝 |
| 自研工作量 | 高(Bootloader 全新写) | 低(逻辑已简化) | 中 |
| 附加限制 | Flash 空间紧张 | 选项字节有擦写次数上限 | 需外部存储与 RAM |
选型建议:
(1)新项目优先选带 Bank Swap 的型号:STM32L4/L5/U5/H5、G4、H7 的大部分型号都支持,能省掉大量 Bootloader 复杂度
(2)选项字节擦写次数有限(通常约 1000 次):不要在每次升级都改 BFB2,而应让"当前运行 Bank"与"待升级 Bank"交替,把写选项字节的次数降到最低;更稳的做法是优先用参数区记录目标 Bank,仅在确实需要换位时写选项位
(3)老项目(F103/F407 等):按 LV022-MCU无BankSwap落地方案 的方案一到方案四选型
(4)优先复用官方套件:SBSFU、MCUboot、wolfBoot 已经把验签、回滚、断电安全做透,自研的目标应是"理解机制",而非"重新实现一遍"
本文档由 markdowncli 技能辅助生成