国科安芯头像
关注
基于 AS32S601 的 A/B 网络 OTA 方案:eFlash 约束、断电保护与自动回滚封面图

基于 AS32S601 的 A/B 网络 OTA 方案:eFlash 约束、断电保护与自动回滚

摘要

网络 OTA 的核心不是把新固件写进 Flash,而是保证任何时刻掉电、复位或新镜像启动失败时,设备仍能回到一个可信、可启动的版本。本文以 AS32S601 的 eFlash 约束为边界,给出 A/B 双镜像、双份状态记录、Pending 启动意图与确认回滚机制的实现框架。文中涉及寄存器位值和厂商底层函数的地方均保留为 BSP/伪代码接口,避免把未经验证的实现细节当成芯片事实。

1. 为什么 OTA 不能覆盖正在运行的镜像

单镜像升级的最大风险是:下载任务、Flash 驱动和业务代码本身都在当前镜像中执行。擦除或改写当前执行区,会让取指、异常处理和中断向量同时失去稳定性;即使暂时没有跑飞,掉电也会留下半写入镜像。

  • 写入目标必须是非活动区;活动镜像在新版本确认前始终保留。
  • “下载完成”不等于“可启动”:镜像完整性、状态记录提交、首次启动与应用确认必须分阶段处理。
  • 回滚不是故障补丁,而是启动器在超时、复位或确认失败时的正常决策路径。

2. AS32S601 eFlash:决定方案边界的硬件约束

根据芯片设计手册第 5 章:PFlash 最大容量为 2 MB(4 个 512 KB block),DFlash 最大容量为 512 KB。eFlash 的 sector 为 4 KB、row 为 512 B;编程最小粒度为 64 bit,目标地址需 8 字节对齐,并且一次编程不得跨越 row。擦除命令为 sector erase 0x01、block erase 0x02,编程命令为 0x10。

状态轮询应围绕 BUSY 与 FINISH,错误收敛至少检查 OPERR、WPERR、ECC1ERR、ECC2ERR。设计手册还明确:当一个 block 正在擦除或编程时,只支持读取另一个 block。因此 Bootloader、Flash 驱动和被读取的关键常量必须落在与被操作 block 不同的区域,或在进入 Flash 操作前转入 RAM 执行。

时钟配置也应被纳入启动顺序:仅在系统时钟初始化完成后,且 eFlash 空闲(未执行擦除/编程)时设置 EFLASH_CNFG.CLKFRQ。具体频率编码、寄存器位值及解锁序列请以当前芯片版本手册和 BSP 为准,本文不假设其数值。

约束

已验证的设计事实

实现含义

容量与 block

PFlash 最大 2 MB / 4 × 512 KB;DFlash 最大 512 KB

分区只是示例;链接脚本和区大小必须以实际型号容量为准。

擦除与 row

sector 4 KB;row 512 B

擦除按 sector 规划;下载块可小于等于 512 B。

编程粒度

最小 64 bit;地址 8 字节对齐;不得跨 row

写入前做对齐、长度和 row 边界检查。

并发读取

操作一个 block 时仅可读取另一个 block

擦/写期间,完整执行路径和中断行为要么都在另一物理 block,要么整体转入 RAM。

完成与错误

BUSY / FINISH;OPERR / WPERR / ECC1ERR / ECC2ERR

每次操作后等待完成、读取状态、清错并记录失败原因。

3. A/B 分区与双份状态记录设计

一个实用的布局是:Bootloader 放在固定、受保护的启动区;应用槽 A 和槽 B 分别存放完整镜像;状态记录放在独立的小型元数据区。这里的容量分配只是一种示例,不能直接套用到所有 AS32S601 型号:链接脚本、镜像上限、状态区位置和分区大小必须与实际 device capacity、block 边界及产品功能一起确定。

状态记录采用双份(Record-0 / Record-1)并增加序号 generation。明确建议把 Record-0 与 Record-1 放在两个可独立擦除的 4 KB sector:更新前先确认另一份(当前被启动器选中的)记录已提交且 CRC 合法,绝不擦除唯一有效副本;随后只擦除非活动记录所在的 sector。这样可避免“擦掉唯一状态页后掉电”的单点风险。

非活动记录的提交顺序应是:擦除后写入除 commit 外的 staged payload(commit 保持擦除态),回读并校验字段与 payload CRC,再把 commit 作为最后一步从擦除态编程为完成态。commit 必须独占一个 8 字节对齐的 64-bit 编程单元,且不纳入 staged payload CRC;最终还要回读,确认 commit、字段和 CRC 同时有效。启动时只在 commit 与 CRC 都正确的副本中选择 generation 更大的记录。

