瑞芯微RK3588应用程序开发全流程记录

起因

手头的项目是 AI 视觉识别:摄像头进画面,板上实时跑模型推理,输出检测/分类结果。这类应用对算力的要求远超普通 MCU 和入门级 SoC——CPU 软跑 YOLO 这类模型只有个位数帧率,没有实用价值,必须上 NPU。

RK3588 是国产 SoC 里做端侧 AI 推理的主流选择:6 TOPS NPU、大小核 A76+A55 八核、Mali-G610 GPU、8K 硬编解码,配合 RKNN 工具链(PC 上 rknn-toolkit2 把 ONNX/PyTorch 模型转成 RKNN 格式,板上 rknnrt 运行时执行推理)能把主流视觉模型推到实时帧率,板卡生态和中文资料也最成熟。

但跑通模型只是一半:视觉识别要变成产品,还有一整条工程链——开发环境、内核与设备树定制、烧录、应用固化、开机自启动、启动画面、显示、GPIO、各类输入。本篇把这条嵌入式 Linux 完整生命周期逐环节过一遍,内容以瑞芯微官方 Developer Guide 和 Linux SDK 的公开惯例为准,落地后逐节把"惯例"替换成"实测"。

需求

流水线覆盖嵌入式 Linux 产品的完整生命周期:

  1. 虚拟机开发环境建设
  2. SDK 编译体系与编译选项配置修改
  3. 设备树(dts)配置修改
  4. 编译与打包
  5. 烧录固件
  6. 固化应用(打进固件,而不是每次 adb push)
  7. 应用自启动
  8. 修改启动图片与启动声音
  9. 修改显示驱动相关配置
  10. GPIO 控制
  11. 输入设备(USB / SD / KEY)

选型:RK3588 是什么量级

模块 规格
CPU 4×A76 @2.4G + 4×A55 @1.8G(大小核)
GPU Mali-G610 MP4
NPU 6 TOPS,RKNN 工具链(PC 端 rknn-toolkit2 转模型 + 板端 rknnrt 执行)
视频 8K 硬编解码(RKMPP),多路 MIPI 摄像头输入
SDK buildroot / Debian / Yocto 三套,build.sh 驱动
烧录 RKDevTool / upgrade_tool / rkdeveloptool
调试串口 ttyFIQ0,默认 1500000(后面有坑)
显示框架 DRM/KMS
AD 按键 SARADC + adc-keys

生态一句话:一个 build.sh 串三套 rootfs 体系(buildroot 精简、Debian 通用、Yocto 严格),工具链标准化程度高;但闭源组件(GPU、NPU、硬件编解码)只随厂商 SDK 发布——用主线内核/Armbian 会失去 Mali、RKNN、RKMPP 这些关键能力,做 AI 视觉产品必须走 Rockchip 原厂 SDK 或板卡厂商配套 SDK,NPU 的 rknnrt 运行库也在这一包里。

板卡生态丰富,常见的:官方 EVB、Firefly(ROC-RK3588S-PC / ITX-3588J)、腾讯 Toybrick TB-RK3588X、野火鲁班猫、Radxa Rock 5B、Orange Pi 5。SDK 优先从 FAE / 板卡厂商处获取配套版本。

虚拟机开发环境建设

  • Ubuntu(SDK 推荐 LTS 版本,buildroot 对主机 glibc 有要求;太新的发行版容易在 host 工具上翻车)
  • 磁盘 100GB+(kernel + buildroot + 预编译工具链全量解压后 40GB 级,编译输出还会继续膨胀)
  • 内存 16GB 起——8 核 SoC 的内核 + buildroot rootfs 并行编译很吃内存,8G 容易 OOM
  • 先补齐编译依赖:sudo apt install build-essential git bc bison flex libncurses5-dev libssl-dev zlib1g-dev gawk gettext ccache

源码经共享目录 /mnt/share 只做中转,编译拷回 ext4 本地进行(hgfs 共享目录不支持符号链接、没有真实权限位,而 buildroot 里全是指向 host 工具的软链,直接在共享目录里编必出玄学错误)。

编译体系与编译选项

build.sh:一个脚本串起全流程

瑞芯微 SDK 的所有操作集中在一个 build.sh 上:

1
2
3
4
5
./build.sh lunch        # 列出板级配置,选择 rk3588_xxx_defconfig
./build.sh              # 一键全编:uboot + kernel + rootfs + 打包
./build.sh kernel       # 单独编内核
./build.sh uboot        # 单独编 bootloader
./build.sh updateimg    # 把分区镜像组装成整包 update.img

