3. 双银行Flash机制原理
在单银行 Flash 的 MCU 上做 OTA,最棘手的问题不是“怎么把新固件写进去”,而是“写的时候程序从哪里取指令”。擦写 Flash 期间,同一块 Flash 通常无法同时被读取,CPU 要么停摆,要么必须把执行代码搬到 RAM。ST Stellar 系列引入双银行(Dual-Bank)Flash 结构,正是为了把“正在运行的代码”和“正在被擦写的代码”在物理上分开,从而让 OTA 从“停机刷写”变成“边跑边刷”。理解双银行机制,核心就是理解三个概念:Bank Swap、Bank Erase 和 RWW(Read-While-Write),以及它们如何共同支撑一次安全的 OTA 升级。
一、双银行 Flash 的物理结构
Stellar 的 Flash 被划分为两个独立的 Bank,通常记为 Bank A 和 Bank B。每个 Bank 拥有独立的地址映射、独立的擦写控制逻辑和独立的读通路。关键点在于:两个 Bank 的擦写操作可以互不干扰,一个 Bank 处于编程/擦除状态时,另一个 Bank 仍然可以被 CPU 正常取指和读取数据。这就是 RWW 能力的物理基础。
以典型配置为例(以下参数为教学示例,实际以具体型号数据手册为准):假设总 Flash 为 4 MB,则 Bank A 与 Bank B 各 2 MB。每个 Bank 内部再按扇区(Sector)划分,例如每扇区 8 KB,则每个 Bank 有 256 个扇区。OTA 升级时,新固件被写入“非活动 Bank”,而 CPU 继续从“活动 Bank”执行,二者互不冲突。
二、RWW:读-写并行的本质与边界
RWW(Read-While-Write)并不是“任意时刻都能读写”,而是有明确边界的。它的准确含义是:当 Bank B 正在执行擦除或编程操作时,CPU 可以从 Bank A 读取指令和数据,而不会被阻塞。反过来也一样。但如果 CPU 试图读取正在被擦写的那个 Bank,就会触发总线错误或返回无效数据,因为该 Bank 的读通路被擦写控制器占用。
这带来一条重要的工程约束:OTA 期间,所有中断向量表、中断服务程序、Flash 驱动代码、通信协议栈,都必须位于活动 Bank 中。如果中断向量表在非活动 Bank,而该 Bank 正在被擦写,一旦发生中断,CPU 取向量就会失败,系统直接跑飞。因此,双银行 OTA 的链接脚本(Linker Script)必须把关键代码段固定到活动 Bank 的地址区间,或者通过 Bank Swap 机制保证运行时向量表始终指向活动 Bank。
另一个边界是 RWW 的时序。擦除一个扇区通常需要几十毫秒,编程一个字需要几十微秒。在这段时间内,活动 Bank 的读取是正常的,但擦写控制器会占用 Flash 高压电荷泵等共享资源。如果两个 Bank 同时发起擦写请求,硬件会进行仲裁,通常只允许一个 Bank 处于擦写状态。因此 OTA 写入流程应串行化:先擦除目标扇区,再编程,再校验,不要并发操作两个 Bank 的擦写。
三、Bank Erase:按 Bank 粒度的擦除策略
Bank Erase 指的是以整个 Bank 为单位执行擦除操作。在 OTA 场景中,它通常用于“准备接收新固件”的阶段:在写入新固件之前,先把非活动 Bank 整体擦除,使其所有位回到 1 状态,然后再按扇区或按页编程。
Bank Erase 与扇区擦除的区别在于粒度和时间。整 Bank 擦除一次完成,比逐扇区擦除 256 次快得多,但代价是整 Bank 数据全部丢失。因此 Bank Erase 只适用于非活动 Bank,绝不能对活动 Bank 执行。一个安全的 OTA 流程通常如下:
- 确认当前活动 Bank(例如 Bank A),非活动 Bank 为 Bank B。
- 对 Bank B 执行 Bank Erase,等待擦除完成并检查状态寄存器。
- 将新固件按扇区写入 Bank B,每写一扇区后回读校验。
- 全部写入完成后,计算整镜像 CRC 并与固件头中的 CRC 比对。
- 校验通过后,在 OTA 元数据区写入“Bank B 就绪”标志。
- 触发 Bank Swap,使 Bank B 成为活动 Bank,复位后从新固件启动。
这里有一个容易忽略的细节:Bank Erase 期间,如果活动 Bank 中的代码正在执行 Flash 驱动本身,而该驱动又调用了擦除函数,那么擦除函数必须位于活动 Bank 中,且不能与被擦除的 Bank 有任何地址重叠。否则会出现“自己擦自己”的致命错误。
四、Bank Swap:切换执行银行
Bank Swap 是双银行 OTA 的“临门一脚”。它的作用是把两个 Bank 的地址映射关系对调:原本映射到 0x0800_0000 的 Bank A 被映射到高地址,原本在高地址的 Bank B 被映射到 0x0800_0000。这样,CPU 复位后从 0x0800_0000 取到的向量表就来自新固件所在的 Bank B。
Bank Swap 的实现方式通常有两种:一种是通过 Flash 控制寄存器中的 Swap 位,写入后立即生效或在下一次复位后生效;另一种是通过选项字节(Option Byte)配置,掉电后仍然保持。工程上更推荐“先置就绪标志,再复位,复位过程中由 Bootloader 读取标志并执行 Swap”的方式,这样即使 Swap 过程中掉电,系统仍能回到旧固件,不会变砖。
Swap 完成后,原来的 Bank A 变成非活动 Bank,可以用于下一次 OTA 写入。这样就形成了 A/B 交替升级的循环:v1.0 在 A,升级到 v2.0 写入 B 并 Swap;v2.0 在 B,升级到 v3.0 写入 A 并 Swap。每个 Bank 都保留一份可回滚的旧固件,极大提升了 OTA 的可靠性。
五、三个机制对 OTA 的意义
把三者放在一起看:RWW 提供了“边跑边写”的能力,Bank Erase 提供了“快速清空目标区”的手段,Bank Swap 提供了“原子切换执行区”的机制。它们共同把 OTA 从“停机、搬 RAM、刷写、重启”的高风险流程,变成了“后台写入、校验、切换、复位”的可控流程。
对开发者而言,需要重点检查三件事:第一,链接脚本是否把中断向量和 Flash 驱动固定在活动 Bank;第二,OTA 写入流程是否严格串行、每步校验;第三,Swap 是否有掉电保护和回滚路径。只要这三点做到,双银行 Flash 就能把 OTA 的停机时间和变砖风险同时压到最低。