设备树是描述硬件的分层数据结构。Zephyr 使用devicetree
来描述其支持的主板上可用的硬件,以及该硬件的初始配置。
有两种类型的设备树输入文件:设备树源和设备树绑定。
- 源包含设备树本身。
- 绑定描述其内容,包括数据类型。
生成系统
使用设备树源和绑定来生成生成的C的头文件devicetree.h
。
语法结构
设备树就是树。此树的可读文本格式称为DTS
。
下面是一个示例 DTS 文件:
/dts-v1/;
/ {
a-node {
subnode_label: a-sub-node {
foo = <3>;
};
};
};
/dts-v1/
代表的是DTS语法的版本为第一版。- 该树有三个节点:
- 根节点:
/
- 名为
a-node
的节点,它是根节点的子节点 - 一个名为
a-sub-node
的节点,它是a-node
节点的子节点
- 根节点:
- 可以为节点指定标签,标签是节点的唯一速记。上面
a-sub-node
节点的标签为subnode_label
。
设备树节点
具有标识其在树
中的位置的路径。与Unix文件系统路径
一样,设备树路径
是用斜杠/
分隔的字符串,根节点的路径是单个斜杠/
。每个节点的路径都是通过将节点的祖先名称
与节点自己的名称
用斜杠
分隔连接起来而形成的。例如,a-sub-node
的完整路径是 /a-node/a-sub-node
设备树节点也可以具有属性
。属性
是键值对
。属性的值可以是任何字节序列。在某些情况下,这些值是称为单元格的数组。单元格只是一个 32 位无符号整数。a-sub-node
节点有一个名为foo
的属性,其值是值为3的单元格
。foo
的大小和类型值由DTS中尖括号
表示。devicetree
节点通常对应于某些硬件,节点层次结构反映硬件的物理布局。例如,让我们考虑一个具有三个 I2C 外设的电路板,该外设连接到SoC上的I2C总线控制器
,如下所示:
与I2C总线控制器
和每个I2C外设
对应的节点将存在于器件树中,以此来反映硬件布局,I2C外设节点将是总线控制器节点的子节点。
/dts-v1/;
/ {
soc {
i2c-bus-controller {
i2c-peripheral-1 {
};
i2c-peripheral-2 {
};
i2c-peripheral-3 {
};
};
};
};
属性
用于描述或配置节点所表示的硬件。例如,I2C外设的节点具有一个属性,其值是总线上外设的地址。
对应的DTS为:
/dts-v1/;
/ {
soc {
i2c@40003000 {
compatible = "nordic,nrf-twim";
label = "I2C_0";
reg = <0x40003000 0x1000>;
apds9960@39 {
compatible = "avago,apds9960";
label = "APDS9960";
reg = <0x39>;
};
ti_hdc@43 {
compatible = "ti,hdc", "ti,hdc1010";
label = "HDC1010";
reg = <0x43>;
};
mma8652fc@1d {
compatible = "nxp,fxos8700", "nxp,mma8652fc";
label = "MMA8652FC";
reg = <0x1d>;
};
};
};
};
除了显示更真实的名称和属性之外,上面的示例还引入了一个新的设备树概念:单元地址
。单元地址
是节点名称中@
符号之后的部分,如i2c@40003000
的单元地址就为40003000
。单元地址
是可选的,节点可以没有。
单元地址
在设备树中,单元地址
提供节点在其父节点的地址空间中的地址。以下是不同类型硬件的一些示例单元地址。
- 内存映射外设:
外设
的寄存器映射基址。例如,i2c@40003000
命名的节点表示其寄存器映射基址0x40003000
I2C控制器。 - I2C 外设:
I2C总线
上的外设地址
。例如,apds9960@39
的I2C外设节点的I2C地址为0x39。 - SPI 外设: 表示外设芯片选择引脚的索引。如果没有芯片选择引脚,则使用0。
- 内存:物理起始地址。例如,名为
memory@2000000
的节点表示从物理地址0x2000000开始的RAM。 - 内存映射闪存: 与RAM一样,物理起始地址也是如此。例如,名为
flash@8000000
的节点表示其物理起始地址为0x8000000的闪存设备。 - 固定的闪存分区: 当设备树用于存储闪存分区表时。单元地址是闪存中分区的起始偏移量。例如,以此闪存设备及其分区为例:
命名的flash@8000000 {
/* ... */
partitions {
partition@0 { /* ... */ };
partition@20000 { /* ... */ };
/* ... */
};
};
partition@0
节点从其闪存设备的开头开始偏移量为 0,因此其基址0x8000000。同样,命名节点partition@20000
的基址0x8020000。
重要的属性
compatible属性
compatible属性
表示的硬件设备的名称。
建议的格式为:<厂商>,<设备型号>
,目前系统里支持的compatible
可以从dts/bindings/vendor-prefixes.txt
查看到。compatible属性
有时也是一个值,当硬件的行为是通用的。如: gpio-keys
,mmio-sram
,fixed-clock
。生成系统
使用compatible属性
为节点查找正确的绑定文件
。
设备驱动程序也可以使用compatible
属性来查找硬件。compatible属性
可以有多个值。只需要能匹配到一个就可以。
label属性
label属性
可以传递给device_get_binding()
来检索相应的驱动程序和struct device*
。然后,可以通过应用程序代码将此指针传递到正确的驱动程序API,以便与设备进行交互。
reg属性
reg属性
用于对设备进行寻址。该值是特定于设备的,也就是说该值根据compatible属性
的不同而不同。
该属性是reg(address, length)
形式的一系列键值对。
以下是一些常见的模式:
- 通过
内存映射I/O寄存器
:address
代表的是I/O寄存器空间的基址
,length
代表的是寄存器占用的字节数。 I2C外设
:reg
代表的是I2C总线上的从地址。- SPI外设:
reg
代表的是芯片选择引脚。
status属性
status属性
描述节点是否是启用的。
可选的值为:okay
: 启动disabled
:禁用
写入属性值
一下列出了zephyr中定义的属性值的类型:
属性类型 | 如何去写 | 例子 |
---|---|---|
string | 双引号 | a-string = "hello, world!"; |
int | 尖括号 | an-int = <1>; |
boolean | 没有定义就是false,定义了就是true | my-true-boolean; |
array | 尖括号中用空格分隔 | foo = <0xdeadbeef 1234 0>; |
uint8-array | 方扩招中用空格分隔 | a-byte-array = [00 01 ab]; |
string-array | 以逗号分隔 | a-string-array = "string one", "string two", "string three"; |
phandle | 尖括号 | a-phandle = <&mynode>; |
phandles | 尖括号用空格分隔 | some-phandles = <&mynode0 &mynode1 &mynode2>; |
phandle-array | 尖括号用空格分隔 | a-phandle-array = <&mynode0 1 2 &mynode1 3 4>; |
- 布尔属性如果存在,则为真。它们不应具有值。布尔属性只有在DTS中完全丢失时才为false。
- 上面的属性值有三个
单元格
,其值按该顺序0xdeadbeef
、1234
和0
。请注意,十六进制数
和十进制
数是允许的,并且可以混合使用。 64位整数
按大端顺序编写为两个32位单元格。例如:写入0xaaaa0000bbbb1111
的值时为<0xaaaa0000 0xbbbb1111>
允许使用
括号
、算术运算符
和按位运算符
。bar = <(2 * (1 << 5))>;
属性值通过其处理项引用设备树中的其他节点。
foo: device@0 { };
device@1 {
sibling = <&foo 1 2>;
};
数组和类似类型的属性值可以拆分为多个块,如下所示:
foo = <1 2>, <3 4>; // Okay for 'type: array'
foo = <&label1 &label2>, <&label3 &label4>; // Okay for 'type: phandles'
foo = <&label1 1 2>, <&label2 3 4>; // Okay for 'type: phandle-array'
别名和选择节点
除了
节点标签
之外,还有两种额外的方法来引用特定节点而不指定其整个路径: ```c /dts-v1/;
/ { chosen { zephyr,console = &uart0; };
aliases {
my-uart = &uart0;
};
soc {
uart0: serial@12340000 {
...
};
};
};
``
aliases和
chosen节点不是指实际的硬件设备。它们的目的是在设备树中指定其他节点。<br />上面的
zephyr,console或
my-uart节点,可以替代
/soc/serial@12340000`节点
输入和输出文件
输入文件
有四种类型的设备树输入文件:
- 设备树源文件(
.dts
) - 设备树源包括文件 (
.dtsi
) - 设备树覆盖文件 (
.overlay
) - 设备树绑定文件 (
.yaml
)
一般.dts
位于板子的目录下,例如zephyr\boards\arm\nrf5340dk_nrf5340\nrf5340dk_nrf5340_cpuapp.dts
。
在.dts
文件中会包含.dtsi
文件。
设备树覆盖文件的作用是在应用程序目录下去覆盖板子
默认设备树。
设备树绑定文件的作用是对各种兼容属性下的属性类型进行规范。
输出文件
它们是在应用程序的生成目录中创建的。
<build>/zephyr/zephyr.dts.pre
:预处理的DTS源
。这是一个中间输出文件,用于创建zephyr.dts
和devicetree_unfixed.h
。<build>/zephyr/include/generated/devicetree_unfixed.h
:生成的宏和描述设备树的其他注释。包含于devicetree.h
中。<build>/zephyr/include/generated/devicetree_fixups.h
: 包含所有的设备树修复文件dts_fixup.h
。包含于devicetree.h
中。<build>/zephyr/zephyr.dts
:最终合并的设备树。