lunch 选中的是 device/rockchip/rk3588/ 下的板级 defconfig,里面是一组 RK_ 开头的变量——RK_UBOOT_DEFCONFIGRK_KERNEL_DTSRK_ROOTFS_SYSTEM(buildroot/debian/yocto 三选一)等。这块板"用哪份 dts、哪套 rootfs"全由它决定,自建板子的第一步就是复制一份最接近的板级 defconfig 改名。

编译选项配置修改

三层配置,各管一段:

内核配置kernel/arch/arm64/configs/rk3588_linux_defconfig。官方推荐直接改 defconfig,或手动流程:

1
2
3
4
5
cd kernel
make ARCH=arm64 rk3588_linux_defconfig
make ARCH=arm64 menuconfig     # 改完 Save
make ARCH=arm64 savedefconfig  # 增量回写
cp defconfig arch/arm64/configs/rk3588_linux_defconfig

注意:改散落的 .config 会被下次构建覆盖,只有落回 defconfig 的改动才持久

buildroot 配置buildroot/configs/<板卡>_defconfig 决定 rootfs 里的包(alsa、libgpiod、你的应用),menuconfig 改完同样要 savedefconfig 回写(具体入口以手头 SDK 的 Developer Guide 为准)。

uboot 配置u-boot/configs/rk3588_defconfig,一般不动,动到的场景多是启动 logo、串口参数这类。

设备树(dts)配置修改

dts 在哪、怎么生效

1
kernel/arch/arm64/boot/dts/rockchip/rk3588-<板名>.dts

由板级 defconfig 的 RK_KERNEL_DTS 变量指定。编译后 dtb 和内核一起打进 boot.img没有独立 dtb 分区、也不需要单独烧——改了 dts 就要重编内核、重新打包烧写。

运行时验证:

1
2
cat /proc/device-tree/model
ls /proc/device-tree/

常改的节点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 串口:RK3588 调试口走 fiq_debugger,普通串口 uart0-9 */
&uart3 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart3m0_xfer>;
};

/* I2C:触摸屏、Codec 挂载 */
&i2c4 {
    status = "okay";
    touchscreen@14 {
        compatible = "...";
        reg = <0x14>;
    };
};

/* PWM 背光:brightness-levels 数组 + default-brightness-level */
&pwm14 {
    status = "okay";
};

/* SD 卡:插拔检测 */
&sdmmc {
    cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>;
    status = "okay";
};

引脚引用是瑞芯微风格:<&gpioN RK_Pxy GPIO_ACTIVE_*>RK_PB5 即 bank B 第 5 脚。pinctrl 子系统的行为:外设驱动 probe 时向它申请引脚,被别的节点占用即失败——引脚不工作先查 status 是否 okay、引脚分配和原理图是否对得上。

编译与打包

./build.sh 完成后产物在 output/firmware/(或 rockdev/,随 SDK 版本):

镜像 内容
uboot.img bootloader(含 SPL)
boot.img 内核 + dtb
resource.img 开机 logo(BMP)
recovery.img 恢复系统(做 OTA/恢复才需要)
rootfs.img 根文件系统
oem.img / userdata.img 厂商资源分区 / 用户数据分区
update.img ./build.sh updateimg 生成的整包刷机固件

分区表在 parameter.txt:一行 mtdparts 风格的布局描述,定义 uboot/boot/rootfs 各分区的起始扇区和大小。rootfs 装不下加东西时,改这里 + 重新打包。

烧录固件

日常主流程不变:选板(build.sh lunch)→ 编译(build.sh)→ 整包(updateimg)→ 烧录测试

工具选型

工具 性质 说明
RKDevTool 官方,Windows 图形界面,最常用
upgrade_tool 官方,Linux 命令行,官方 Linux 烧写
rkdeveloptool 开源社区 跨平台,命令行脚本化友好,批量烧写首选
1
2
3
4
5
# rkdeveloptool 常用命令
rkdeveloptool ld                     # 列出设备(Loader/MaskRom 状态)
rkdeveloptool uf update.img          # 烧整包
rkdeveloptool wl 0x4000 boot.img     # 按分区烧(起始扇区查 parameter.txt)
rkdeveloptool rd                     # 复位重启

Loader 与 MaskRom:两种烧写模式

  • Loader 模式:板子上跑着可用的 bootloader,从它进入下载态。进法:uboot 命令行输入 reset(部分板)、系统里 adb reboot loader、或按住 RECOVERY/VOL+ 键上电
  • MaskRom 模式:BootROM 里固化的一段 USB 下载协议,eMMC 为空、bootloader 损坏也能进,是救砖的最后防线。进法:按住 MASKROM 键上电,或短接 eMMC CLK 测试点

