Skip to content

LV040-image镜像文件打包原理与生成 ​

一、 为什么需要 app.img ​

1. 裸 bin 缺三样东西 ​

sram-app.bin / flash-app.bin 是链接器吐出的纯字节流,从镜像第一个字节开始,到最后一个字节结束。它本身没有任何自描述信息,接收方拿到之后无法回答三个问题:

问题裸 bin 的困境app.img 的答案
装到哪不知道是 Flash 版还是 SRAM 版头部第 16 字节起 4 字节目标地址
收多少不知道长度,只能靠"串口安静了"判断头部第 8 字节起 4 字节载荷长度
收对没没有任何校验头部第 12 字节起 4 字节 CRC32

因此 app.img 的本质是"给裸 bin 加一层可自描述的封套",而不是另一种固件格式。

2. 封套解决了什么工程问题 ​

  • 同一套协议装两种镜像:flash-app 与 sram-app 的固件帧完全一样,只有地址字段不同,Bootloader 不需要两套解析;
  • 结束条件明确:有了长度,状态机知道"收满 N 字节就该看尾部了",不依赖超时;
  • 错误可定位:魔数错、版本错、长度越界、CRC 错各自有独立提示,不用猜是链路问题还是镜像问题;
  • 可离线核对:pack_iap.py -x 能把头部解析回人可读的字段,排查"烧进去的是不是这个版本"很方便。

二、 帧结构 ​

1. 字段布局 ​

image 镜像文件的帧结构

偏移长度字段字节序说明
08魔数 IAPBIN02ASCII前 6 字节是 IAPBIN,后 2 字节是版本
84载荷长度 N小端上限 128KB
124载荷 CRC32小端只覆盖载荷
164目标加载地址小端只接受 0x08020000 / 0x20007000
20N载荷—原始 bin 字节
20 + N8尾部 IAPEOF02ASCII形式校验,防截断

2. 魔数与版本 ​

魔数被拆成"前缀 + 版本"两段判断,这样版本不匹配时能给出准确提示而不是笼统的"格式错误":

c
// atk-f103/Drivers/BSP/Iap/iap.h
#define IAP_MAGIC_PREFIX        "IAPBIN"    /* 魔数前 6 字节 */
#define IAP_MAGIC_VERSION       "02"        /* 帧格式版本, 2 字节 */
#define IAP_MAGIC               "IAPBIN02"  /* 完整魔数, 8 字节 */
#define IAP_TRAILER             "IAPEOF02"  /* 尾部魔数, 8 字节 */

版本升到 02 的原因就是新增了地址字段:01 版的头部只有 16 字节,接收方无法区分两种镜像。版本号放在魔数里,比单独开一个版本字段省 4 字节,也让旧固件在比对魔数时自然失败。

3. CRC32 为什么只覆盖载荷 ​

CRC 的职责是"确认这段字节没被链路改坏",而头部的长度、地址、CRC 字段本身是控制信息:如果它们也参与校验,接收方就必须先读全帧才能验证头部,超时判断和内存规划都会被迫后移。现在的分工是:

  • 头部字段做范围校验(地址是否合法、长度是否超容器),不做 CRC;
  • 载荷做 CRC32,收满后由主循环算一次,与头部给出的期望值比对;
  • 尾部魔数做截断校验,长度对但尾部错,说明发送方少发了或多发了。

三、 怎么生成 bin ​

1. fromelf 的两种调用 ​

.bin 由 ARMCC 自带的 fromelf 从 .axf 导出,两种等价写法:

powershell
# 只输出 bin
fromelf --bin -o flash-app.bin flash-app.axf

# 同时输出反汇编与 bin(排查时用)
fromelf --bin --text -c -o flash-app.bin flash-app.axf > flash-app.dis

--bin 会按链接地址把可加载段拼成扁平镜像,因此 bin 的第一个字节就是向量表的初始栈顶,起始地址等于 IROM1 的基址。

2. 构建脚本里的做法 ​

工程里把这一步注册成了"构建后执行"的用户程序,build_flash.ps1 / build_sram.ps1 也会在构建成功后显式补一次并校验产物:

项目做法好处
何时执行UV4 构建成功后不会拿旧 axf 生成新 bin
输出位置与 .hex 同目录(Target 的 Output 目录)产物集中,-c 清理时一并删除
是否依赖 PATH脚本里带候选路径,最后才回退 PATHKeil 装在非默认盘也能跑
校验生成后检查文件存在且非空避免"构建成功但没有 bin"的静默失败

3. bin 的边界 ​

bin 只包含已初始化的可加载段:.text、.rodata、.data 的初值。.bss 与栈不占 bin 空间(它们在 RAM 里,由启动代码清零)。所以判断"镜像多大"时看 bin 大小即可,但要判断"占多少 Flash",还得看 map 文件里的 load region size。

四、 怎么生成 app.img ​

1. 打包 ​

powershell
# flash-app:默认地址 0x08020000
python tools\pack_iap.py -p flash-app\MDK-ARM\flash-app\flash-app.bin -o flash-app\MDK-ARM\flash-app\app.img

# sram-app:显式指定 0x20007000
python tools\pack_iap.py -p sram-app\MDK-ARM\sram-app\sram-app.bin -a 0x20007000 -o sram-app\MDK-ARM\sram-app\app.img

工程里这一步同样挂在"构建后执行"上,因此 release 构建结束就能直接拿到 app.img。

2. 打包时做的检查 ​

检查判据拦下什么
地址合法必须是 TARGETS 里的两个基址之一手工敲错地址、拿 flash-app.bin 当 SRAM 版打包
容量载荷 ≤ 容器容量(128KB / 24KB)链接进错容器的镜像
向量表初始栈顶落在内部 RAM,复位向量落在容器内且为 Thumb打包了空的、错位的、非 Thumb 的 bin
覆盖输出已存在时需 -f误覆盖上一次的产物

向量表检查可以关(--no-vector-check),但只在"故意打包一个特殊镜像"时才用,日常不该关。

3. 解析与核对 ​

powershell
# 只看头部,不导出
python tools\pack_iap.py -x flash-app\MDK-ARM\flash-app\app.img

# 把载荷还原成裸 bin,用于比对
python tools\pack_iap.py -x sram-app\MDK-ARM\sram-app\app.img -o check.bin

解析模式会打印目标地址、载荷长度、CRC32,以及容器剩余空间。现场排查"设备里跑的到底是哪一版"时,先看这个头部,再决定要不要重发。

五、 两种镜像的差异 ​

对比项flash-appsram-app
打包地址默认 0x08020000必须 -a 0x20007000
容量上限128KB24KB
落盘位置与运行位置相同暂存区 0x08040000
触发运行go flash 或上电自动go sram(每次上电都要重来)
调试版地址0x080000000x08000000

一个高频错误是把 sram-app.bin 用默认地址打包:pack_iap.py 的向量表检查会拦下它(复位向量落在 0x20007000,不在 Flash APP 区内),提示 vector table check failed。看到这条提示,先确认 -a 参数,再确认 bin 是哪个 Target 编出来的。


本文档由 markdowncli 技能辅助生成