字段

示例含义

启动器处理

magic / format_version

记录身份与格式版本

不匹配则忽略该副本。

generation

单调递增的记录序号

在 CRC 合法副本中选较新者。

active_slot / pending_slot

当前确认槽与候选槽

Pending 仅用于一次受控试启动。

image_size / image_crc

候选镜像长度与完整性摘要

跳转前按产品策略校验长度和 CRC。

boot_attempts / confirm_deadline

试启动计数或确认窗口

超限或超时则撤销 Pending 并回到 active_slot。

record_crc / commit

payload CRC 与最终提交标志(commit 不计入 payload CRC)

commit 是独立 64-bit 单元的最后一次编程;任一项无效即回退另一份记录。

图 1  总体升级策略

4. 网络 OTA 主流程:先写非活动区,再切换启动意图

  1. 启动器从双份状态记录恢复 active_slot,并启动已经确认的应用镜像。
  2. 应用接收版本、镜像长度、摘要和签名等元数据,确认目标为非活动槽。
  3. 按 sector 擦除目标槽,并以不跨 512 B row 的小块下载、编程和读取回校。
  4. 完整镜像验证通过后,预先验证当前状态副本仍有效,擦除非活动记录所在的独立 4 KB sector;回读校验 staged payload 后,最后以独立 64-bit commit 单元提交 pending_slot。此时尚未改变 active_slot。
  5. 复位后 Bootloader 仅把 pending_slot 当作候选镜像试启动;应用自检成功后显式确认,才提升为 active_slot。

镜像认证机制应结合产品安全目标选择,例如受保护的版本元数据、签名验证、防回滚版本计数和密钥存储。CRC 可以发现传输或存储错误,但不能替代来源认证。

5. eFlash 写入实现:擦除、行编程与校验

将网络分包与 Flash 物理写入解耦:网络层可以收到任意长度的数据,但 Flash 层必须把它整理为 8 字节对齐、最多一个 row 的写入单元。跨 row 的尾部数据应留在 RAM 缓冲区,与下一包拼接后再写入;未满 64 bit 的镜像尾部可按镜像格式填充并计入完整性校验。

读写并发约束要按“完整执行闭包”审查,而不是只把一个 Flash 驱动函数放入 RAM:任一 PFlash erase/program 事务期间,要么活动可执行区以及所有可达的指令、常量、向量表和中断处理函数都位于被操作 block 以外的物理 block;要么把完整调用路径连同相关中断行为整体迁入 RAM(或在事务窗口内按产品策略禁止/接管中断)。仅搬移 driver 函数仍可能因常量访问、异常或中断取指而违反跨 block 读取限制。

清单 1  单个下载块的边界检查(BSP/伪代码)

static ota_result_t ota_program_chunk(uint32_t dst, const uint8_t *src, size_t len)
{
    if ((dst & 0x7U) != 0U || len == 0U || (len & 0x7U) != 0U || len > 512U) return OTA_ALIGN_ERR;
    if (((dst & 0x1FFU) + len) > 512U) return OTA_ROW_CROSS_ERR;
    eflash_unlock();
    if (!eflash_wait_idle()) return OTA_BUSY_ERR;
    eflash_program_64bit_units(dst, src, len);
    return eflash_finish_and_clear_status();
}

上例的 eflash_unlock、eflash_wait_idle、eflash_program_64bit_units 与状态清除均为 BSP 接口名称。实际实现应在每个 sector 擦除和每次编程后检查 FINISH/错误状态,并把 OPERR、WPERR、ECC1ERR、ECC2ERR 映射为可追踪的失败原因。

清单 2  双份状态记录的安全提交顺序(BSP/伪代码)

ota_result_t ota_commit_pending(const ota_state_t *next)
{
    ota_state_t staged = *next;
    if (!state_other_committed_copy_is_valid()) return OTA_STATE_PRECONDITION_ERR;
    staged.generation = ota_next_generation();
    staged.commit = OTA_COMMIT_ERASED;
    staged.record_crc = crc32_staged_payload_excluding_commit(&staged);
    if (!state_erase_inactive_record_4k_sector()) return OTA_STATE_ERASE_ERR;
    if (!state_write_payload_except_commit(&staged)) return OTA_STATE_WRITE_ERR;
    if (!state_readback_payload_valid(&staged)) return OTA_STATE_VERIFY_ERR;
    if (!state_program_commit_64bit(OTA_COMMIT_DONE)) return OTA_STATE_COMMIT_ERR;
    return state_readback_committed_valid(&staged) ? OTA_OK : OTA_STATE_VERIFY_ERR;
}

