RK3576_OpenHarmony6.1_AI自动化烧录实践
AI 帮我写了个 RK3576 烧录脚本:完整代码 + 5 个适配点 + 闭环验证
RK3576 手动烧一次要在 GUI 里点十几步。这篇给出一份 159 行的 bash 脚本,把它压成一条命令。
附:照抄必改的 5 处、让 AI 写出你自己那份的 prompt 模板、以及“烧进去的确实是这份产物”的验证方法。

手动烧录的日常:逐条加载十几个分区的固件路径、勾选、点「执行」、盯右侧日志窗肉眼看结果。换台机器路径全变,得重来一遍。
一、先看结果
脚本叫 flash_rk3576.sh,用法:
bash flash_rk3576.sh -c # 只查设备,不写入任何分区
bash flash_rk3576.sh # 默认 repair:只烧 system + vendor
MODE=ohos bash flash_rk3576.sh # 全部 OpenHarmony 分区
MODE=full bash flash_rk3576.sh # ohos + boot + loader + GPT + uboot
MODE=all bash flash_rk3576.sh # full + userdata(会清用户数据)
PARTS="system:system.img" bash flash_rk3576.sh # 自定义分区
DRY=1 bash flash_rk3576.sh # 只打印命令,不执行
四种模式各烧什么:
| 模式 | 烧录范围 | 典型场景 |
|---|---|---|
repair | system + vendor | 同一基线上改了几个库 |
ohos | 再加 sys-prod / chip-prod / updater / eng_system / chip_ckm | 换了整套 OpenHarmony 分区 |
full | 再加 boot + MiniLoaderAll + parameter.txt + uboot | 换内核 / 换引导 |
all | 再加 userdata | 换基线、板子来路不明 |
默认不烧的三个分区(存设备专属数据):persist(传感器/工厂校准)、nvdata1、nvdata2(MAC/SN)。只有显式写进 PARTS 才会碰。
先查设备:
$ bash flash_rk3576.sh -c
== 查找处于 Loader/MaskROM 模式的板子 ==
List of rockusb connected(1)
DevNo=1 Vid=0x2207,Pid=0x5000,LocationID=253 Mode=Maskrom SerialNo=<device-sn>
== LocationID=253 Mode=Maskrom
不放心就先演算,看它到底要执行什么:
$ DRY=1 MODE=repair bash flash_rk3576.sh
== 模式: MODE=repair loader=0 gpt=0 uboot=0 dry=1
== LocationID=253 Mode=Maskrom
[DRY] $TOOL -s 253 DI -system $IMG/system.img
[DRY] $TOOL -s 253 DI -vendor $IMG/vendor.img
== DRY 模式结束,未写入任何分区 ==
二、底座:树里的 upgrade_tool
能自动化的根本原因不是“有人写了个脚本”,而是 Rockchip 在 SDK 里带了一个纯命令行下载器。它的命令集和 GUI 里的操作一一对应:
| 命令 | 含义 | GUI 里的对应动作 |
|---|---|---|
LD | 列出 rockusb 设备 | 左下角“发现设备” |
UL <loader> -noreset | 下载 Loader | 「升级固件」页 |
DI -p parameter.txt | 写 GPT 分区表 | 加载 parameter |
DI -<分区名> <img> | 烧一个分区 | 勾一条 + 执行 |
RD | 复位板子 | 烧完自动重启 |
坑:同一个 SDK 树里并存三代 CLI,名字都叫 Upgrade Tool、命令集也一样,但不是同一个二进制,对新 SoC 的支持程度不同:
| 所在目录 | 版本 |
|---|---|
loader/rkbin/tools/ | v1.54 |
developtools/.../DeployDevice/resource/RK3568_tool/ | v2.4 |
rktools/flashtool/ | v2.55 ← RK3576 用这个 |
不用连板子就能确认版本:file 一下是静态链接的 x86-64 ELF,strings | grep "Upgrade Tool" 就能读到版本号。
三、完整脚本
把 ROOT 改成你的源码根目录即可直接用。
#!/bin/bash
# =============================================================================
# RK3576 OpenHarmony 烧录脚本
# 依赖:Rockchip 树内自带的 upgrade_tool(纯命令行,不需要 GUI)
#
# 前置条件(二选一):
# A. 板子正在运行 OpenHarmony:hdc shell "reboot loader"
# B. 板子断电,按住 RECOVERY/MASKROM 键上电
# 执行 `flash_rk3576.sh -c` 能看到 Mode=Loader/Maskrom 即就绪。
#
# 用法:
# bash flash_rk3576.sh -c # 只查询设备连接,不写入任何分区
# bash flash_rk3576.sh # 默认 MODE=repair:只烧 system + vendor
# MODE=ohos bash flash_rk3576.sh # 全部 OpenHarmony 分区(不含 boot/uboot/GPT)
# MODE=full bash flash_rk3576.sh # ohos + boot + MiniLoaderAll + parameter.txt + uboot
# MODE=all bash flash_rk3576.sh # full + userdata(会清空用户数据)
# PARTS="system:system.img" bash flash_rk3576.sh # 自定义分区列表,覆盖 MODE
# DRY=1 bash flash_rk3576.sh # 只打印将要执行的命令
#
# 刻意不默认烧录的分区(存的是设备专属数据,除非在 PARTS 里显式列出):
# persist(传感器/工厂校准)、nvdata1、nvdata2(MAC/SN 等)
#
# 环境变量:
# MODE / PARTS / FLASH_LOADER / FLASH_GPT / FLASH_UBOOT / LOG / DRY
# =============================================================================
set -u
# ---------------------------------- 改这里 ----------------------------------
ROOT="${OHOS_ROOT:-/path/to/your/ohos}" # ← OpenHarmony 源码根目录
TOOL="$ROOT/rktools/flashtool/flash.x86_64" # ← 烧录工具,见第四节第 1 条
IMG="$ROOT/out/rockchip/packages/phone/images" # ← 镜像目录,见第四节第 2 条
# ---------------------------------------------------------------------------
LOG=${LOG:-/tmp/flash_rk3576_$(date +%Y%m%d_%H%M%S).log}
DRY=${DRY:-0}
# ---------------------------------------------------------------- 设备查询模式
if [ "${1:-}" = "-c" ] || [ "${1:-}" = "--check" ]; then
echo "== tool: $TOOL"
[ -x "$TOOL" ] || { echo "!! 工具不存在或不可执行: $TOOL"; exit 2; }
"$TOOL" LD
exit $?
fi
# ---------------------------------------------------------------- 参数与模式
MODE=${MODE:-repair}
case "$MODE" in
repair)
PARTS_DEFAULT="system:system.img vendor:vendor.img"
DEF_LOADER=0; DEF_GPT=0; DEF_UBOOT=0 ;;
ohos)
PARTS_DEFAULT="system:system.img vendor:vendor.img sys-prod:sys_prod.img
chip-prod:chip_prod.img updater:updater.img eng_system:eng_system.img chip_ckm:chip_ckm.img"
DEF_LOADER=0; DEF_GPT=0; DEF_UBOOT=0 ;;
full)
PARTS_DEFAULT="boot:boot.img system:system.img vendor:vendor.img sys-prod:sys_prod.img
chip-prod:chip_prod.img updater:updater.img eng_system:eng_system.img chip_ckm:chip_ckm.img"
DEF_LOADER=1; DEF_GPT=1; DEF_UBOOT=1 ;;
all)
PARTS_DEFAULT="boot:boot.img system:system.img vendor:vendor.img sys-prod:sys_prod.img
chip-prod:chip_prod.img updater:updater.img eng_system:eng_system.img chip_ckm:chip_ckm.img
userdata:userdata.img"
DEF_LOADER=1; DEF_GPT=1; DEF_UBOOT=1 ;;
*)
echo "!! 未知 MODE=$MODE(可选 repair|ohos|full|all)"; exit 2 ;;
esac
PARTS=${PARTS:-$PARTS_DEFAULT}
FLASH_LOADER=${FLASH_LOADER:-$DEF_LOADER}
FLASH_GPT=${FLASH_GPT:-$DEF_GPT}
FLASH_UBOOT=${FLASH_UBOOT:-$DEF_UBOOT}
say() { echo "$@"; echo "$@" >> "$LOG"; }
run() { # run <期望输出子串> <命令...>
local expect="$1"; shift
local out
if [ "$DRY" = "1" ]; then
say " [DRY] $*"
return 0
fi
out=$("$@" 2>&1)
{ echo "----- \$ $*"; echo "$out"; } >> "$LOG"
echo "$out" | tail -3
if echo "$out" | grep -q "$expect"; then
say " [OK] $expect"
return 0
fi
say " [FAIL] 期望输出 '$expect'"
return 1
}
step() { # step <期望输出子串> <命令...> (失败重试 3 次)
local expect="$1"; shift
local i
for i in 1 2 3; do
say "--> $*"
run "$expect" "$@" && return 0
say " retry $i ..."
sleep 2
done
say "!! FAILED: $*"
return 1
}
# ---------------------------------------------------------------- 前置检查
say "== 日志: $LOG"
say "== 镜像目录: $IMG"
[ -x "$TOOL" ] || { say "!! 工具不存在或不可执行: $TOOL"; exit 2; }
[ -d "$IMG" ] || { say "!! 镜像目录不存在: $IMG"; exit 2; }
say "== 模式: MODE=$MODE loader=$FLASH_LOADER gpt=$FLASH_GPT uboot=$FLASH_UBOOT dry=$DRY"
say "== 查找处于 Loader/MaskROM 模式的板子 =="
LD=$("$TOOL" LD 2>&1); echo "$LD" >> "$LOG"; echo "$LD"
# 注意:本工具 LocationID 以十六进制打印(%x),必须按 hex 解析,不能用 [0-9]*
LOC=$(echo "$LD" | grep -o 'LocationID=[0-9a-fA-FxX]*' | head -1 | cut -d= -f2)
LOC=${LOC#0x}; LOC=${LOC#0X}
DEVMODE=$(echo "$LD" | grep -o 'Mode=[A-Za-z]*' | head -1 | cut -d= -f2)
say "== LocationID=${LOC:-none} Mode=${DEVMODE:-none}"
if [ -z "$LOC" ]; then
if [ "$DRY" = "1" ]; then
say "-- DRY 模式且未发现设备,用占位 LocationID=0 继续演算"
LOC=0
else
say "!! 未发现 rockusb 设备:请先让板子进入 Loader/MaskROM 模式"
exit 2
fi
fi
# 逐项核对镜像是否存在(缺文件直接中止,避免烧到一半失败)
MISSING=0
for pair in $PARTS; do
img=${pair##*:}
[ -f "$IMG/$img" ] || { say "!! 缺少镜像: $IMG/$img"; MISSING=1; }
done
[ "$MISSING" = "0" ] || exit 2
# ---------------------------------------------------------------- 烧录
if [ "$FLASH_LOADER" = "1" ]; then
step "Upgrade loader ok" "$TOOL" -s "$LOC" UL "$IMG/MiniLoaderAll.bin" -noreset || exit 3
fi
if [ "$FLASH_GPT" = "1" ]; then
step "Write gpt ok" "$TOOL" -s "$LOC" DI -p "$IMG/parameter.txt" || exit 3
fi
if [ "$FLASH_UBOOT" = "1" ]; then
step "Download image ok" "$TOOL" -s "$LOC" DI -uboot "$IMG/uboot.img" "$IMG/parameter.txt" || exit 3
fi
# 分区名 : 镜像文件(分区名取自 parameter.txt / config.cfg)
for pair in $PARTS; do
part=${pair%%:*}; img=${pair##*:}
step "Download image ok" "$TOOL" -s "$LOC" DI "-$part" "$IMG/$img" || exit 3
done
if [ "$DRY" = "1" ]; then
say "== DRY 模式结束,未写入任何分区 =="
exit 0
fi
say "== 复位板子 =="
"$TOOL" -s "$LOC" RD 2>&1 | tee -a "$LOG" | tail -3
say "== 烧录完成 OK =="
say "== 复位后请等待 40~60 秒,再执行: hdc list targets =="
整份脚本只有两个核心函数,其余都是它们的使用者:
run()—— 执行命令并匹配期望输出子串,同时把完整输出写进日志step()—— 在run()外套一层失败重试 3 次
这两个函数是“能自动化”的关键:把“人看屏幕判断成功”变成“机器匹配字符串”。没有它们,脚本只能敲完命令然后祈祷;有了它们,每一步都能自动判定、自动重试、事后可取证。
四、照抄必改的 5 处
1. 工具路径和版本
脚本里写的是 $ROOT/rktools/flashtool/flash.x86_64。如果你换一个版本的 OpenHarmony 树,这里可能变。先确认版本:
"$TOOL" -v # 或 strings "$TOOL" | grep "Upgrade Tool"
树里可能同时存在 v1.54 / v2.4 / v2.55 三个,选错会在 UL 或 DI 阶段失败。
2. 镜像目录
脚本里是 $ROOT/out/rockchip/packages/phone/images。
注意:产品名是 rk3576,但产物目录在 out/rockchip/ 下(chipset 级构建),不是 out/rk3576/。这个只能实测或查文件确认。
ls "$IMG" | head # 能看到 system.img / vendor.img / parameter.txt 就对了
3. LocationID 是十六进制
工具用 %x 打印设备信息:
DevNo=1 Vid=0x2207,Pid=0x5000,LocationID=253 Mode=Maskrom SerialNo=...
脚本里用的是 hex 安全解析:
LOC=$(echo "$LD" | grep -o 'LocationID=[0-9a-fA-FxX]*' | head -1 | cut -d= -f2)
LOC=${LOC#0x}; LOC=${LOC#0X}
如果你照抄成 grep -o 'LocationID=[0-9]*',在这次拿到的 253(纯数字)上照样能跑。
但只要 ID 里出现 a–f(比如 1a3),就会被截断成 1,选中错误设备——这种 bug 平时不发作,一发就是事故。
4. 分区名和 GPT 要对得上
PARTS 里的分区名必须与 parameter.txt 里的完全一致(含连字符/下划线)。RK3576 的分区是:
uboot / boot / persist / nvdata1 / nvdata2 / system / vendor
sys-prod / chip-prod / updater / eng_system / chip_ckm / userdata
这里有个真实的坑:不同构建产物的 GPT 可能不一样。我们手上两套镜像的差别是:
新: ... 0x19000@0x656000(chip-prod), 0x10000@0x66F000(updater), ...
旧: ... 0x19000@0x656000(chip_prod), 0x30000@0x66F000(updater_boot), ...
- 分区名:
sys-prod/chip-prod(连字符) vssys_prod/chip_prod(下划线) updater段大小:32 MiB vs 96 MiB
后果是 chip_ckm 和 userdata 的起始扇区相差 0x20000(64 MiB)。只烧 GPT、不烧这两个分区,就会错位。
处理办法:全量烧录时把 GPT 和所有受影响分区一起烧。同时确认 persist / nvdata1 / nvdata2 在两套里偏移一致(我们核对过,一致),所以它们不受影响。
核对命令:
grep CMDLINE "$IMG/parameter.txt" # 看新参数的布局
cat /proc/partitions # 设备上执行,看实际布局(单位是 KiB)
5. 不用 sudo(先查 udev)
vendor 自带的 flash.sh 用的是 sudo。但先查一下有没有 udev 规则:
# /etc/udev/rules.d/90-ohos-hdc.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="2207", MODE="0660", GROUP="plugdev"
这条规则覆盖所有 Rockchip(VID 2207)设备。用 id 确认自己在 plugdev 组里,有就不需要 sudo:
id -nG | tr ' ' '\n' | grep plugdev
五、让 AI 写出你自己那份
上面的脚本就是这么来的。关键是别让 AI 从零猜,而是给它“底座 + 约束 + 验收标准”。
要喂给 AI 的四样东西:
- 底座:树内
upgrade_tool的完整路径(让它去读-h输出和实际命令集) - 产物:镜像目录的绝对路径(让它
ls一遍,确认有哪些.img) - 分区表:
parameter.txt的内容(分区名、大小、偏移) - 约束:安全默认、模式分级、必须留日志
可直接用的 prompt 模板:
基于树内自带的 upgrade_tool(<绝对路径>),给我写一个 RK3576 的
OpenHarmony 烧录脚本,要求:
1. 先读 upgrade_tool -h 和实际产物目录 <镜像目录>,确认命令集和文件清单,
不要凭印象写命令;
2. 每一步都要匹配"期望输出子串"来自动判定成败,失败重试 3 次,
每步完整输出写进日志;
3. 分四种模式:repair(只烧 system+vendor)/ ohos / full(含 boot、loader、GPT、uboot)
/ all(含 userdata);
4. persist、nvdata1、nvdata2 存的是设备专属数据(校准、MAC/SN),
默认绝对不烧,只有显式指定才烧;
5. 提供 -c 只读查询模式和 DRY=1 只演算模式;
6. 烧录前要检查工具可执行、镜像目录存在、每个待烧镜像文件存在,缺任何一项就中止;
7. 从设备信息里解析 LocationID 时注意它可能是十六进制,用 hex 安全的写法;
8. 用 bash,路径顶部集中成变量,加中文注释。
AI 交付后,按这份清单验收(这步不能省):
-
bash -n语法检查通过 -
-c能列到设备,且 LocationID 解析正确(对着LD原始输出核一遍) -
DRY=1打印的命令序列顺序正确:loader → GPT → uboot → 各分区 -
PARTS里的分区名与parameter.txt逐字一致 -
persist/nvdata*/userdata不在默认列表里 - 每个
step都有期望子串和重试
六、烧完怎么证明烧对了
脚本跑完屏幕全是 ok,但真问题是:怎么知道烧进去的就是刚编译的那份?
不要相信构建目录,直接从 .img 里把文件掏出来比哈希。debugfs 不需要 root、不需要挂载:
# 从 vendor.img 里取出 libais_native.z.so
debugfs -R "dump /lib64/libais_native.z.so /tmp/vendor_libais.so" vendor.img
# 从 system.img 里取出 SELinux 策略
debugfs -R "dump /etc/selinux/targeted/policy/policy.31 /tmp/system_policy.31" system.img
sha256sum /tmp/vendor_libais.so out/rockchip/packages/phone/vendor/lib64/libais_native.z.so
sha256sum /tmp/system_policy.31 out/rockchip/packages/phone/system/etc/selinux/targeted/policy/policy.31
结果:
vendor.img 内提取 : 80df1678a43d0ae10d78119ba127607529ddbceab8525813e90ff88b04e78a32
构建产物 : 80df1678a43d0ae10d78119ba127607529ddbceab8525813e90ff88b04e78a32
烧录后再在设备上复核一次,三段链路(编译产物 → 镜像 → 设备)全对上:
hdc shell "md5sum /vendor/lib64/libais_native.z.so"
hdc shell "md5sum /system/etc/selinux/targeted/policy/policy.31"
这一步把“烧录成功”从主观感觉变成了可复现的哈希。以后谁问“板子上跑的是哪个版本”,一句话就能答。
七、排错速查
| 现象 | 原因 | 处理 |
|---|---|---|
未发现 rockusb 设备 | 板子不在 Loader/MaskROM | hdc shell "reboot loader",或按住 RECOVERY 键上电 |
Upgrade loader ok 一直不匹配 | 工具版本太旧,不支持该 SoC | 换 rktools/flashtool/ 那版(v2.55) |
DI -<分区> 报找不到分区 | 分区名与 GPT 不一致(sys-prod vs sys_prod) | 对着 parameter.txt 逐字核对 |
| 烧完起不来 / 分区错位 | GTP 和分区不同源 | 用同一套产物的 parameter.txt + 全量重烧 |
| 权限拒绝 / 需要 root | 没有 udev 规则或不在 plugdev 组 | 查 /etc/udev/rules.d/,或临时用 sudo |
| 每次都要手工确认 | 没写期望输出匹配 | 用本文 run / step 的写法 |
八、一句话
烧录脚本应该被当成要交给别人 review 的代码来写,而不是“我自己敲一遍的命令记录”。
而 AI 在这件事里最值钱的不是“替你敲命令”,是帮你想清楚“怎么证明这一步真的成了”——期望输出匹配、安全默认、哈希闭环,都是这个问题的答案。
本文实践环境:Rockchip RK3576 EVB1 V13 / OpenHarmony 6.1.0.31 / Upgrade Tool v2.55。
更多推荐


所有评论(0)