分不清当前在哪个模式:rkdeveloptool ld 会显示 LoaderMaskRom

开发节奏:push 循环优先

按改动层级选更新方式,是整个生命周期里最重要的节奏:

改动类型 更新方式 耗时
应用代码 adb push app /tmp/ && /tmp/app 秒级
资源/配置(logo、音频、脚本) adb push / 单分区烧写 秒~分钟级
内核、dts、rootfs 布局、分区表 ./build.sh && ./build.sh updateimg 整包烧录 十分钟级

RK3588 的 Linux SDK 默认起了 adbd(USB gadget),adb reboot loader 还能把"重启进烧写模式"和 push 迭代串成一条命令流水,全程不用碰按键。

SD 启动卡

官方 SD_Firmware_Tool 把 update.img 写进 SD 卡做成启动卡,板子拨到 SD 启动优先即可——适合不动 eMMC 做验证和批量老化。

固化应用

rootfs 体系不同,固化路径随 RK_ROOTFS_SYSTEM 分岔:

buildroot(产品固件常用)

  • 正规做法:buildroot 包——package/<你的应用>/ 下放 Config.in + Makefile(源码 tar 或 git 拉取,make 规则调交叉工具链),buildroot/configs 里选中 BR2_PACKAGE_你的应用,装进 rootfs
  • 快速做法:BR2_ROOTFS_OVERLAY 指一个 overlay 目录,二进制和启动脚本直接按目标路径摆进去

Debian(开发/通用场景):走 rootfs 定制脚本(Debian rootfs 是 mk-rootfs 一类脚本在 PC 上 chgroup/debootstrap 出来的),把应用和 systemd 单元写进脚本;也可以 apt 打私有源。

交叉编译器在 prebuilts/ 下(aarch64-buildroot-linux-gnu- 前缀):

1
2
prebuilts/gcc/linux-x86/aarch64/gcc/bin/aarch64-buildroot-linux-gnu-gcc \
    -o app_launcher main.c -lm

应用自启动

buildroot(busybox init)/etc/init.d/S99app_launcher,序号排最后保证 DRM 设备、声卡、input 都就绪后再起应用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh
case "$1" in
  start)
    cd /usr/share/app_launcher
    ./app_launcher &
    ;;
  stop)
    killall app_launcher
    ;;
esac

Debian(systemd):写 unit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Unit]
Description=App Launcher
After=multi-user.target

[Service]
ExecStart=/usr/share/app_launcher/app_launcher
Restart=always

[Install]
WantedBy=multi-user.target

systemctl enable 后开机自启。图形应用注意:RK3588 上 /dev/fb0 是 DRM 的 fbcon 兼容层,不是原生 framebuffer,Qt 程序 QT_QPA_PLATFORM=linuxfb 能跑,但正式产品建议直接走 DRM/KMS 或 Wayland,才能吃到多层合成与 GPU——对视觉识别应用来说,视频层 + UI 层 + 检测框叠加正是 DRM 多平面合成的用武之地。

修改启动图片和启动声音

启动画面分三层,瑞芯微的实现机制如下:

1. bootloader logoresource.img 里的 logo.bmp,uboot 阶段显示。换图:替换 logo.bmp 后重新生成 resource.img 打包;调试期也可以只烧 resource 分区(rkdeveloptool 单分区写入),不必整包。

2. 内核 logoresource.img 里的 logo_kernel.bmp,由内核 fbcon 显示。产品一般把它做成纯黑,衔接 bootloader logo 和应用首帧,避免闪企鹅/花屏。

3. 开机动画与开机音:SDK 的 bootanimation 组件(动画 zip + 配套音频,放 rootfs 指定目录,具体路径随 SDK 版本);最稳的做法仍然是自启动脚本里 aplay 播开机音后拉起主程序:

1
2
amixer cset numid=1 60
aplay /usr/share/sounds/boot.wav &

RK3588 音频走 ALSA(codec 挂 I2S,dts 里 rk809-codec/es8388 之类),aplay -l 先确认 card 设备号再写脚本。

修改显示驱动相关配置

RK3588 的显示框架是 DRM/KMS(不是传统 framebuffer):多视频端口 + 多输出控制器 + 多图层合成,对视觉识别产品(视频层叠 UI、检测框)是刚需能力。

dts 侧

RK3588 显示子系统是"4 个视频端口(vp0-vp3)+ 多种输出控制器(2×HDMI、2×MIPI DSI、eDP、LVDS)“的矩阵,dts 里要指定哪个 vp 接哪个控制器、什么时序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* HDMI 输出示例 */
&hdmi0 {
    status = "okay";
};
&hdptxphy0 {
    status = "okay";
};

