AI 帮我写了个 RK3576 烧录脚本:完整代码 + 5 个适配点 + 闭环验证

RK3576 手动烧一次要在 GUI 里点十几步。这篇给出一份 159 行的 bash 脚本,把它压成一条命令。
附:照抄必改的 5 处、让 AI 写出你自己那份的 prompt 模板、以及“烧进去的确实是这份产物”的验证方法。

![图 1:瑞芯微开发工具(RKDevTool v3.37)的「下载镜像」页,

img

手动烧录的日常:逐条加载十几个分区的固件路径、勾选、点「执行」、盯右侧日志窗肉眼看结果。换台机器路径全变,得重来一遍。

一、先看结果

脚本叫 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                   # 只打印命令,不执行

四种模式各烧什么:

模式烧录范围典型场景
repairsystem + 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(连字符) vs sys_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 的四样东西:

  1. 底座:树内 upgrade_tool 的完整路径(让它去读 -h 输出和实际命令集)
  2. 产物:镜像目录的绝对路径(让它 ls 一遍,确认有哪些 .img)
  3. 分区表:parameter.txt 的内容(分区名、大小、偏移)
  4. 约束:安全默认、模式分级、必须留日志

可直接用的 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/MaskROMhdc 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。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