第4章 设备树(Device Tree)详解
设备树这东西,刚接触时觉得挺玄乎。说白了,它就是一套描述硬件的数据结构。Linux内核通过它才知道「哦,我这块板子上接了哪些外设」。我在早期做嵌入式时,还用过古老的板级文件(board-xxx.c),每次换硬件都要重新编译内核,那叫一个痛苦。设备树解决了这个问题——硬件描述和内核代码分离,改个.dts文件就行。
4.1 设备树语法基础
设备树的源文件是.dts,编译后变成.dtb。内核启动时,bootloader把.dtb传给内核。内核解析这个二进制文件,构建出设备树。
一个最简单的节点长这样:
/dts-v1/;
/ {
compatible = "acme,coyote";
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0>;
};
};
};
几个关键点:
- 根节点:用
/表示,整棵树从这里开始 - 节点名:格式是
node-name@unit-address,比如uart@10000000 - 属性:键值对,值可以是字符串、数字、二进制数据
- reg:描述地址空间,由
#address-cells和#size-cells决定长度
#address-cells 和 #size-cells 定好。这两个值一旦写错,后面所有reg解析都会乱套。我一般用1和1,32位地址空间够用了。
4.2 GPIO节点配置
GPIO在设备树里怎么配?我拿一个实际项目中的例子来说。当时做一款工业数据采集器,需要控制8路继电器输出。
&gpio1 {
gpio-controller;
#gpio-cells = <2>;
};
/ {
relay-control {
compatible = "mycompany,relay-ctrl";
relay-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>,
<&gpio1 11 GPIO_ACTIVE_HIGH>,
<&gpio1 12 GPIO_ACTIVE_HIGH>,
<&gpio1 13 GPIO_ACTIVE_HIGH>;
};
};
这里要注意:
gpio-controller声明这个节点是GPIO控制器#gpio-cells = <2>表示引用GPIO时需要两个参数:引脚号和电平标志GPIO_ACTIVE_HIGH定义有效电平,这个宏在dt-bindings/gpio/gpio.h里
4.3 UART节点配置
UART配置相对标准。大部分SoC的UART都兼容16550。我调试过不少串口设备,配置模板基本一致:
&uart2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2>;
current-speed = <115200>;
uart-has-rtscts;
};
几个关键属性:
| 属性 | 说明 | 常见值 |
|---|---|---|
| status | 使能状态 | "okay" 或 "disabled" |
| pinctrl-0 | 引脚复用配置 | 引用pinctrl节点 |
| current-speed | 波特率 | 9600, 115200, 921600 |
| uart-has-rtscts | 硬件流控 | 有则启用 |
实战经验:工业现场经常遇到串口通信不稳定。我建议把 current-speed 设低一点,比如9600。虽然慢,但抗干扰能力强。另外,uart-has-rtscts 这个属性,如果你的设备不支持硬件流控,千万别加,否则驱动会一直等CTS信号,导致通信卡死。
4.4 I2C节点配置
I2C总线上挂多个从设备,这是工业网关的常见场景。比如挂一个温湿度传感器和一个RTC时钟芯片:
&i2c1 {
status = "okay";
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
temp-humidity@44 {
compatible = "sensirion,sht20";
reg = <0x44>;
};
rtc@68 {
compatible = "dallas,ds3231";
reg = <0x68>;
};
};
配置要点:
clock-frequency:I2C时钟频率,标准模式100kHz,快速模式400kHz- 每个从设备用
reg指定7位地址 compatible必须与驱动匹配,格式是 "厂商,型号"
i2cdetect -y 1 扫描总线,看设备地址对不对。我遇到过好几次,芯片手册写的地址是8位格式,但设备树里要用7位,差一位就找不到设备。
4.5 SPI节点配置
SPI在工业网关里常用于连接ADC、DAC或无线模块。配置示例:
&spi2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi2>;
cs-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>,
<&gpio1 16 GPIO_ACTIVE_LOW>;
adc@0 {
compatible = "ti,ads1256";
reg = <0>;
spi-max-frequency = <2000000>;
spi-cpol;
spi-cpha;
};
rf-module@1 {
compatible = "semtech,sx1278";
reg = <1>;
spi-max-frequency = <10000000>;
};
};
SPI配置的几个关键点:
cs-gpios:片选引脚,可以用GPIO模拟,也可以用硬件片选spi-max-frequency:每个从设备可以单独设置最大频率spi-cpol和spi-cpha:定义SPI模式(0/1/2/3)reg:片选编号,从0开始
4.6 设备树覆盖(Overlay)技术
Overlay是什么?简单说就是「打补丁」。你有一个基础设备树,然后动态加载一个overlay文件,修改或添加节点。这在工业网关里特别有用——比如现场需要临时接一个传感器,不用重新编译整个设备树,加载一个overlay就行。
Overlay的写法:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &i2c1;
__overlay__ {
status = "okay";
co2-sensor@76 {
compatible = "sensirion,scd30";
reg = <0x76>;
};
};
};
};
关键点:
- 文件头加
/plugin/;声明这是overlay - 用
fragment@N定义每个修改片段 target指定要修改的节点__overlay__里写要添加或覆盖的内容
加载overlay的方法:
# 编译overlay
dtc -@ -I dts -O dtb -o co2-overlay.dtbo co2-overlay.dts
# 加载到内核
mkdir /sys/kernel/config/device-tree/overlays/co2
cat co2-overlay.dtbo > /sys/kernel/config/device-tree/overlays/co2/dtbo
我的建议:Overlay技术非常适合工业现场的设备扩展。但要注意,不是所有内核都开启了CONFIG_OF_OVERLAY。编译内核时一定要确认这个选项打开了。另外,overlay加载后不能随意卸载,搞不好会内核崩溃。我一般在测试环境先验证好,再到现场部署。
4.7 设备树知识体系
下面这张图总结了设备树的核心知识结构:
4.8 调试与排错
设备树出问题,最常见的现象是驱动不加载。我的排查步骤:
- 检查dtb是否加载成功:看内核启动日志,搜索 "Device Tree" 或 "OF:"
- 查看设备树内容:
ls /proc/device-tree/看看节点有没有生成 - 检查compatible匹配:
cat /proc/device-tree/model确认板子型号 - 验证引脚复用:
cat /sys/kernel/debug/gpio看GPIO状态 - 检查地址冲突:I2C和SPI设备地址不能重复
dtc -I dts -O dtb -o test.dtb test.dts 2>&1 | grep -i error 严格检查。另外,make dtbs 时加上 DTC_FLAGS=-@ 才能生成overlay支持。
嗯,设备树这部分内容不少,但掌握了核心语法和配置方法,后面做工业网关集成调试就会顺手很多。记住一点:设备树是硬件和驱动之间的桥梁,桥搭好了,数据才能跑得通。