&route_hdmi0 {
    status = "okay";
    connect = <&vp0>;
};

MIPI 屏则是 dsi 节点 + panel 子节点(panel-init-sequence 一串 DCS 初始化命令,屏厂 datasheet 给多少抄多少)+ backlight PWM。

调试

1
2
3
cat /sys/kernel/debug/dri/0/summary    # RK 显示第一现场:各 vp/控制器/时序状态
modetest -M rockchip -c                # 列 connector/crtc/encoder
cat /sys/class/drm/card0-HDMI-A-1/status   # 接口热插拔状态

dri/0/summary 是 RK 排显示问题的招牌命令——时序对不对、控制器挂没挂、vp 有没有分配,一眼全出来。黑屏排查顺序:summary → 电源/背光量波形 → panel init 序列。

GPIO

用户空间访问 GPIO 用 libgpiod(gpiodetect / gpioinfo / gpioset),编号规则:

  • 5 个 bank:gpiochip0gpiochip4(GPIO0GPIO4),每 bank 32 线
  • 组内:A0-A7=0-7,B0-B7=8-15,C=16-23,D=24-31
  • 全局线号 = bank×32 + 组内偏移,如 GPIO1_B5 = 32+8+5 = 45
1
2
3
gpiodetect
gpioinfo gpiochip1
gpioset gpiochip1 13=1     # GPIO1_B5 拉高

dts 里被 pinctrl 声明占用的脚无法再从用户空间申请(gpioinfo 的 consumer 字段可查占用者);LED/继电器大电流负载一律加三极管或驱动芯片。

输入:USB / SD / KEY

USB

RK3588 的 USB3.1 控制器是 dwc3,host/device 双 role。U 盘热插拔:插上后枚举出 /dev/sda1,mdev/udev 负责自动挂载;adb push 迭代应用。USB3 的读写带宽足够 4K 视频素材直读,视觉产品的素材盘、模型盘都够用。

SD 卡

sdmmc 节点 + cd-gpios 插拔检测,设备 /dev/mmcblk1p1。两个用途:数据卡(模型文件、媒体资源库)和启动卡(SD_Firmware_Tool 制作)。

KEY(按键)

GPIO 直连:dts 配 gpio-keys,内核生成 /dev/input/event*evtest 验证:

1
2
3
4
5
6
7
8
gpio-keys {
    compatible = "gpio-keys";
    key-back {
        label = "back";
        linux,code = <158>;
        gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
    };
};

SARADC 按键(AD 分压按键):一根 AD 脚挂一串不同阻值的分压电阻,一个电压区间对应一个键,省 IO。dts 用 adc-keys 节点 + io-channels = <&saradc> + 毫伏/键值表。

用户空间读法:read() /dev/input/event*struct input_eventEV_KEY + value 1/0 分按下/抬起。前提是内核开了 Event interface(CONFIG_INPUT_EVDEV),否则连 /dev/input/event* 都不会出现。

踩坑记录与注意事项(预研版)

以下是按公开资料预判的高频坑,落地时逐条验证替换为实测:

  • 串口 1500000:RK 默认调试口是 ttyFIQ0 @ 1500000,很多 USB 转串口不支持这个波特率——买串口线先确认;连不上串口先怀疑波特率,不是驱动
  • 主线内核陷阱:Armbian/主线能点亮系统,但 Mali GPU、RKNN NPU、RKMPP 硬编解是闭源组件,只随厂商 SDK 发布;做产品老老实实用原厂 SDK
  • .config 不生效:内核/buildroot 配置必须 savedefconfig 落回各自 defconfig,散改会被下次构建覆盖
  • 烧不进去分不清状态rkdeveloptool ld 看是 Loader 还是 MaskRom;两都不是就按住按键重新上电
  • uboot 损坏救砖:MaskRom 模式 + 烧 uboot.img(eMMC CLK 测试点短接进法提前在图纸上标好)
  • 单分区烧错扇区wl 的起始扇区必须查 parameter.txt 对应分区,抄错分区表会互相覆盖
  • 黑屏先看 summary/sys/kernel/debug/dri/0/summary 一条顶十条,先确认 vp/控制器/时序再量硬件
  • fb0 语义差异:RK3588 的 fb0 是 DRM 兼容层,不是原生 fb;从其他 fb 平台移植来的应用(DMA 直写、多 buffer)行为可能不同,重活建议迁 DRM
  • buildroot 编译机内存不足:8G 内存 -j8 会 OOM,加 swap 或减并发
  • 一切启动问题先看串口:1500000 的 log 从 BootROM 一路打到应用,是唯一全链路现场