3. 双分区OTA系统架构设计

双分区OTA的核心思想,是在同一片非易失性存储器中保留两套可独立运行的应用程序镜像,通过一个始终可信的引导程序决定本次上电运行哪一套。这样做的直接收益是:升级写入过程不影响当前正在运行的镜像,写入失败或校验不通过时仍可回退到旧镜像,从而把“刷写失败即变砖”的风险降到可接受范围。本节围绕A/B分区划分、Bootloader与Application的职责边界、升级代理与通信接口三部分展开,并给出可直接参考的地址规划与状态机设计。

一、A/B分区划分与地址规划

在AURIX TC3xx上,程序通常存放在PFlash(Program Flash)。以TC375为例,PFlash容量为6 MB,逻辑上分为PF0~PF5共6个Bank,每个Bank 1 MB,擦除粒度为一个PFlash逻辑扇区(通常8 KB或16 KB,具体以芯片手册为准)。做A/B分区时,第一件事是确定“分区”到底指什么:是整片PFlash一分为二,还是只对Application区做A/B,而Bootloader单独占一块永不参与升级的区域。工程上推荐后者,即三区结构:Bootloader区 + Slot A + Slot B。

下面给出一份示例地址规划(以TC375、PFlash 6 MB、Bootloader占1 MB为例,实际项目须按链接脚本与芯片手册核对):

区域起始地址大小说明
Bootloader0x8000_00001 MB不参与OTA,含启动决策与刷写逻辑
Slot A0x8010_00002 MBApplication镜像A
Slot B0x8030_00002 MBApplication镜像B
元数据区0x8050_000064 KB记录活动槽、镜像版本、CRC、升级标志

元数据区是双分区能否可靠回滚的关键。它至少需要保存:当前活动槽(active slot)、每个槽的镜像版本号、镜像长度、镜像CRC32、镜像状态(valid / pending / invalid)、以及一个“升级请求”标志。写入元数据时必须采用“先写备份、再写主份、最后校验”的顺序,避免掉电导致元数据自身损坏。若芯片支持DFlash(数据Flash),也可将元数据放在DFlash中,与PFlash的擦写互不干扰。

分区大小必须留出余量。若Application当前编译产物约1.6 MB,Slot按2 MB规划是合理的;若产物接近2 MB,应重新评估分区或裁剪功能,否则升级包无法完整写入。

二、Bootloader与Application的职责边界

Bootloader的职责可以概括为“决策 + 搬运 + 校验”,它不参与业务逻辑。上电后Bootloader首先读取元数据区,判断是否存在待处理的升级请求:若存在,则进入刷写流程,把新镜像写入非活动槽,写完后计算CRC并与升级包中携带的CRC比对,通过则将新槽标记为pending并切换活动槽,随后跳转到新镜像;若不通过,则清除升级标志,保持原活动槽不变。若不存在升级请求,则直接跳转到当前活动槽的入口。

Application的职责是“运行 + 上报 + 触发”。它正常运行期间通过通信接口接收升级指令,把升级包暂存到外部存储或直接交给Bootloader处理;在收到“确认升级”指令后,Application写入升级请求标志并复位,把控制权交还Bootloader。这里有一个容易被忽略的细节:Application跳转前必须关闭看门狗、关闭全局中断、清理外设状态,否则Bootloader重新初始化时可能因残留状态而异常。

AURIX 双分区OTA 架构与启动决策 Bootloader 0x8000_0000 / 1 MB 决策 · 搬运 · 校验 Slot A 0x8010_0000 / 2 MB Application 镜像A Slot B 0x8030_0000 / 2 MB Application 镜像B 元数据区 0x8050_0000 / 64 KB 活动槽 · 版本 · CRC · 标志 升级代理 接收升级包 · 暂存 写升级请求标志 触发复位交权 通信接口 CAN / CAN-FD 以太网 / UART 诊断服务 UDS 启动决策逻辑 读元数据 → 有升级请求? 是:写非活动槽 → 校验CRC 通过:切活动槽 → 跳转 不通过:清标志 → 保持原槽 否:直接跳转活动槽 跳转至活动槽入口 运行 Application

三、升级代理与通信接口

升级代理是运行在Application中的一段软件模块,它把“外部升级请求”翻译成“Bootloader能理解的元数据操作”。典型流程是:上位机通过CAN或以太网发送升级开始指令,代理校验版本与签名后,把升级包分片写入暂存区(可以是外部Flash,也可以是Slot中非活动槽的预留区域),每写一片回一个确认;全部写完后代理计算整包CRC,写入元数据区的升级请求标志,然后调用复位。复位后Bootloader接管,完成真正的镜像搬运与校验。

通信接口的选择直接影响升级时间。以2 MB镜像为例,若走经典CAN 500 kbps,理论极限约每秒60 KB,实际考虑协议开销约40 KB/s,传输2 MB需要约50秒;若走CAN-FD 2 Mbps数据段,实际可达约400 KB/s,传输时间降到约5秒;若走100 Mbps以太网,则通常在1秒以内。因此,对升级时间敏感的项目应优先评估CAN-FD或以太网,并在代理中实现分片重传与断点续传。

下面给出一个升级请求标志的示例结构,用于说明元数据区应记录哪些字段:

typedef struct {
    uint32_t magic;          // 0x4F544131 "OTA1"
    uint8_t  active_slot;    // 0 = Slot A, 1 = Slot B
    uint8_t  pending_slot;   // 待激活槽
    uint8_t  upgrade_flag;   // 1 = 有升级请求
    uint8_t  reserved;
    uint32_t version_a;      // Slot A 版本号
    uint32_t version_b;      // Slot B 版本号
    uint32_t length;         // 镜像长度
    uint32_t crc32;          // 镜像CRC32
    uint32_t meta_crc;       // 本结构自身CRC
} ota_meta_t;

Bootloader在读取该结构后,先校验magic与meta_crc,再判断upgrade_flag。若标志为1,则把pending_slot指向的槽作为写入目标,写入完成后重新计算crc32并与结构中的crc32比对。只有比对通过,才把active_slot更新为pending_slot,并把upgrade_flag清零。这个“先校验后切换”的顺序,是保证回滚能力的关键:任何一步失败,active_slot都保持原值,系统仍能启动旧镜像。

不要在写入过程中直接修改active_slot。若写入中途掉电,active_slot已被改到未写完的槽,系统将无法启动。正确做法是:写入与校验全部完成后,才在最后一次元数据写入中同时更新active_slot和upgrade_flag。

四、职责划分的工程检查点

在架构评审时,可以用以下问题快速检查设计是否完整:Bootloader是否独立于Application且不参与业务?元数据是否有备份与自校验?升级包是否带版本号与CRC?写入失败时是否保证active_slot不变?Application跳转前是否关闭看门狗与中断?通信接口是否支持分片与重传?这些问题覆盖了双分区OTA最容易出错的环节,建议在编码前逐条确认。

综上,双分区OTA的架构设计可以归纳为:用Bootloader守住启动入口,用A/B双槽隔离新旧镜像,用元数据区记录状态并支撑回滚,用升级代理与通信接口完成升级包的接收与触发。四者各司其职,才能让OTA在真实车载环境中既可用又可回退。