☰
RK3588 U-Boot设备树定制指南:改动到生效完整流程
2026/10/2 4:02:22 网站建设 项目流程

1. 为什么偏偏要改U-Boot的设备树

拿到一块RK3588开发板,上电、跑demo、看log,大多数人第一步都是在内核里折腾。但玩过几个项目之后你会发现,真正卡你一个星期的地方,往往不在内核,而在更早的U-Boot阶段。尤其是当你需要换调试串口、调显示分辨率、控一组GPIO去点灯或者拉风扇,甚至只是想让某个外设在开机logo阶段就完成初始化时,改内核设备树根本不顶用——U-Boot阶段根本还没走到那一步。这时候,U-Boot自己的设备树文件就成了绕不开的关卡。

设备树(Device Tree)本质上是一份描述硬件资源的数据结构,从U-Boot到内核,它一路传递硬件信息。RK3588这套方案里,U-Boot里也有一份完整的设备树,用来在启动最早期初始化DDR、时钟、串口、显示、电源域等关键资源。简单说,内核设备树管的是Linux起来之后的设备,而U-Boot设备树管的是Linux起来之前的那些硬件。很多使用RK3588做AI视觉、机器人、工业控制项目的朋友,都会卡在“设备树明明改了,但U-Boot阶段没反应”这种问题上,本质上就是没分清楚自己在改哪一份。

这篇文章适合正在做RK3588相关嵌入式开发、接触到U-Boot阶段定制需求、或者被启动早期外设问题折磨的开发者。我会尽量把从源码定位、设备树语法、编译打包到烧录验证的完整链路讲清楚,并且把我在实际项目里踩过的坑、排查过的疑难杂症一并整理出来。不同基础的读者都能从这里找到可以直接抄的配置方法。

2. 动手前的准备工作:源码定位、工具链与备份

2.1 从SDK里找到U-Boot设备树文件

RK3588的SDK通常叫rk3588_linux_sdk,解压之后U-Boot代码在u-boot目录下,设备树源码则统一放在u-boot/arch/arm/dts/。你打开这个目录会发现,里面有大量文件名带rk3588的dts/dtsi文件,比如rk3588-evb.dts、rk3588-evb1-lp4-v10.dts、rk3588s.dtsi等等。

这里有个特别容易看花眼的地方:RK3588的U-Boot设备树并不是只有板级文件,而是由多个dtsi一层层包含起来的。最常见的是下面这个继承链:

rk3588-evb.dts ├── rk3588s.dtsi # SoC级通用定义,包含所有外设控制器节点 │ ├── rk3588s-pinctrl.dtsi # 所有引脚复用定义 │ ├── rk3588s-clk.dtsi # 时钟树定义 │ └── rk3588s-gpu.dtsi # GPU相关节点 ├── rk3588-evb.dtsi # 板级通用外设差异定义 └── rk3588-linux.dtsi # Linux内核启动相关定义

命名规则上,rk3588s代表的是RK3588S芯片版本(少了一些接口的裁剪版),evb是官方评估板的缩写,后面带lp4、v10、v11的一般是具体硬件版本和内存颗粒类型。如果你用的是第三方核心板,板级文件往往是从官方的rk3588-evb.dts复制过去改的。那么问题来了:你第一件事就需要确认自己的开发板到底对应哪个dts文件,然后在自己的板级dts(而不是dtsi)里做修改。这一点极其重要,改到dtsi里虽然也可能生效,但多人协作、SDK升级时很容易冲突,而且后期维护会非常痛苦。

2.2 编译工具链与U-Boot编译流程

RK3588 U-Boot的编译不像普通U-Boot那样用make rk3588_evb_defconfig就完了,瑞芯微把整个流程封了一层,官方推荐在SDK顶层目录下直接编译。如果只想编译U-Boot,SDK里也更推荐在u-boot目录用自带的make.sh,大致流程是:

cd u-boot ./make.sh rk3588 -j$(nproc)

执行完以后,会生成uboot.img、trust.img、rk3588_loader_all.bin这些固件。注意,uboot.img并不是单纯的U-Boot ELF,而是打包了SPL、TPL、DDR初始化代码和U-Boot主程序的镜像。这也是瑞芯微方案的特殊之处——它的DDR初始化、Trusted Firmware都不在U-Boot主程序里,而是由trust.img等镜像管理。

