ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

OpenHarmony设备树DTS实战:从定位RK3568文件到GPIO调试全流程

OpenHarmony设备树DTS实战:从定位RK3568文件到GPIO调试全流程 做OpenHarmony系统适配和驱动开发的朋友对设备树DTS肯定不陌生。尤其是RK3568这颗芯片网上资料铺天盖地但真到动手那一刻很多新手就卡住了源码里有那么多dts文件到底哪个才是当前板子用的改完了一编译烧进去发现外设半点反应都没有问题到底出在哪这些坑我都踩过所以这篇教程不想讲太多虚的直接带你把“改DTS”这件事的完整链路走一遍——从文件定位、语法基础、实际修改到编译烧录、排查验证一步都不落。这篇文章适合正在做OpenHarmony系统移植、外设适配、或者单纯想搞懂“为什么这块板子能识别某个传感器而另一块不行”的开发者。不管你是刚接触内核的新人还是被设备树折磨过的老手都能从中找到可直接抄作业的方法。1. 设备树到底是什么改之前先把这几个概念对齐1.1 设备树在OpenHarmony里是怎么工作的设备树全称Device Tree是一种描述硬件资源的数据结构。它的核心作用是把“硬件长什么样”的信息从内核源码里剥离出来交给一份独立的、可读性很强的文本文件去描述。内核启动的时候会用这份文件去识别板子上有哪些器件、寄存器地址在哪里、中断号是多少、引脚接在了哪个GPIO上。在OpenHarmony体系里Kernel层用的还是Linux内核所以设备树的运行机制和标准Linux是一脉相承的。从上层应用到底层硬件大致是这样一条链路引导加载程序比如U-Boot在启动内核之前会把编译好的设备树二进制文件dtb加载到内存里把起始地址传给内核内核解析这段数据建立起“硬件设备—驱动—总线”的对应关系之后驱动才能通过匹配设备树节点的方式被正确加载。我拿一个生活化的例子来打比方设备树就好像是小区物业交房时给你的那份《房屋设施清单》上面写清楚了“客厅空调装在哪个墙、热水器用的是电还是燃气、插座是几孔位的”。搬进去之后维修工不需要满屋子乱找只要照着清单就能很快定位每一个设备。内核就是那个维修工设备树就是那份清单。1.2 dts、dtsi、dtb、dtc四个后缀别搞混很多新手第一次进到设备树目录看到一堆后缀完全不同的文件当场就懵了。这里我先把四个基本概念理顺dtsDevice Tree Source设备树源文件是真正描述硬件信息的文本文件。通常一个具体的开发板对应一个dts文件比如rk3568-evb.dts。dtsiDevice Tree Source Include设备树的“头文件”它存放的是可以被多个dts共同引用的公共部分。一般是SoC级别的定义比如rk3568.dtsi描述RK3568这颗芯片内部有哪些控制器、中断控制器、时钟、内部外设等还有板级公共定义比如rk3568-evb.dtsi描述某系列开发板的公共资源。dtbDevice Tree Blobdts源文件经过编译后生成的二进制文件这才是内核真正能吃进去的格式。烧录时放进boot镜像或resource镜像里的就是它。dtcDevice Tree Compiler设备树编译器负责把dts编译成dtb也可以反编译dtb。这个工具在Linux内核源码的scripts/dtc目录下OpenHarmony编译内核时会自动用到它。为什么要分dts和dtsi直接写一个大文件不香吗原因是复用性。同一颗RK3568芯片能被用在好几十款开发板上每块板子差异主要在内存大小、外设接口、GPIO分配上。如果每个板子都复制一份完整的芯片描述维护成本高到离谱。所以SoC芯片级的内容放在rk3568.dtsi板级差异放在rk3568-evb.dts这种文件里不同板子共用同一个dtsi各改各的dts这样才算合理的工程结构。注意在OpenHarmony源码中RK3568的内核版本通常是Linux 5.10设备树文件路径一般在kernel/linux/linux-5.10/arch/arm64/boot/dts/rockchip/下但不同发行版和版本号可能会略有差异先通过find命令确认一下最稳妥。2. 板级文件这么多RK3568的DTS究竟该怎么选2.1 从文件命名看懂板级差异很多人在OpenHarmony社区问过同一个问题arch/arm64/boot/dts/rockchip/目录下有几十个rk3568开头的文件到底该改哪一个要回答这个问题先看命名规律。RK3568相关文件通常分成两类一类叫rk3568.dtsi这是全芯片共用另一类是以开发板型号命名的比如rk3568-evb.dts、rk3568-evb1-ddr4-v10.dts、rk3568-rock-3a.dts等。文件名里的信息其实很直白evb表示评估板数字后缀表示硬件版本ddr4-lpddr4表示内存类型rock-3a这类是第三方开发板的专属型号。搞清楚这一点选文件的逻辑就很清晰了你的板子是哪块开发板或者是你自己画的板子硬件设计参考的是哪个官方方案就直接改对应板级的dts。如果你用的是第三方板子如香橙派、友善之臂、飞凌等厂商的开发板OpenHarmony官方源码里不一定有同名文件那就得找厂商适配包或者自己新建一个dts。2.2 三条路径锁定当前设备用的DTS那如果拿到手里是一台已经能跑起来的OpenHarmony设备但不确定它用哪个dts怎么办这里我分享三个亲测有效的定位思路。思路一从编译配置反推。OpenHarmony编译构建时各个板级方案的配置通常会明确指定内核的defconfig和设备树。你可以在//vendor/{厂商}/{产品}/目录下的配置脚本、config.json、BUILD.gn等文件里找到类似product_rk3568的配置项。以RK3568为例如果编译命令里指定的是某个标准evb产品那么dts大概率就是rk3568-evb.dts或者它的变体。这个方法最权威因为它是编译过程中的硬性约束。思路二从编译产物里找dtb。编译内核完成后如果你的设备树是随资源镜像(resource.img)打包的那么在out/.../packages/phone/system/或out/.../kernel/src/之类的输出目录下会存在对应的dtb。用find按名字匹配一下比如find out -name *.dtb看到哪个rk3568的dtb被编译了基本就能确定。如果是用fastboot或烧录工具把镜像解包查看也能看到dtb文件那一栏。思路三从设备运行状态反查。如果系统已经跑着想确认当前内核解析的是不是你以为的那个dtb可以用cat /sys/firmware/devicetree/base/model查看设备树根节点的model属性。这个属性在dts里一般写在根节点下比如model Rockchip RK3568 EVB1 DDR4 V10 Board。如果该属性值和你的板子对不上那说明加载的dtb很可能选错了。2.3 一个永远有效的定位技巧上面的思路已经能覆盖大多数场景。但如果你用的是自研硬件而且找遍源码都找不到匹配的dts那还有一个终极技巧新建一个板级dts从相近的官方evb文件复制过来改。具体步骤是复制rk3568-evb.dts为rk3568-myboard.dts。根据自研板原理图修改内存颗粒配置、电源管理IC型号、外设引脚复用。找到内核Makefile或OpenHarmony的构建配置把新的dts加入编译目标。编译生成的dtb单独烧录验证。这种做法在行业里非常常见毕竟没有哪家做产品的人会直接用官方evb板级文件量产。新dts文件从复制到适配基本就是设备树移植的标准做法。3. 实战改DTS一个GPIO外设从需求到落地的完整流程3.1 先摸清需求外设与引脚清单这一步不写代码但比写代码更重要。拿到一块板子需求是“加一个GPIO控制的LED指示灯”或者“引出一个UART调试串口”先别急着打开dts去敲节点而是先做一张硬件接口对照表。我以南向开发中最常见的“GPIO LED”举例。假设你的板子原理图上写着LED1的正极通过限流电阻接到SoC的GPIO3_A1引脚负极接GND低电平点亮。那这张表至少要有三列功能名称、SoC引脚名、原理图网络标号。另外还要确认该引脚的IO电压域是1.8V还是3.3V别小看这一步电压域配错轻则外设不工作重则烧芯片。拿到这些信息后去芯片手册或者Rockchip官方TRM里查GPIO3_A1对应的引脚复用号。RK3568的GPIO引脚大多是复用引脚可以当普通GPIO用也可以当UART、I2C、PWM等专用功能用具体由iomux寄存器决定。这个iomux设置在设备树里就是pinctrl子节点待会我们要重点处理它。3.2 打开正确的文件在dts里改还是dtsi里改设备树改在哪一层很有讲究。我的原则很简单芯片级定义不动板级定义看情况产品级定制最优先放dts。比如GPIO LED这种明显是产品板级资源的设备就应该放在板级dts里而不是塞进rk3568.dtsi。否则后面做其他项目时复制一个新的板级dts发现还残留着上一个产品的LED节点又要花时间清理。板级dts文件结构通常长这样/dts-v1/; #include rk3568-evb.dtsi #include rk3568-android.dtsi / { model Rockchip RK3568 EVB1 DDR4 V10 Board; compatible rockchip,rk3568-evb1-ddr4-v10, rockchip,rk3568; chosen { bootargs ...; }; };这个文件本身其实很短大量的板级描述都被include进来的rk3568-evb.dtsi吸收了。看到这里你应该明白真正要改的节点大多数情况下写在你自己的dts文件的根节点{ }内部或者通过符号去引用并覆盖其他地方定义好的节点。3.3 添加gpio-leds设备节点的实操现在开始写真正的设备树代码。Linux内核里GPIO LED有一套标准驱动框架我们只需要按照框架的格式声明节点就行。在板级dts的根节点下面加一段gpio-leds/ { leds { compatible gpio-leds; status okay; work_led { label work; gpios gpio3 RK_PA1 GPIO_ACTIVE_LOW; default-state on; linux,default-trigger heartbeat; }; }; };逐个字段解释一下。compatible gpio-leds是设备与驱动的匹配钥匙驱动会去设备树里找符合这个名字的节点。gpios属性中gpio3表示GPIO3控制器RK_PA1表示A组第1脚也就是GPIO3_A1GPIO_ACTIVE_LOW告诉内核该引脚是低电平有效。default-state表示系统启动后这个LED默认亮还是灭因为我们的电路是低电平点亮所以如果要默认亮就写on。linux,default-trigger是可选属性填heartbeat可以让这个LED像心跳一样闪烁很适合做系统运行状态指示灯。这里有个小知识点RK_PA1这类宏定义在Rockchip平台通用的头文件里比如include/dt-bindings/pinctrl/rockchip.h或include/dt-bindings/gpio/gpio.h。写代码时不用担心宏能不能展开编译阶段dtc会处理好这一切。3.4 pinctrl引脚复用改之前先查这张表光有上面的LED节点还不够。前面说了GPIO3_A1默认可能是其他功能想让这颗引脚老老实实当一个普通GPIO必须把它的iomux配置成GPIO模式。常见做法有两种一种是在LED节点里直接回避因为大多数情况下SoC引脚的默认复用状态就是GPIO模式如果不涉及和其他控制器冲突系统能正常工作。另一种更稳妥的做法是显式声明pinctrl。pinctrl { leds { work_led_gpio: work-led-gpio { rockchip,pins 3 RK_PA1 RK_FUNC_GPIO pcfg_pull_none; }; }; };这行配置的意思是第3组引脚、A1脚复用为GPIO功能RK_FUNC_GPIO上下拉为无pcfg_pull_none。然后在LED节点里加上pinctrl引用leds { compatible gpio-leds; pinctrl-names default; pinctrl-0 work_led_gpio; work_led { ... }; };为什么要显式配置pinctrl我见过太多初学者直接把GPIO当LED输出结果不亮排查半天发现引脚被复用在UART或I2C功能上了。显式配置pinctrl本质是把这个引脚的“身份”钉死避免内核其他子系统或者bootloader遗留的复用状态造成冲突。在复杂产品上这块尤其重要因为你不知道其他驱动会不会也想用同一颗引脚。关于引脚复用强烈建议把RK3568的TRM里IOMUX相关的寄存器表格打印出来放在手边。你不需要背下来但需要知道怎么查比如GPIO3 group的IOMUX寄存器偏移是多少每个引脚bit位对应什么功能码。查了几次之后你对pinctrl配置的理解会有质的提升。4. 编译与烧录怎么让改掉的DTS真正生效4.1 OpenHarmony里只编译dtb的快捷方式很多人在OpenHarmony的构建系统里栽跟头是因为每次改动都触发全量构建一等就是半小时甚至更久。其实设备树只是内核的一部分我们完全可以把编译范围缩小。以标准RK3568平台为例进入OpenHarmony根目录后先source环境source build/envsetup.sh然后你可能会用类似hb build -f -T kernel的命令编整个内核。但如果你只想快速验证dtb的改动还有一个更直接的路径——进入内核源码目录直接用make编译设备树cd kernel/linux/linux-5.10 make ARCHarm64 rockchip/rk3568-evb.dtb前提是相关依赖已经被之前的构建初始化过比如存在.config和必要的生成头文件。如果没有.config先执行make ARCHarm64 rockchip_defconfig这里的rockchip_defconfig是RK3568平台在内核里的默认配置。这样编译完在arch/arm64/boot/dts/rockchip/下能找到新生成的rk3568-evb.dtb整个过程只需要几十秒。这是我最常用的内循环调试方式。如果OpenHarmony的构建系统版本较新也可以考虑在hb构建时指定kernel的编译参数或者直接使用./make.sh dtb这类厂商提供的脚本。具体方式取决于你手上的发行版但核心思路是一样的不要总是全量编译dtb单编最快。4.2 打包与烧录boot和resource都不能少dtb编出来只是第一步还得让它进到最终烧录的镜像里。RK平台有两种常见打包方式dtb集成到boot.img的kernel image尾部或者编译成resource.img单独分区。在OpenHarmony标准系统里RK3568的dtb通常和kernel一起被打进boot镜像或者放在resource分区。具体怎么打包取决于方案商的适配程度。很多开发板固件里resource.img里就存放了多个dtb启动时由U-Boot根据板级识别自动选择。如果你改了dtb并编译成dtb文件打包时就得严格替换掉原来resource.img里对应的dtb再用烧录工具把新的resource.img烧进去。我建议的做法是编译完dtb之后先把它解包出来确认名字和板级模型对不对。比如用dtc -I dtb -O dts -o output.dts rk3568-evb.dtb反编译看一眼model字段确认就是你要的那块板再去打包。这个习惯能避免烧了好几轮才发现烧的是旧资源。烧录工具方面各开发板厂商基本都会提供Windows下的烧录工具比如RKDevTool以及文档说明如何通过Maskrom或者Loader模式烧录boot/resource分区。OpenHarmony开发中也可以使用fastboot方式烧录。但无论工具是哪家的原则都一样单独烧录改动过的分区不要一上来就整包烧这样可以显著缩短验证周期。4.3 上电验证三招确认你的DTS已经生效设备树改完烧录完成系统启动后怎么确认它真的生效我常用的招数有三个。第一招看启动串口日志。内核对设备树解析的过程会在启动早期打印大量信息比如[ 0.000000] Machine model: Rockchip RK3568 EVB1 DDR4 V10 Board如果这个model字符串和你dts里写的对不上说明你烧的根本不是你以为的dtb。第二招查设备树运行时视图。系统起来后内核会把设备树展开成sysfs下的文件执行ls /sys/firmware/devicetree/base/ cat /sys/firmware/devicetree/base/model可以看到所有设备树节点的影子。如果能看到leds目录基本说明gpio-leds节点被内核解析到了。再进一步ls /sys/class/leds/如果出现work说明驱动已经把LED注册成了标准LED设备设备树修改算是真正打通了内核LED不亮就只剩电路或GPIO配置的问题这个后面再说。第三招用busybox或shell直接读写GPIO值做兜底测试。排除内核驱动的问题echo 1 /sys/class/leds/work/brightnessIDE亮起说明硬件链路没问题设备树只是把驱动挂上了。如果不亮就得看gpio chip的base编号和实际物理引脚是否对应这个排查方向就需要对照芯片寄存器去查了。5. 高频问题与排查速查表5.1 症状一设备树改了但外设没反应这种情况最让人抓狂明明是照着教程改的为什么就是没反应按我的经验主要从四个方向排查改动位置对不对。是不是改了一个根本没参与编译的dts文件RK3568目录下有几十个文件你改的那个可能在某个不存在的板级配置里。首先要确认编译日志里编译的确实是这个文件。编译产物有没有被更新。dtb单编成功不代表打包镜像用的是新产物。很多打包脚本有缓存不清理干净就会用旧资源。bootloader有没有加载新的dtb。有些U-Boot会从resource分区加载dtb如果你只更新了boot.img而resource分区没动等于白改。驱动的compatible匹配不上。设备树里有节点但没有对应驱动的compatible字符串内核不会报错只会安静地忽略掉这个节点。所以要对一下compatible的命名。5.2 症状二GPIO被占用或被复用成其他功能比较典型的现象是明明在dts里写了一个gpio-leds节点系统起来后cat /sys/kernel/debug/gpio一看这颗GPIO的状态却是“claimedby某个驱动”或者被设置成了非GPIO的复用功能。这种冲突多半是pinctrl没写好或者有其他设备节点也引用到了同一个引脚。比如I2C控制器配置里的i2c-scl、i2c-sda引脚恰好和你的GPIO重叠两个节点都试图占用它就会导致其中一个失败。排查办法是查看pinctrl的debugfs信息cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins从中搜索相关引脚编号看它最终被谁复用、复用成哪个功能。再回过头去改dts里的pinctrl把冲突的另一方禁用掉比如把不需要的I2C控制器status改成disabled。5.3 症状三编译通过却找不到预期节点有些人编译不报错烧进去系统也正常但/sys/firmware/devicetree/base/下就是没有刚加的节点。在排除了合法性问题后最常见的原因是dts文件里语法写错了但错误不够明显dtc把它当成了未知属性忽略。比如某条属性结尾忘了分号或者引号没有成对dtc可能报warning而不是error最终生成的dtb里对应节点被丢弃。另外还要注意include路径是否真的生效。如果你在dts里#include xxx.dtsi但该文件里没有你自定义的节点那很可能是include了别处的同名文件。用dtc反编译生成的dtb检查根节点下是否有预期内容是最朴素但最有效的方法。5.4 排查工具速查表为了以后调试方便我整理了一张自己常用的速查表大家可以直接收藏目标命令/操作说明查看设备树模型cat /sys/firmware/devicetree/base/model确认加载的dtb是不是目标板全量查看设备树节点ls /sys/firmware/devicetree/base/查看内核解析后的设备树视图查看GPIO被谁占用cat /sys/kernel/debug/gpio排查GPIO冲突查看引脚复用状态cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins确认引脚最终复用为哪个功能反编译dtbdtc -I dtb -O dts -o out.dts xxx.dtb检查dtb里到底有什么单编dtbmake ARCHarm64 rockchip/rk3568-evb.dtb快速验证dts语法和产物查看内核崩溃日志cat /sys/fs/pstore/console-ramoops*pstore机制保存kernel panic前后的日志最后分享一个小技巧在解码硬件问题时不要急着改代码。先把“当前状态”完整记录一遍查一遍sysfs和debugfs再做修改。很多时候你改第二版之前第一版没生效的原因就已经在记录中暴露出来了。在实际开发中我对设备树修改的最大体会是它本身并不难难的是确认“你改的东西和系统实际加载的东西是同一样东西”。只要把这条链路打通把“定位dts、修改节点、单编dtb、确认加载”这个最小闭环练成肌肉记忆OpenHarmony的硬件适配工作基本上就成功了一大半。希望这篇教程能帮你少走几步弯路。
返回列表