清单中的 state_* 是表达顺序与前置条件的 BSP/伪代码接口,不代表厂商 API。`state_erase_inactive_record_4k_sector` 只能针对未被当前启动决策依赖的那一个记录 sector;`state_write_payload_except_commit` 保持 commit 擦除态;`state_program_commit_64bit` 则把独占、8 字节对齐的 commit 单元作为最后一次 64-bit 编程。payload CRC 的计算范围明确排除 record_crc 自身和 commit 字段,避免 commit 状态变化破坏 staged payload 的校验。

6. 启动确认与自动回滚

候选镜像第一次启动不应立即覆盖 active_slot。Bootloader 可在跳转前递增 boot_attempts 或写入短期试启动标志;应用完成时钟、外设、配置迁移和业务自检后,调用一个受控的确认接口。若确认窗口内发生看门狗复位、硬复位或再次上电,Bootloader 保持/撤销 Pending 并回到已确认槽。

清单 3  启动器选择逻辑(伪代码)

boot_target_t boot_select_target(const ota_state_t *state)
{
    if (!state_is_valid(state)) return boot_factory_or_safe_image();
    if (state->pending_slot != OTA_SLOT_NONE && pending_is_eligible(state))
        return boot_try_pending_once(state->pending_slot);
    return boot_confirmed_slot(state->active_slot);
}

void app_confirm_healthy(void)
{
    /* 仅在应用自检、配置迁移与关键通信均成功后调用。 */
    ota_confirm_pending_via_bsp();
}

图 2  启动确认与回滚

7. 断电恢复:把每一个中间态设计成可启动

断电恢复设计的关键是区分“数据还没写完”和“意图已经提交”。擦除/下载中的非活动镜像可以丢弃;状态切换则必须先验证另一独立记录 sector 有效,再擦除非活动 sector,随后写入 payload、回读校验,最后以单独的 64-bit commit 单元提交。启动器永远只信任 CRC 与 commit 都正确的最新记录,因此在任意写入指令之间掉电都能找到旧记录或新记录,而不是半条记录。

  • 擦除过程中掉电:目标槽保持无效,已确认槽继续启动。
  • 镜像写入过程中掉电:不写 pending_slot,下一次 OTA 从断点策略或重新下载开始。
  • 状态副本擦除、payload 写入或预提交回读中掉电:commit/CRC 不通过,启动器选择另一独立 sector 中的旧副本。
  • Pending 试启动中掉电:确认未发生,按尝试次数或确认窗口回到已确认镜像。
  • 确认写入中掉电:双份状态记录保证至少保留一个可判定的有效副本。

图 3  异常与断电恢复

8. 安全边界与测试清单

A/B 解决的是可用性和可恢复性,不自动等于安全 OTA。生产方案应把传输保护、镜像来源认证、密钥生命周期、版本防回滚、调试口策略和故障日志纳入同一威胁模型。不要把下载通道的 TLS 或 CRC 当成固件签名验证的替代品。

测试项

注入方式

期望结果

row 边界

在 512 B row 末尾构造跨界分包

Flash 层拒绝跨 row 写入或正确拆分,不产生越界。

对齐与尾包

制造非 8 字节地址、非完整 64-bit 尾部

按镜像格式缓冲/填充;非法地址被拒绝。

擦除/写入掉电

对每个 sector、row 的关键操作点断电

旧 active_slot 仍可启动,未完成目标槽不被选择。

状态记录掉电

在另一有效副本的前提下,对 inactive 4 KB sector 的擦除、payload、回读、独立 64-bit commit 间断电

启动器选择 CRC+commit 有效的另一份记录。

读写并发闭包

擦/写 block 时触发可达常量访问、异常或中断

完整执行路径在另一 block 或 RAM;不得仅验证 Flash driver 在 RAM。

Pending 失败

候选镜像看门狗复位或不确认

超出策略后自动回滚至确认槽。

错误状态

由测试桩模拟 OPERR/WPERR/ECC 错误

记录失败原因、清理状态,不提交 pending_slot。

安全验证

篡改摘要、签名或版本号

认证失败不切换启动意图,保留已确认镜像。

9. 结语

AS32S601 的 sector、row、64-bit 编程和跨 block 读写限制,决定了 OTA 不应只被实现为一个网络下载任务。把非活动槽写入、镜像校验、双份状态、Pending 试启动和应用确认串成一个原子性逐步增强的流程,设备就能在升级失败和断电场景下保留可启动路径。落地前,请以实际芯片容量、当前版本手册、BSP 和链接脚本为最终依据完成地址规划与底层寄存器实现。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/ANSILIC/article/details/163994607

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--