所以这里有个非常容易踩的认知误区:你只改了设备树,但烧录时没把uboot.img重新编出来并下载到开发板上,那一切修改都不会生效。我见过不止一个同事改了设备树源码,只跑了一下dtc生成了dtb,然后直接拿去烧,结果上电发现U-Boot仍然用旧配置。原因很简单:U-Boot的设备树会被打包进uboot.img,而不是像内核那样单独分成一个boot.img里的dtb分区。

还有一点需要提醒:编译前确保SDK里已经设置好交叉编译工具链路径。官方SDK一般自带或通过source envsetup.sh加载环境变量。如果你是自己搭的环境,至少需要aarch64的裸机或Linux交叉工具链,建议直接用预编译好的工具链,别花时间自己编译。

2.3 烧录前的备份策略,不要等变砖了再后悔

在开始改任何文件之前,先把当前开发板上能跑固件完整备份出来。瑞芯微方案的烧录和备份方式相对成熟,一般有两种:

一种是用Windows下的RKDevTool,在“高级功能”里可以按分区读取固件,把当前eMMC里的uboot、boot、misc、vendor等分区全部备份出来。另一种是Linux下用upgrade_tool,命令大致是:

upgrade_tool rd # 读取设备信息 upgrade_tool rl # 备份整个eMMC镜像

如果你用的开发板支持SD卡启动,那备份更容易:把当前eMMC上跑的系统做成SD卡启动镜像,或者至少确保手里有出厂官方的完整固件包。备份的意义我在项目里体会极深:改U-Boot设备树不是每次都能一次过,一旦改错导致U-Boot起不来,如果手里有原始固件,用Maskrom模式几分钟就能救回来;如果没有备份,可能就得返厂烧写了,周期少则两三天,项目直接卡住。

提示:操作前请务必确认开发板的烧录模式。RK3588一般按住BOOT键再上电进入Loader模式,如果U-Boot已经被你改挂,则进入Maskrom模式。不同开发板操作方法会有细微差异,建议先查自己板子的手册。

3. 核心实操:常见需求下设备树改法与重编译

3.1 修改调试串口,让log从另外一路UART出来

很多RK3588项目为了调试方便,会把调试串口从默认的UART2换到其他串口,或者主板设计上明明用了UART3做log却想通过U-Boot设备树改过来。U-Boot阶段的串口输出由两部分决定:一是chosen节点里的stdout-path,二是在dtsi里定义好的串口控制器以及pinctrl。

在U-Boot设备树里,常见的串口节点定义是这种风格:

&uart2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart2m0_xfer>; }; &sdmmc { status = "okay"; };

要把默认log换到UART3,最稳妥的做法是修改板级dts里的chosen节点和对应的uart节点,类似:

/ { chosen { stdout-path = "uart3:1500000n8"; }; }; &uart3 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart3m1_xfer>; };

注意这里的uart3m1_xfer不是随便写的,它表示UART3使用m1组引脚复用。具体用哪一组引脚,取决于你的电路板上UART3接到了哪几个物理引脚。引脚复用组在rk3588s-pinctrl.dtsi里都有定义,建议先搜索确认。

这里有个容易被忽略的点:修改串口时,如果只改了chosen节点而对应的UART3没有配置status = "okay",U-Boot会打印不出来任何log;而如果UART3的pinctrl配置错了,比如针脚被复用成了GPIO或者I2C,log会直接消失。而且这两类问题在过程中都很难通过看屏幕发现,因为屏幕压根没输出。我的排查经验是:改用示波器或者逻辑分析仪抓串口引脚上的波形,或者对照原厂的硬件原理图,确认引脚复用的组号正确。

3.2 显示分辨率和U-Boot logo调整

项目里使用RK3588做AI视觉或边缘计算网关时,经常需要接HDMI或者LVDS屏幕,在U-Boot阶段就要显示logo或调试信息。瑞芯微方案里,U-Boot的显示逻辑由设备树中的显示相关节点决定,常见有display-timings、route_hdmi、route_dsi、route_edp等。

