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 Flash 物理分区与地址映射(教学示例) Bank A(0x0800_0000 起) Sector 0 · 256KB Bootloader(只读保护) Sector 1 · 256KB 应用镜像 A(运行区) Sector 2 · 256KB 应用镜像 B(备份/升级区) Sector 3 · 256KB 参数/标定区 Sector 4~7 · 保留/扩展 每Sector 64 Page × 4KB Bank B(0x0810_0000 起) Sector 0 · 256KB Bootloader 备份 Sector 1 · 256KB 应用镜像 A 副本 Sector 2 · 256KB 应用镜像 B 副本 Sector 3 · 256KB 参数区副本 Sector 4~7 · 保留/扩展 每Sector 64 Page × 4KB 地址 偏移 访问粒度:擦除 = Sector(256KB)|编程 = Page(4KB)|读取 = 字节/字对齐

双银行组织方式与地址映射

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起始地址(示例)大小擦除粒度用途
BootloaderBank A · Sector 00x0800_0000256KBSector启动、校验、跳转
应用运行区Bank A · Sector 10x0804_0000256KBSector当前运行镜像
升级暂存区Bank B · Sector 10x0814_0000256KBSector接收新镜像
参数区Bank A · Sector 30x080C_0000256KBSector版本、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大小与数据手册不符,说明型号或配置理解有误,必须先纠正再继续。

判断依据:若Bank A与Bank B的基地址差值为0x100000(1MB),且Sector大小为0x40000(256KB),则分区规划可按上表执行;若差值或粒度不同,需重新计算各区域起始地址,避免越界擦写。

常见误区与检查点

  • 把Page当擦除单位:擦除只能按Sector进行,写Page前必须先擦除其所属Sector。
  • 忽略Bank边界:跨Bank写入不会自动切换,必须显式指定目标Bank地址。
  • 参数区与代码区同Sector:一次擦除会同时清掉参数,导致版本信息丢失。
  • 未确认启动映射:切换Bank后未复位或未更新选项字节,仍从旧Bank启动。