1. 固件打包这件事,到底在打什么
搞RK3588板子的朋友,迟早会碰到一个绕不开的环节:把编译好的各个分区镜像,合成一个可以整包烧录的update.img。不管你是做Ubuntu桌面、Android12,还是跑YOLOv8的边缘盒子,最后交付给产线或者客户的那一刻,手里拿的往往就是这个update.img。
我见过太多人卡在这一步。编译内核、编Buildroot、调设备树都挺顺,一到打包就报错,或者打出来的包烧进去起不来。问题往往不在打包脚本本身,而在于没搞清楚这个包是怎么被“拼”出来的。RK3588的固件打包,本质上是把boot.img、rootfs.img、parameter.txt、uboot.img、trust.img这些零散件,按照分区表的位置关系,塞进一个带头部描述信息的容器里,最终生成瑞芯微平台能识别的update.img。
这篇文章面向的是已经能在RK3588上跑通编译、但打包环节还不太有底的开发者。我会从脚本调用的入口讲起,把mkupdate.sh、afptool、img_maker这几个关键工具串起来,讲清楚每个参数为什么这么填,分区表怎么对齐,以及我实际踩过的那些坑。看完你应该能自己改打包脚本,而不是只会敲一条命令然后祈祷。
2. 打包前的准备工作与目录结构梳理
2.1 先搞清楚你手里有哪些镜像
RK3588的SDK编译完之后,镜像不会散落在各处,通常集中在rockdev/目录下。这个目录是SDK约定的输出汇总点,不同版本的SDK可能叫rockdev也可能叫output,但结构大同小异。以常见的Buildroot或Ubuntu SDK为例,你会看到这些东西:
boot.img:内核加设备树,有些方案会把resource也并进来rootfs.img:根文件系统,Ubuntu的话可能是rootfs.ext4转过来的uboot.img:U-Boot引导程序trust.img:ARM Trusted Firmware,负责安全启动相关misc.img:杂项分区,恢复模式标志位放这儿parameter.txt:分区表描述文件,这是打包的灵魂MiniLoaderAll.bin:一级加载器,烧录工具最先写进去的东西
注意:
parameter.txt不是可有可无的配置文件,它直接决定了每个分区在存储介质上的起始扇区和大小。打包工具读的就是它,烧录工具认的也是它。分区表错了,包打得再漂亮也白搭。
我建议在动手打包前,先ls -l rockdev/看一眼文件时间戳,确认这些镜像都是最近一次编译产出的。我遇到过有人拿半年前编译的boot.img配新编的rootfs.img,烧进去内核和文件系统版本对不上,驱动加载失败,查了一整天才发现是镜像不同步。
2.2 parameter.txt 分区表怎么读
parameter.txt的内容长这样(以eMMC方案为例):
FIRMWARE_VER: 8.1 MACHINE_MODEL: RK3588 MACHINE_ID: 007 MANUFACTURER: RK3588 MAGIC: 0x5041524B ATAG: 0x00200800 MACHINE: 0xffffffff CHECK_MASK: 0x80 PWR_HLD: 0,0,A,0,1 TYPE: GPT CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(trust),0x00002000@0x00008000(misc),0x00010000@0x0000a000(boot),0x00010000@0x0001a000(recovery),0x00020000@0x0002a000(backup),0x00040000@0x0004a000(oem),0x00c00000@0x0008a000(rootfs),-@0x00caa000(userdata:grow)这里最关键的是CMDLINE那一行。mtdparts=rk29xxnand:后面跟的是一串分区定义,格式是大小@起始扇区(分区名)。扇区单位是512字节,所以0x00002000就是 8192 个扇区,等于 4MB。
拿uboot分区举例:0x00002000@0x00004000(uboot),意思是uboot从第0x4000个扇区开始,占0x2000个扇区。换算成字节,起始位置是0x4000 * 512 = 8MB,大小是0x2000 * 512 = 4MB。为什么从8MB开始而不是0?因为前面留给了一级加载器和GPT分区表本身。
userdata那个-@0x00caa000(userdata:grow)里的-表示“剩余全部空间”,grow表示这个分区可以动态扩展。这是给用户数据留的弹性空间,产线烧录后第一次启动会自动把剩余eMMC空间划给它。
实操心得:改分区大小的时候,一定要保证前一个分区的“起始+大小”等于后一个分区的“起始”,中间不能有空洞也不能重叠。我习惯用计算器把每个分区的起止字节都算出来列个表,比肉眼盯着十六进制靠谱得多。
2.3 工具链从哪来
打包用的工具不在系统PATH里,而是SDK自带的。通常在tools/或者RKTools/目录下,你会找到:
afptool:负责把各个镜像按分区表打包成中间格式img_maker:给中间格式加上RKFW头部,生成最终的update.imgmkupdate.sh或mkupdate.bat:封装好的调用脚本
这些工具是预编译的二进制,Linux下是ELF,Windows下是exe。如果你在Ubuntu上打包,直接用Linux版;如果SDK只带了Windows版,可以用wine跑,但我更建议找对应SDK版本的Linux工具,wine跑二进制有时候会有权限和路径问题。
3. 打包脚本的调用链路拆解
3.1 mkupdate.sh 到底做了什么
很多人打包就是敲一句./mkupdate.sh,然后看到输出update.img就完事了。但这个脚本内部其实做了好几件事,理解它对排查问题至关重要。典型的mkupdate.sh逻辑是这样的:
#!/bin/bash # 进入rockdev目录 cd rockdev # 第一步:用afptool打包成update.raw.img ../tools/afptool -pack ./ update.raw.img # 第二步:用img_maker加头部 ../tools/img_maker -RK3588 update.raw.img update.img # 第三步:清理中间文件 rm -f update.raw.imgafptool -pack ./ update.raw.img这个命令的意思是:以当前目录./为根,读取目录下的parameter.txt和各个镜像文件,按照分区表把它们打包成update.raw.img。这个raw文件是纯数据,没有RKFW头部。
img_maker -RK3588 update.raw.img update.img则是给raw文件加上芯片平台标识和校验信息,生成烧录工具能识别的update.img。-RK3588这个参数告诉工具目标芯片型号,不同芯片的头部格式有细微差别。
注意:有些SDK的
mkupdate.sh里芯片型号是写死的,比如从RK3568的SDK改过来的可能还写着-RK3568。如果你拿RK3568的脚本打RK3588的包,烧录工具可能认不出来或者烧进去起不来。一定要检查这一行。
3.2 afptool 的参数细节
afptool除了-pack,还有-unpack和-check两个常用模式。-unpack可以把已有的update.img解包,看看里面到底装了什么,这在排查“为什么烧录后某个分区不对”时特别有用。
# 解包update.img到unpack目录 ../tools/afptool -unpack update.img unpack/解包之后你会看到unpack/目录下按分区名建了子目录,每个子目录里是对应的镜像文件,还有一个parameter.txt。这样你就能对比打包前后的分区表是否一致,镜像文件是否完整。
-check模式用来校验update.img的完整性,它会检查头部校验和和每个分区的数据校验。产线批量烧录前跑一遍-check,能提前发现打包过程中文件损坏的问题。
3.3 img_maker 的头部信息
img_maker生成的头部包含这些关键字段:
| 字段 | 含义 | 典型值 |
|---|---|---|
| RKFW标识 | 固定魔数 | 0x57464B52 |
| 芯片型号 | 目标平台 | RK3588 |
| 头部大小 | 头部占用的字节数 | 0x66 |
| 数据偏移 | raw数据在文件中的起始位置 | 0x66 |
| 数据大小 | raw数据的字节数 | 动态计算 |
| 校验和 | 头部和数据的CRC | 动态计算 |
这个头部是瑞芯微烧录工具识别固件的依据。如果头部损坏或者芯片型号不对,烧录工具会直接报“固件格式错误”或者“芯片不匹配”。
实操心得:如果你改了
parameter.txt里的分区布局,一定要重新跑完整的mkupdate.sh,不能只替换update.img里的某个分区文件。因为头部里的数据大小和校验和是基于整个raw数据算的,局部替换会导致校验失败。
4. 完整打包流程实操记录
4.1 从编译产物到rockdev目录
假设你刚跑完./build.sh或者make,编译产物散落在out/目录下。第一步是把它们汇总到rockdev/。有些SDK的编译脚本会自动做这一步,有些需要手动拷贝。
我习惯写一个简单的汇总脚本,避免漏拷或者拷错版本:
#!/bin/bash ROCKDEV=rockdev mkdir -p $ROCKDEV # 拷贝内核相关 cp out/kernel/boot.img $ROCKDEV/ cp out/kernel/resource.img $ROCKDEV/ 2>/dev/null # 拷贝uboot和trust cp out/u-boot/uboot.img $ROCKDEV/ cp out/trust/trust.img $ROCKDEV/ # 拷贝根文件系统 cp out/rootfs/rootfs.img $ROCKDEV/ # 拷贝分区表和加载器 cp device/rockchip/rk3588/parameter.txt $ROCKDEV/ cp out/u-boot/MiniLoaderAll.bin $ROCKDEV/ echo "汇总完成,检查文件:" ls -lh $ROCKDEV/跑完之后ls -lh看一眼,确认每个文件大小合理。boot.img一般几十MB,rootfs.img看你的文件系统内容,Ubuntu桌面版可能上GB,Buildroot精简版可能就几十MB。如果某个文件只有几KB,那多半是编译出错了,别急着打包。
4.2 修改分区表适配你的存储方案
RK3588支持eMMC、SD卡、SPI Flash等多种启动介质,不同介质的分区表不一样。eMMC通常用GPT,SPI Flash可能用MTD。你拿到的SDK默认parameter.txt可能是针对某个参考板的,直接用到自己的板子上可能分区大小不够或者浪费。
假设你的板子eMMC是32GB,想给rootfs分8GB,userdata留剩下的。计算过程如下:
eMMC总扇区数 = 32GB / 512B = 67108864 扇区 = 0x4000000
前面uboot到oem这些分区加起来(按默认表):
- uboot: 0x2000
- trust: 0x2000
- misc: 0x2000
- boot: 0x10000
- recovery: 0x10000
- backup: 0x20000
- oem: 0x40000
合计 = 0x2000+0x2000+0x2000+0x10000+0x10000+0x20000+0x40000 = 0x8A000 扇区
rootfs起始 = 0x4000 + 0x8A000 = 0x8E000(注意uboot从0x4000开始,前面0x4000是保留区)
rootfs大小8GB = 8 * 1024 * 1024 * 1024 / 512 = 16777216 扇区 = 0x1000000
所以rootfs定义是0x1000000@0x8E000(rootfs)
userdata起始 = 0x8E000 + 0x1000000 = 0x18E000
userdata用-@0x18E000(userdata:grow)表示剩余全部。
把这些填回parameter.txt的CMDLINE行,保存。
注意:改完分区表后,如果之前已经烧录过旧分区表的板子,需要先擦除eMMC再烧新固件,否则GPT分区表冲突会导致挂载失败。可以用烧录工具的“擦除”功能,或者进maskrom模式全片擦除。
4.3 执行打包并验证
分区表改好,镜像齐全,就可以打包了:
cd rockdev ../tools/afptool -pack ./ update.raw.img ../tools/img_maker -RK3588 update.raw.img update.img如果一切正常,你会看到类似输出:
Pack file: ./parameter.txt Pack file: ./uboot.img Pack file: ./trust.img ... Pack file: ./rootfs.img Total size: 0x1A2B3C4D bytes Make update.img success!打包完成后,先别急着烧录,跑一遍校验:
../tools/afptool -check update.img校验通过会输出Check update.img success!。如果报错,通常是某个镜像文件在打包过程中读取失败,或者分区表里有分区指向了不存在的文件。
然后解包对比一下:
mkdir unpack_test ../tools/afptool -unpack update.img unpack_test/ diff unpack_test/parameter.txt parameter.txt如果diff没有输出,说明分区表一致。再ls -l unpack_test/看看每个分区文件大小是否和原始镜像一致。
4.4 烧录验证与启动日志检查
用瑞芯微的烧录工具(Windows下是RKDevTool,Linux下是upgrade_tool)加载update.img,板子进maskrom或loader模式,执行烧录。
烧录完成后串口接上,看启动日志。重点看这几个阶段:
U-Boot SPL或MiniLoader是否正常加载U-Boot是否识别到eMMC并读取分区表- 内核是否从
boot分区加载成功 rootfs是否挂载成功,有没有报VFS: Cannot open root device
如果卡在Starting kernel ...之后没输出,多半是boot.img里的设备树和你的板子不匹配。如果内核起来了但挂载rootfs失败,检查parameter.txt里rootfs的起始扇区和大小是否和实际烧录的一致。
实操心得:我习惯在打包前把
parameter.txt备份一份,命名成parameter_日期.txt。因为调试阶段经常要改分区大小,改乱了可以随时回滚。另外,每次打包后记录一下update.img的MD5,产线反馈问题时先对MD5,能快速判断是不是固件版本不对。
5. 常见问题与排查技巧实录
5.1 打包报错“parameter.txt not found”
这个错误通常是因为afptool -pack的路径参数不对。-pack ./ update.raw.img里的./表示在当前目录找parameter.txt。如果你在rockdev目录外执行,或者parameter.txt不在rockdev里,就会报这个错。
解决方法:cd到rockdev目录再执行,或者把路径参数改成rockdev/。但注意,如果改成rockdev/,afptool会去rockdev/下找镜像文件,所以镜像也必须在那个目录里。
5.2 烧录后卡在logo或者反复重启
这种情况八成是分区表对不上。用afptool -unpack解包你烧录的update.img,对比parameter.txt里的分区定义和实际镜像大小。常见问题有:
- rootfs分区大小小于实际
rootfs.img大小,导致文件被截断 - boot分区起始扇区算错,内核加载到了错误位置
- userdata分区和rootfs分区重叠,文件系统互相覆盖
排查方法:在U-Boot命令行下用mmc part查看实际分区表,和parameter.txt对比。如果U-Boot识别的分区和预期不一致,说明GPT分区表没写对。
5.3 update.img 体积异常大
正常情况下update.img的大小约等于所有分区镜像之和加上头部。如果你发现它比预期大很多,可能是:
userdata分区用了grow但打包时把整个剩余空间都填了0,导致文件巨大- 某个镜像文件本身有问题,比如
rootfs.img是稀疏文件但打包时被展开了
afptool打包时对grow分区的处理是只记录分区定义,不实际填充数据。如果你看到update.img有几十GB,检查一下是不是某个镜像文件本身就这么大。用du -h和ls -lh对比一下,du显示的是实际磁盘占用,ls显示的是表观大小,稀疏文件两者会差很多。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决 |
|---|---|---|---|
| 打包报parameter.txt not found | 工作目录不对 | pwd确认当前位置 | cd到rockdev再打包 |
| 烧录工具报固件格式错误 | img_maker芯片型号不对 | 检查mkupdate.sh里的-RK参数 | 改成-RK3588重新打包 |
| 烧录后无法启动 | 分区表与镜像不匹配 | afptool -unpack对比 | 修正parameter.txt重新打包 |
| rootfs挂载失败 | rootfs分区大小不足 | 对比rootfs.img和分区定义 | 扩大rootfs分区 |
| update.img异常大 | grow分区被填充 | ls -lh和du -h对比 | 检查afptool版本,确认grow处理逻辑 |
| 校验失败 | 镜像文件损坏 | afptool -check | 重新编译镜像再打包 |
5.5 独家避坑技巧
第一个技巧:在mkupdate.sh里加一行set -e。这样任何一步失败脚本都会立即退出,不会带着错误继续往下跑。我见过有人afptool打包失败了但脚本没停,接着img_maker拿旧的raw文件生成了update.img,结果烧进去的是上一次的固件。
第二个技巧:打包完成后用md5sum update.img记录哈希值,同时把parameter.txt和mkupdate.sh一起归档。产线出问题时,先对哈希确认固件版本,再对分区表确认配置,能省掉大量沟通成本。
第三个技巧:如果你的SDK同时支持多个板型,建议为每个板型建独立的rockdev目录,比如rockdev_boardA/、rockdev_boardB/,每个目录里放各自的parameter.txt和镜像。打包时切换目录,避免不同板型的文件混在一起。我吃过这个亏,把A板的boot.img打到B板的包里,烧进去屏幕不亮,查了半天才发现是文件拿错了。
6. 自动化打包与产线适配
6.1 写一个靠谱的打包脚本
SDK自带的mkupdate.sh通常比较简陋,我建议根据自己的产线需求重写一个。下面是我在用的版本,加了错误检查、日志记录和版本标记:
#!/bin/bash set -e VERSION=$(date +%Y%m%d_%H%M%S) ROCKDEV=rockdev TOOLS=tools LOG=pack_${VERSION}.log echo "开始打包,版本:$VERSION" | tee $LOG # 检查必要文件 for f in parameter.txt uboot.img trust.img boot.img rootfs.img; do if [ ! -f "$ROCKDEV/$f" ]; then echo "错误:缺少 $ROCKDEV/$f" | tee -a $LOG exit 1 fi done # 记录分区表 cp $ROCKDEV/parameter.txt parameter_${VERSION}.txt # 打包 cd $ROCKDEV ../$TOOLS/afptool -pack ./ update.raw.img 2>&1 | tee -a ../$LOG ../$TOOLS/img_maker -RK3588 update.raw.img update_${VERSION}.img 2>&1 | tee -a ../$LOG # 校验 ../$TOOLS/afptool -check update_${VERSION}.img 2>&1 | tee -a ../$LOG # 记录哈希 md5sum update_${VERSION}.img | tee -a ../$LOG echo "打包完成:update_${VERSION}.img" | tee -a ../$LOG这个脚本的好处是每次打包都带时间戳,不会覆盖旧版本;日志和分区表一起归档,出问题可追溯;校验步骤强制通过才继续。
6.2 产线批量烧录的注意事项
产线烧录和研发调试不一样,讲究的是稳定和效率。几个关键点:
- 固件统一用
update.img整包烧录,不要用分区单独烧录,减少操作步骤 - 烧录工具配置里勾选“烧录后重启”和“校验”,确保每片板子都验证过
- 如果产线用SD卡量产,把
update.img和烧录工具一起放到SD卡根目录,工人只需插卡上电 - 定期抽检烧录后的板子,跑一遍启动日志检查,防止烧录工具或固件在批量过程中出问题
注意:产线环境如果电压不稳,烧录过程中断电可能导致eMMC分区表损坏。建议产线配UPS,或者烧录工位单独稳压。我见过因为电压波动导致批量烧录后部分板子无法启动的情况,返工成本很高。
6.3 固件版本管理与回滚
每次打包生成的update.img建议按产品名_版本号_日期.img命名,比如RK3588_Box_V1.2_20250115.img。同时维护一个版本记录表:
| 版本号 | 日期 | 变更内容 | 分区表版本 | MD5 |
|---|---|---|---|---|
| V1.0 | 20250101 | 初始版本 | param_v1 | abc123... |
| V1.1 | 20250110 | 修复WiFi驱动 | param_v1 | def456... |
| V1.2 | 20250115 | 扩大rootfs到8GB | param_v2 | ghi789... |
回滚的时候,如果分区表也变了,需要同时回滚parameter.txt并重新烧录整包。不能只替换update.img里的某个分区,因为分区布局变了,旧镜像可能放不到新分区表对应的位置。
7. 关于打包这件事的一些个人体会
RK3588的固件打包,表面上看就是跑两个命令,但真正决定成败的是对分区表的理解和对手头镜像的掌控。我刚开始搞的时候,觉得parameter.txt里那一串十六进制看着就头大,后来强迫自己拿计算器把每个分区的起止字节都算了一遍,画了张图贴在工位上,之后改分区就再也没出过错。
另一个体会是,打包脚本一定要自己改一版。SDK自带的脚本是给参考板用的,你的板子存储容量、分区需求、产线流程都可能不一样。花半小时写个带校验和日志的脚本,后面能省下几十小时的排查时间。
还有一点,update.img打出来只是第一步,烧录验证才是真正的考验。我现在的习惯是每次打包后至少在一台样机上完整走一遍烧录和启动,看串口日志确认每个阶段都正常。样机验证通过的包才归档,没验证的包一律标记为“未测试”,绝不发给产线。
最后分享一个小技巧:如果你不确定某个分区改大之后会不会影响启动,可以先不改parameter.txt,而是用afptool -unpack把现有update.img解开,手动替换里面的镜像文件,再用afptool -pack重新打包。这样可以在不重新编译整个SDK的情况下快速验证分区调整的效果。等验证通过了,再把改动同步回parameter.txt和编译配置里。