如果只是改分辨率,核心是确保display-timings里设置的时序参数与屏体的规格一致。常见配置大概长这样:

&route_hdmi { status = "okay"; connect = <&hdmi>; }; &hdmi { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&hdmim1_tx0_cec &hdmim1_tx0_sda &hdmim1_tx0_scl &hdmim1_tx1_cec &hdmim1_tx1_sda &hdmim1_tx1_scl>; }; &hdmi_in_vp0 { status = "okay"; }; &video_phy0 { status = "okay"; }; &display_timing { clock-frequency = <148500000>; // 1080p hactive = <1920>; vactive = <1080>; hfront-porch = <88>; hsync-len = <44>; hback-porch = <148>; vfront-porch = <4>; vsync-len = <5>; vback-porch = <36>; hsync-active = <0>; vsync-active = <0>; de-active = <0>; pixelclk-active = <0>; };

注意,RK3588的显示链路很复杂,包含视频端口、video_phy、HDMI控制器、路由选择等多个环节,光改display-timings不一定能生效,还得保证route和对应的video_port节点处于okay状态。更麻烦的是,瑞芯微不同版本的SDK在显示设备树结构上并不完全一致,有的版本用&hdmi、&video_phy0,有的版本用&hdmi_phy之类命名,所以建议先在dtsi里把现有节点结构摸清楚再动手。

另外一个隐藏很深的问题:logo图片资源其实不在U-Boot设备树里,而是打包在resource.img里,对应的是内核的logo_kernel.bmp、logo.bmp这些图片。U-Boot设备树只决定显示接口和时序,logo图片本身则决定显示内容。如果你改了分辨率但logo花屏或者比例不对,多半是图片分辨率与显示分辨率不匹配,得重新生成resource.img,而不是继续改设备树。

3.3 在U-Boot阶段控制GPIO,点亮LED或拉起风扇

RK3588做工业产品时经常有这种需求:开机瞬间就要点亮一个电源指示灯,或者U-Boot阶段就把散热风扇转起来,避免后面内核起来太慢导致温度过高。这些功能都需要在U-Boot设备树里做好GPIO的初始化。

设备树层面,需要确认GPIO引脚没有被其他外设复用,并且在pinctrl中正确配置。在实际操作中,我很少只依赖设备树去完成U-Boot阶段的GPIO控制,因为U-Boot的设备树主要管“初始状态”,真正动态控制靠的是U-Boot命令行或代码。例如,你可以在U-Boot命令行下用:

# 查看GPIO分组状态 gpio status # 将GPIO4_B3设置为输出并拉高 gpio set 4-3-1

这个4-3-1的含义是bank4、groupB、index3、输出高电平。要解释一下:RK3588的GPIO分组是GPIO0到GPIO4,每个bank有A/B/C/D四组,每组有8个引脚。如果你在设备树里看到的某个引脚是RK_GPIO4 | RK_PB3 | GPIO_ACTIVE_HIGH,那在U-Boot命令行里对应的编号就是4-3-1。

更大的坑是引脚复用冲突。RK3588很多引脚默认会被dtsi里的某个外设节点占用,比如I2C、UART、PWM。如果你直接用gpio set去操作一个已经被复用的引脚,往往没有任何反应。排查时优先使用gpio status确认引脚当前是否被复用为GPIO。如果发现被其他外设占用,就要在板级dts里把那个外设节点置为disabled,例如:

&i2c3 { status = "disabled"; };

然后再确认引脚组是否被设定为GPIO功能。你可以在pinctrl相关节点里查看是否有对应的gpio配置。大多数情况下,一个引脚只要没被其他外设占用,默认就是GPIO功能。

另外,调试时建议把GPIO的初始状态放在U-Boot环境变量里,通过bootcmd设置,而不是每次手工敲。例如在U-Boot的默认环境变量中加入一行让风扇在启动时立刻打开,会稳定很多。这个控制需要在board代码里实现,但设备树的正确配置是前提,别跳过。

3.4 修改启动顺序与分区相关配置

RK3588从eMMC、SD卡还是U盘启动,虽然主要由U-Boot环境变量和启动源决定,但设备树里也有些间接影响。比如mmc节点、sdmmc节点和eMMC节点的状态,决定了U-Boot阶段能不能正确识别这些存储介质。

我在实际项目里遇到过这样一种情况:把SD卡启动功能打开后,U-Boot死活不识别我的SD卡。查了半天,最后发现是SD卡接口的pinctrl配置在板级dts里被屏蔽了。补上之后:

&sdmmc { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&sdmmc_clk &sdmmc_cmd &sdmmc_bus4>; bus-width = <4>; max-frequency = <150000000>; };

U-Boot就能正常识别SD卡了。另一个容易忽略的问题是电压域配置。如果你的板子SD卡供电是3.3V,但设备树里配了一个外部io-domain节点,并且电压域不匹配,会导致识别不稳定,甚至烧卡。RK3588的电源域管理比较细致,改存储相关节点时务必对照原理图检查io-domain的配置。

启动顺序方面,用fw_setenv或直接在/etc/u-boot/env(部分SDK)里修改boot_targets环境变量会直接得多,比如把SD卡启动优先级提前:

boot_targets=mmc1 mmc0 usb0 pxe dhcp

这里的mmc1对应SD卡,mmc0对应eMMC。设备树主要负责“硬件能不能被正确初始化”,启动顺序则是另一个维度,两件事别混在一起排查。

4. 踩坑实录:我改坏过的那些地方

4.1 设备树编译报错:语法问题、节点重复与标签未定义

改设备树最常见的报错,其实跟硬件关系不大,而是语法和参考问题。拿我自己的经历来说,刚接触设备树时最常犯的错就是漏掉分号、注释嵌套错误、还有节点属性拼写错误。设备树语法本身不算复杂,但RK3588的dtsi文件很大,嵌套层级深,一旦少一个符号,编译报错信息往往指向一个很靠后的位置,排查起来非常恼火。

比较典型的两类编译错误:

Error: arch/arm/dts/rk3588-evb.dts:123.1-3 syntax error FATAL ERROR: Syntax error parsing input tree

这种一般是少了分号或者括号不匹配。另一个常见的是:

Error: arch/arm/dts/rk3588-evb.dts:87.3-6 Label or path not found

说明你引用的某个标签(如&uart2)在dtsi里不存在。原因可能是这个SoC版本没有该外设,也可能是你拼写错了。比如RK3588的UART3有m0、m1、m2等多组引脚复用,标签可能是uart3m0_xfer、uart3m1_xfer、uart3m2_xfer,少写一个m就找不到了。

排查这类问题时,别急着猜。用搜索工具在整个arch/arm/dts目录下搜一下标签定义,确认存在再写。也可以先用SDK自带的编译脚本对单独的dts文件做语法检查,不需要整包编译。比如:

make dtbs

单独编译dts会快很多,能大大缩短调试周期。

4.2 U-Boot编译通过了,但改动完全没生效

这个坑是我见过最多的情况,而且非常诡异。明明在板级dts里改了GPIO状态、串口配置,./make.sh rk3588也确实重新跑完了,烧录后U-Boot启动log却和之前一模一样。排除烧录不正确之后,最可能的原因就是:U-Boot编译时默认使用了多个设备树,而你改的那个dts根本没被编译进去。

RK3588的make.sh默认会为多种板型生成对应的dtb,并根据实际运行的板型自动匹配。如果你在rk3588-evb.dts里改了内容,但你的开发板在U-Boot启动时实际匹配到的是rk3588-evb1-lp4-v10.dtb,那就等于白改。确认当前板子用的是哪份dtb,可以在U-Boot log里搜索FDT或DTB相关输出,也可以烧录后在U-Boot命令行执行:

fdt addr

然后看打印出的内存地址对应哪个dtb文件,再回到源码里核对板级dts。很多时候,官方出厂固件默认匹配的板型和你以为的板型并不是同一个,尤其是第三方核心板移植出来的,DTS修改需要找到真正生效的那个文件。

一个我再三强调的经验:修改前,先把config文件里默认的DEFAULT_DTB或CONFIG_DEFAULT_DEVICE_TREE确认清楚。瑞芯微某些SDK里还会用CONFIG_SYS_CONFIG_NAME来决定编译哪个板级文件,搞错了就是改了个寂寞。

4.3 pinctrl冲突和GPIO复用,改了个寂寞

RK3588的引脚复用是出了名的复杂,一个引脚可能是UART、I2C、SPI、PWM、GPIO等好几种功能。设备树里如果两个节点同时引用同一个引脚,编译不一定报错,但实际运行时只有一个功能生效,另一个则静默失效。

我实际排查过的一个案例:想在U-Boot阶段通过GPIO0_B5控制一个外部复位芯片,但每次拉高之后很快又变成低电平。查来查去,发现这个引脚在dtsi里被默认配置为I2C2的SDA。虽然板级dts里没有显式打开I2C2,但I2C控制器的pinctrl配置还在,U-Boot初始化I2C时把这个引脚复用了。解决办法是在板级dts里把I2C2节点显式设为disabled:

&i2c2 { status = "disabled"; pinctrl-names = "default"; pinctrl-0 = <&i2c2m1_xfer>; };

注意,这里还要同时把pinctrl子项注释掉或者改为空。因为有的U-Boot版本即使节点disabled,pinctrl解析还是会执行。更稳妥的做法是把这个引脚在pinctrl里显式配置成GPIO功能,比如:

&pinctrl { gpio0_b5 { rockchip,pins = <0 RK_PB5 0 &pcfg_pull_none>; }; };

这里0 RK_PB5 0的含义是bank0、B组5号引脚、功能mux为0(即GPIO功能)。这种三层括号的写法初看很劝退,但理解了含义之后就清楚多了:第一层是bank,第二层是引脚位置,第三层是复用功能号。

排查引脚冲突时,最实用的方法是检索dtsi里同一个引脚被引用了几次:

grep -R "RK_PB5" arch/arm/dts/

看输出结果,凡是带1、2这些非零功能号的引用,都可能和你的GPIO配置存在竞争关系。

4.4 regulator和电源域配置错,启动直接卡在DDR初始化

RK3588的电源管理非常“敏感”,设备树里的regulator节点如果配置不对,轻则某个外设不工作,重则卡死在DDR初始化或TF-A阶段。这个阶段屏幕通常还没有任何输出,排查起来非常痛苦。

我经历过一次改错vdd_cpu_big的regulator节点,把输出电压范围写得太低,结果U-Boot反复重启。log里最后能看到的输出往往是“DDR Version”一行之后就没下文了。这类问题和设备树语法无关,纯粹是电源参数被改坏了。

所以我的建议是:在需要改regulator相关配置时,除非有明确的原理图依据,否则千万不要动电压范围、ramp rate这些参数。如果你只是想给某个外设断电或者调整供电策略,优先用regulator-boot-on、regulator-always-on这些标志位,而不要动电压数值。例如:

&vcc5v0_otg { regulator-boot-on; regulator-always-on; };

这样既能达到“上电就供电”的目的,又不会引入电压错误的风险。如果一定要改电压值,务必对照硬件原理图和官方datasheet双重确认。

另外,电源域节点和io-domain节点配置错误,也可能导致eMMC、SD卡、USB等外设在U-Boot阶段工作不稳定。这类问题很难直接定位,往往表现为“有时候启动正常,有时候启动卡死”。遇到这种情况,先别急着怀疑硬件,回头检查自己的设备树有没有动过io_domain相关的配置。

5. 调试设备树的几个实用工具与心得

5.1 用dtc反编译,确认编译结果和预期一致

编译生成的dtb是二进制格式,不方便直接查看。拿到U-Boot生成的uboot.img后,如果你想知道里面真正打包进去的设备树内容是啥,最可靠的办法是反编译。

瑞芯微的SDK里通常自带dtc工具,如果没有,可以用系统自带的:

dtc -I dtb -O dts -o uboot_release.dts uboot_release.dtb

反编译后,重点检查你修改的节点是否真的存在,比如chosen、uart3、gpio0_b5这些。这一步能帮你快速排除“改了但没编译进去”的问题。我习惯在每次烧录之前都做一次反编译确认,尤其是多人协作的项目,避免别人在你之后重新编译覆盖了你的修改。

5.2 在U-Boot命令行里用fdt命令即时验证

如果不方便反复烧录,U-Boot命令行本身也提供了fdt操作工具,可以在运行阶段实时查看和修改设备树内容。进入U-Boot命令行后:

# 将设备树加载到内存,假设之前在fdt_addr fdt addr $fdt_addr # 打印某个节点 fdt print /chosen # 修改某个属性,比如把stdout-path改成uart3 fdt set /chosen stdout-path "uart3:1500000n8"

fdt set修改的是当前内存里的设备树,重启后会丢失,但用于验证思路非常方便。比如你怀疑串口配置有问题,可以在U-Boot里先用fdt set把chosen改掉,再执行boot启动内核,看log是否从目标串口输出。如果有效,再回到源码里修改并重新编译固件。这套流程能省下大量反复烧录的时间。

还有个小技巧:fdt list、fdt get value都是很好用的查询命令。建议在改动前先把当前设备树的关键节点内容全部打印出来存档,这样对比起来一目了然。

5.3 维护一份自己板子的差异补丁

RK3588的官方SDK更新频率不低,第三方板卡厂商也会不定期升级SDK。如果你在板级dts上做了零零散散的修改,升级时很容易遗漏。我个人的习惯是:所有设备树改动都集中在一个单独的文件里,或者在板级dts里加上清晰的分区注释,并且用git管理。每次升级SDK前,先把自己的改动打成一个patch,升级后再重新apply。

举个例子,我在U-Boot设备树里加的配置会统一放在板级dts末尾,用以下格式分隔:

/* ---------- custom: fan & led ---------- */ / { fan_gpio { compatible = "gpio-leds"; status = "okay"; }; };

虽然这个语法不一定在所有SDK版本通用,但关键是把改动视觉隔离出来,后续维护不用到处找。别小看这个习惯,RK3588的SDK结构复杂,几个项目并行的时候,没有这个习惯早晚要在版本合并上翻车。

另外,RK3588这款芯片在AI视觉、SLAM类项目里应用特别广,很多人会同时改内核设备树(摄像头、ISP相关节点)和U-Boot设备树(显示、存储、调试口)。我的建议是内核和U-Boot两份设备树分开管理,各自个的分支或标签。毕竟它们用的文件路径、编译方式、打包脚本完全不一样,混在一起会让问题边界变得非常模糊。

6. 回顾一段真实调试经历

最后简单分享一个让我印象深刻的排查过程。某个RK3588项目里,客户反馈设备经常在低温环境下启动失败,概率性卡死在U-Boot阶段。一开始以为是硬件问题,查了电源、晶振、DDR,都没找到明确原因。后来反复对比设备树,发现板级dts里有一个SD卡接口的电源控制引脚被配置成了普通GPIO输出,但这个引脚的pinctrl同时被默认的pinctrl配置复用为其他功能。

低温环境下电源轨上升斜率变缓,SD卡检测引脚的状态不稳定,最终导致U-Boot在初始化SD卡时卡死。解决办法其实很简单:在板级dts里把这个引脚强制配置为GPIO功能并设置正确的上下拉:

&pinctrl { sdmmc_pwr_en { rockchip,pins = <4 RK_PA5 0 &pcfg_pull_none>; }; };

问题的根源完全在设备树的pinctrl配置,而不是硬件。这次调试让我深刻意识到,U-Boot设备树不是“随便改改就能行”的东西,它直接决定了RK3588上电后最早的几百毫秒里,硬件以什么状态运行。这几百毫秒虽然短,却往往是整个系统稳定性的基石。

如果你现在正在被RK3588的U-Boot设备树问题卡住,不妨按照这篇文章的顺序从头捋一遍:先确认自己改的dtb文件真的被编译进uboot.img,再确认pinctrl没有冲突,最后再深入排查电源域和regulator。这三步走完,大部分疑难杂症都能找到方向。尤其要记住:改U-Boot设备树和改内核设备树是两套完全不同的方法论,千万别用后者的思维去调试前者。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询