LV060-IAP升级完整流程
一、 为什么还要单独写一篇
前面几篇把零件都拆过了:收帧状态机在 LV045,go 命令与跳转在 LV050,两种镜像的工程配置在 LV030 / LV035。但把 app.img 从串口发进去、到 APP 跑起来,这条链路横跨三个执行位置——USART1 中断、主循环、跳转时的硬件状态切换,任何一篇单独看都只剩半条链路。
常见的三类疑问恰好都落在接缝上:
- 头收完之后,什么时候算这一帧收完?
- 校验到底校的是 Flash 里的镜像,还是 SRAM 里的镜像?
- 同样是
go,为什么 Flash 目标直接跳,SRAM 目标要先搬一次?
本篇只负责把链路串直,细节仍以各篇为准。
二、 端到端链路
以 PC 通过串口发送一份 app.img 为例,一次升级的全部动作如下:
这条链路不是一条直线,图上能看到三件事:
- 一处上下文分界:
iap_rx_poll()返回IAP_EVT_FRAME_OK的那一刻。之前一切都在中断里,只做"把字节装进帧",不校验、不落盘、不打印;之后一切都在主循环里,依次是校验、落盘、回执、等命令、跳转; - 一个失败回路:尾部不符、CRC32 不符、或者 3 s 超时,都走同一条退路——丢弃整帧、回到
SCAN重新对齐,命令行照常可用,不必重启; - 一个决定去处的判断:帧头声明的目标地址只有两个合法值,
0x08020000走左路(就地执行),0x20007000走右路(先落暂存区,go sram时才搬进内部 RAM)。两路最后汇到同一个跳转动作。
三、 阶段与代码的对照
| 阶段 | 执行者 | 对应代码 | 结束条件 |
|---|---|---|---|
| 等命令 | 主循环 | iap_service_task() | 收到字节流 |
| 对齐帧头 | 中断 | iap_rx_byte() → IAP_ST_SCAN | 8 字节滑窗命中 IAPBIN02 |
| 读头部 | 中断 | IAP_ST_HEADER | 再收 12 字节,得到 N / CRC32 / 目标地址 |
| 收载荷 | 中断 | IAP_ST_PAYLOAD | 收满 N 字节,逐字节写进外部 SRAM |
| 比对尾部 | 中断 | IAP_ST_TRAILER | 8 字节与 IAPEOF02 逐字节相等 |
| 帧级校验 | 主循环 | iap_rx_poll() → IAP_ST_VERIFY | 载荷 CRC32 与头部声明值相等,上报 FRAME_OK |
| 落盘 | 主循环 | iap_install_image() | 擦除 + 编程 + 回读 CRC 全部通过 |
| 回执 | 主循环 | svc_on_frame_ok() | 打印 send 'go flash' 或 send 'go sram',不自动跳转 |
| 跳转 | 主循环 | cmd_go() → iap_load_app() | 向量表合法则交出控制权,不返回 |
四、 三个接缝上的疑问
1. "收完"是双条件,不是收够字节
只按长度判断是不够的:长度字段本身也可能被噪声改写。所以一帧收完要求两件事同时成立:
IAP_ST_PAYLOAD收满头部声明的 N 字节;- 紧接着的 8 字节与
IAPEOF02逐字节相等。
尾部不一致不会去"猜"哪里少了字节,而是直接丢掉整帧、回到 SCAN。另外收帧中途字节间隔超过 IAP_RX_TIMEOUT_MS(3 s)也会判超时复位,避免主机中途取消后设备卡在半帧上。
2. CRC32 校的是 SRAM 里的载荷,不是 Flash
按发生顺序,一共三层校验,对象各不相同:
| 层 | 校验对象 | 位置 | 失败结果 |
|---|---|---|---|
| 帧级 CRC32 | 外部 SRAM 载荷缓冲里刚收完的 N 字节 | iap_rx_poll() | IAP_ERR_CRC,整帧丢弃,不碰 Flash |
| 落盘回读 | Flash 里刚写进去的内容 | iap_write_appbin() / iap_write_sram() | IAP_WR_ERR_VERIFY,留在 Bootloader |
| 向量表检查 | 目标地址处的前 8 字节 | iap_app_is_valid() | iap_load_app() 返回 -1,不跳转 |
也就是说:协议层的完整性由帧级 CRC 保证,介质层的正确性由回读保证。前者防传输错误,后者防擦写错误,两者不能互相替代。
3. go 的分叉只在"要不要搬运"
go 是普通文本命令,走命令表,与帧协议没有关系;它只决定"从哪儿开始执行":
| 命令 | 目标基址 | 动作 |
|---|---|---|
go flash | 0x08020000 | 向量表检查通过后直接跳转,落盘位置就是运行位置 |
go sram | 0x20007000 | 先 iap_load_sram_app() 从暂存区 0x08040000 拷 24 KB 进内部 SRAM,回读 CRC 通过再跳 |
go(不带参数) | 最近一次装好的镜像 | 同上,按该镜像的类型选路径 |
SRAM 目标多一次搬运,是因为帧头声明的 0x20007000 是运行位置,而 Flash 里只有暂存位置。这样设计的好处是掉电后镜像还在暂存区,重新上电直接 go sram 就能跑,不必重发帧。
五、 失败一律回到命令行
链路上任何一步失败——超时、尾部不符、CRC 不符、擦写失败、向量表不合法——都不会复位,也不会锁死:状态机回到 SCAN,主循环回到命令行,直接重发即可。这一点是设计目标,不是巧合:升级本来就要面对不可靠的串口和随时可能断电的现场。
六、 与其它各篇的分工
- 收帧状态机的逐状态细节:LV045《IAP 串口接收与帧处理》
- 帧格式与
app.img的打包:LV040《image 镜像文件打包原理与生成》 - Flash 分区与擦写实现:LV050《IAP 跳转与向量表原理》
- 两种镜像的工程配置:LV030《FLASH APP 设计》、LV035《SRAM APP 设计》
- 升级过程中的喂狗:LV055《看门狗问题与解决方案》