本文是《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》系列的第 5 篇。设备树是嵌入式移植的必修课,这篇从"为什么需要"讲到"怎么读",配合真实代码片段。
上一篇文章讲了开机的 5 棒接力。第④棒 Linux 内核要接管硬件,但内核怎么知道"这块板的屏幕接在哪个引脚"?答案就是今天的主角:设备树(Device Tree)。
一、内核为什么需要一份"说明书"
内核是通用的,板子是千差万别的。怎么让同一份内核适配不同的板子?历史上只有两条路:
- 老办法(硬编码):每块板的硬件信息写死在 C 代码里 → 换块板就要改代码重编内核,几百块板就是几百份内核,灾难
- 现代办法(设备树):硬件信息写在一个独立的数据文件里,内核启动时读取它。同一份内核 + 不同的说明书 = 适配不同的板子
这个说明书就是 DTS(Device Tree Source,设备树源文件),编译后是二进制 DTB,内核读的是 DTB。
类比:内核是"万用遥控器",DTS 是"这台设备怎么接线"的说明书。换设备只换说明书,不用换遥控器。
二、DTS 长什么样:先读一段真实的
这是我在 CHESHIT10 板移植中写的设备树片段(以太网部分,已简化):
&gmac0 { /* 主芯片的第 0 个网卡控制器,补充说明 */
phy-mode = "rmii"; /* 用 RMII 这种接线模式 */
snps,reset-gpio = <&gpio0 RK_PD5 GPIO_ACTIVE_LOW>;
/* 复位引脚接 gpio0 的 D5 脚,低电平有效 */
phy-handle = <&rmii_phy0>; /* 配套的网卡芯片是下面定义的 rmii_phy0 */
status = "okay"; /* 启用这个设备 */
};
翻译成人话:"主芯片的网卡控制器 gmac0:用 RMII 模式接线,复位信号接在 gpio0_D5 引脚(低电平触发),它管着的网卡芯片叫 rmii_phy0,启用它。"
设备树就是几百条这样的条目组成的树状结构(所以叫"树")。
10 分钟语法速成
节点名: 标签 { /* 一个"条目",{ } 里写属性 */
属性名 = <值1 值2>; /* 尖括号:数字 */
属性名 = "字符串"; /* 引号:字符串 */
属性名 = [十六进制]; /* 方括号:原始字节(屏幕初始化序列就是这种) */
};
三个最常用的写法:
&gmac0 { ... }—— 给别处定义过的节点补充/覆盖属性(这叫"引用")status = "okay" / "disabled"—— 启用/禁用设备#include "xxx.dtsi"—— 引用另一个文件,把说明书拆成多个章节(像拼图)
三、一份真实板子的说明书长什么样
我写的 CHESHIT10 说明书结构(真实文件):
rk3568-cheshit10-7inch.dts ← 总目录(入口):包含下面所有章节
├── rk3568.dtsi ← 第1章:芯片说明书(RK3568 有哪些控制器) [官方自带]
├── rk3568-linux.dtsi ← 第2章:开机参数、调试串口 [官方自带]
├── rk3568-cheshit10.dtsi ← 第3章:【我写的】CHESHIT10 这块板的说明书
│ ├── #include "rk3568-toybrick-x0.dtsi" ← 先引用官方参考板当底稿
│ └── 然后覆盖 CHESHIT10 不一样的地方:
│ ├── 以太网:官方板 RGMII 接线 → CHESHIT10 改 RMII(网卡芯片不同)
│ ├── 屏幕通路:官方板主屏接 DSI0 → CHESHIT10 接 DSI1
│ ├── 触摸:I2C3 总线上挂 4 个候选触摸芯片
│ ├── 摄像头:官方传感器删掉,换成 GC2053+GC2093
│ ├── USB:5V 供电引脚、Hub 复位引脚改到 CHESHIT10 走线
│ └── RTC 时钟芯片:HYM8563 挂 I2C1
├── panel-cheshit10-7inch-jh071.dtsi ← 第4章:【我写的】屏幕说明书
└── touch-cheshit10.dtsi ← 第5章:【我写的】触摸芯片说明书
为什么分层? 因为这块板要支持多块屏、多颗触摸芯片(第 9 篇变体框架):换屏只换第 4 章,换触摸只改第 5 章,公共部分(第 3 章)不动。这就是工程上"高内聚、低耦合"的落地。
四、屏幕的"密语":初始化序列
屏幕说明书里最不容错的部分是这段(真实项目片段):
panel-init-sequence = [
15 00 02 30 00
39 00 05 F7 49 61 02 00
...(几十行)...
05 78 01 11 /* 发送命令 0x11(屏幕启动),等 120ms */
];
这段像"咒语"一样的十六进制是什么?屏幕驱动 IC 的初始化指令流。屏幕模组上电后必须按固定顺序收到一串命令才会正常显示,顺序是屏厂给的。格式:[命令类型 延时 长度 命令字节 数据...],例如 05 78 01 11 = "类型05,延时120ms,长度1,发送命令 0x11"。
移植要点:这段咒语必须一字不差地从原厂系统抄过来——写错一个字节,屏幕可能白屏、花屏、不亮。 这是"翻译工作"里最不容错的部分。
还有 timing(时序)参数:
clock-frequency = <51000000>; /* 像素时钟 51MHz */
hactive = <1024>; vactive = <600>; /* 分辨率 1024x600 */
hfront-porch = <160>; hsync-len = <24>; hback-porch = <136>;
vfront-porch = <12>; vsync-len = <2>; vback-porch = <21>;
这组数字描述"每画一帧画面,信号怎么走",也是屏厂给的,抄错屏幕会闪、偏、黑。
五、写错说明书的代价:三个真实教训
写设备树是精细活,这次移植踩了三个坑,值得每个入门者提前知道:
- 删除节点必须放顶层:
/delete-node/ &标签;语句放在&overlay { }块内部会直接语法错误(新版 dtc 的严格检查) - 删除前先查引用:被声卡等节点引用的芯片节点,宁可
status="disabled"也不要删除——删了会留下"悬空引用",编译报错 - 改完先用 dtc 单独验证:不要等几小时的内核编译才发现 DTS 有错,用 dtc 工具 30 秒验证一次:
cpp -nostdinc -undef -x assembler-with-cpp \
-I <头文件目录> -I <dts目录> rk3568-cheshit10-7inch.dts \
| dtc -I dts -O dtb -o test.dtb -
# 只关注 Error/FATAL;Warning 与官方参考板同级即可接受
写在最后
设备树是嵌入式 Linux/Android/OpenHarmony 通用的技能,学会了三端通吃。入门就一句话:DTS 是硬件的说明书,移植的大部分工作就是在写/翻译这份说明书。
下一篇是实战:我是怎么把 Android 4.19 内核的设备树,翻译成 OpenHarmony 6.6 内核能读的版本的。
📚 本系列文章导航
《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》
- Ubuntu 24.04 编译 OpenHarmony 6.1:我踩过的 11 个坑
- 我把一块跑 Android 11 的开发板,换成了 OpenHarmony
- 零基础拆解:一块 RK3568 开发板里有什么
- 按下电源后发生了什么?嵌入式开机全流程
- 给 Linux 内核写一份"硬件说明书":设备树 DTS 入门
- 设备树翻译实战:把 Android 4.19 的 DTS 搬到 OpenHarmony 6.6
- OpenHarmony HDF 驱动框架入门:屏幕驱动"参数化"改造
- 从零写一个 OpenHarmony 触摸驱动:TW3106 移植实录
- 一块板子如何兼容 4 种触摸芯片和 3 种 4G 模组?
- 移植全景复盘:从 0 到可烧写镜像,我做了什么