2. ST Stellar硬件与存储架构解析
要真正把双银行Flash OTA做对,第一步不是写升级代码,而是把Stellar这颗MCU的存储“地图”看清楚。ST Stellar系列(如SR5E1、SR6P3等)面向汽车域控与安全应用,其内部Flash并不是一整块连续、可随意擦写的空间,而是由若干物理分区(Bank、Sector、Page)按层级组织,并通过固定的地址映射暴露给CPU与调试器。OTA能否安全切换、能否在升级失败后回滚,本质上取决于你是否理解这些物理边界与访问粒度。
物理分区:Bank、Sector与Page的层级
Stellar的嵌入式非易失存储通常以“双Bank”为基本组织单位。每个Bank内部再划分为若干Sector,Sector是擦除操作的最小单位;Sector内部又由多个Page组成,Page是编程(写入)操作的最小单位。这种“擦除粗、写入细”的粒度设计,是理解OTA分区规划的关键。
以典型配置为例(以下参数为教学示例,具体以对应型号数据手册为准):单Bank容量2MB,双Bank合计4MB;每个Bank划分为8个Sector,每Sector 256KB;每Sector再分为64个Page,每Page 4KB。这意味着:擦除一次至少影响256KB,而写入可以精确到4KB。若把Bootloader、应用A、应用B、参数区混在同一Sector内,一次OTA擦除就会连带破坏其他内容——这是初学者最常见的翻车点。
双银行组织方式与地址映射
Stellar的双Bank并非简单的容量翻倍,而是为“读写分离”和“安全回滚”服务的。典型映射关系是:Bank A起始地址0x0800_0000,Bank B起始地址0x0810_0000(示例偏移0x100000,即1MB对齐)。两个Bank在物理上独立,可以做到“从Bank A运行、向Bank B写入”,从而避免边运行边擦写同一Bank导致的取指冲突。
地址映射上有两个必须区分的概念:物理地址与别名/重映射地址。物理地址是Flash控制器实际访问的单元;而通过选项字节(Option Bytes)或启动配置,可以把Bank A或Bank B映射到0x0800_0000这个“启动别名区”。这样Bootloader只需跳转到固定入口,就能根据当前有效镜像决定从哪个Bank启动。OTA切换的本质,就是修改这个映射选择并复位。
此外,Stellar通常还提供一块独立的信息块/OTP区,用于存放选项字节、保护位、生命周期状态等。它不参与普通擦写,但决定了Bank的读写保护与启动行为,OTA方案中必须把它纳入版本与状态管理。
访问粒度对OTA分区规划的实际影响
理解了粒度,分区规划就有了硬约束。下面给出一个可直接参考的分区规划示例(参数为教学假设,需按实际型号调整):
| 区域 | Bank/Sector | 起始地址(示例) | 大小 | 擦除粒度 | 用途 |
|---|---|---|---|---|---|
| Bootloader | Bank A · Sector 0 | 0x0800_0000 | 256KB | Sector | 启动、校验、跳转 |
| 应用运行区 | Bank A · Sector 1 | 0x0804_0000 | 256KB | Sector | 当前运行镜像 |
| 升级暂存区 | Bank B · Sector 1 | 0x0814_0000 | 256KB | Sector | 接收新镜像 |
| 参数区 | Bank A · Sector 3 | 0x080C_0000 | 256KB | Sector | 版本、CRC、标志 |
注意:应用运行区与升级暂存区必须落在不同Bank,否则“边运行边写入”会触发同一Bank的擦写冲突。同时,参数区单独占一个Sector,是为了让OTA过程中频繁更新的版本号、CRC、有效标志不会因擦除而丢失。
实操:读取并确认分区信息
在动手写OTA前,建议先用调试器或一段只读代码确认实际分区。以下示例展示如何通过读取Flash寄存器与选项字节来核对Bank边界(寄存器名与地址为示意,实际以参考手册为准):
/* 读取Bank起始地址与Sector大小(示意) */
#define FLASH_BASE_A 0x08000000UL
#define FLASH_BASE_B 0x08100000UL
#define SECTOR_SIZE 0x00040000UL /* 256KB */
#define PAGE_SIZE 0x00001000UL /* 4KB */
void flash_dump_layout(void)
{
printf("Bank A base = 0x%08lX\n", FLASH_BASE_A);
printf("Bank B base = 0x%08lX\n", FLASH_BASE_B);
printf("Sector size = %lu KB\n", SECTOR_SIZE / 1024);
printf("Page size = %lu KB\n", PAGE_SIZE / 1024);
/* 读取选项字节中的启动Bank选择位 */
uint32_t opt = FLASH->OPTCR;
printf("Boot bank = %s\n", (opt & BOOT_BANK_B) ? "Bank B" : "Bank A");
}
运行后应能看到Bank A/B的基地址、Sector与Page大小,以及当前启动Bank。若打印出的Sector大小与数据手册不符,说明型号或配置理解有误,必须先纠正再继续。
常见误区与检查点
- 把Page当擦除单位:擦除只能按Sector进行,写Page前必须先擦除其所属Sector。
- 忽略Bank边界:跨Bank写入不会自动切换,必须显式指定目标Bank地址。
- 参数区与代码区同Sector:一次擦除会同时清掉参数,导致版本信息丢失。
- 未确认启动映射:切换Bank后未复位或未更新选项字节,仍从旧Bank启动。