设备树是描述硬件的分层数据结构。Zephyr 使用devicetree来描述其支持的主板上可用的硬件,以及该硬件的初始配置。
有两种类型的设备树输入文件:设备树源和设备树绑定。

  • 源包含设备树本身。
  • 绑定描述其内容,包括数据类型。

生成系统使用设备树源和绑定来生成生成的C的头文件devicetree.h
13.zephyr的设备树简介 - 图1

语法结构

设备树就是树。此树的可读文本格式称为DTS
下面是一个示例 DTS 文件:

  1. /dts-v1/;
  2. / {
  3. a-node {
  4. subnode_label: a-sub-node {
  5. foo = <3>;
  6. };
  7. };
  8. };
  • /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总线控制器,如下所示:
13.zephyr的设备树简介 - 图2
I2C总线控制器和每个I2C外设对应的节点将存在于器件树中,以此来反映硬件布局,I2C外设节点将是总线控制器节点的子节点。

  1. /dts-v1/;
  2. / {
  3. soc {
  4. i2c-bus-controller {
  5. i2c-peripheral-1 {
  6. };
  7. i2c-peripheral-2 {
  8. };
  9. i2c-peripheral-3 {
  10. };
  11. };
  12. };
  13. };

属性用于描述或配置节点所表示的硬件。例如,I2C外设的节点具有一个属性,其值是总线上外设的地址。
13.zephyr的设备树简介 - 图3
对应的DTS为:

  1. /dts-v1/;
  2. / {
  3. soc {
  4. i2c@40003000 {
  5. compatible = "nordic,nrf-twim";
  6. label = "I2C_0";
  7. reg = <0x40003000 0x1000>;
  8. apds9960@39 {
  9. compatible = "avago,apds9960";
  10. label = "APDS9960";
  11. reg = <0x39>;
  12. };
  13. ti_hdc@43 {
  14. compatible = "ti,hdc", "ti,hdc1010";
  15. label = "HDC1010";
  16. reg = <0x43>;
  17. };
  18. mma8652fc@1d {
  19. compatible = "nxp,fxos8700", "nxp,mma8652fc";
  20. label = "MMA8652FC";
  21. reg = <0x1d>;
  22. };
  23. };
  24. };
  25. };

除了显示更真实的名称和属性之外,上面的示例还引入了一个新的设备树概念:单元地址
单元地址是节点名称中@符号之后的部分,如i2c@40003000的单元地址就为40003000
单元地址是可选的,节点可以没有。

单元地址

在设备树中,单元地址提供节点在其父节点的地址空间中的地址。以下是不同类型硬件的一些示例单元地址。

  • 内存映射外设: 外设的寄存器映射基址。例如,i2c@40003000命名的节点表示其寄存器映射基址0x40003000I2C控制器。
  • I2C 外设: I2C总线上的外设地址。例如,apds9960@39的I2C外设节点的I2C地址为0x39。
  • SPI 外设: 表示外设芯片选择引脚的索引。如果没有芯片选择引脚,则使用0。
  • 内存:物理起始地址。例如,名为memory@2000000的节点表示从物理地址0x2000000开始的RAM。
  • 内存映射闪存: 与RAM一样,物理起始地址也是如此。例如,名为flash@8000000的节点表示其物理起始地址为0x8000000的闪存设备。
  • 固定的闪存分区: 当设备树用于存储闪存分区表时。单元地址是闪存中分区的起始偏移量。例如,以此闪存设备及其分区为例:
    1. flash@8000000 {
    2. /* ... */
    3. partitions {
    4. partition@0 { /* ... */ };
    5. partition@20000 { /* ... */ };
    6. /* ... */
    7. };
    8. };
    命名的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。
  • 上面的属性值有三个单元格,其值按该顺序0xdeadbeef12340。请注意,十六进制数十进制数是允许的,并且可以混合使用。
  • 64位整数按大端顺序编写为两个32位单元格。例如:写入0xaaaa0000bbbb1111的值时为<0xaaaa0000 0xbbbb1111>
  • 允许使用括号算术运算符按位运算符

    1. bar = <(2 * (1 << 5))>;
  • 属性值通过其处理项引用设备树中的其他节点。

    1. foo: device@0 { };
    2. device@1 {
    3. sibling = <&foo 1 2>;
    4. };
  • 数组和类似类型的属性值可以拆分为多个块,如下所示:

    1. foo = <1 2>, <3 4>; // Okay for 'type: array'
    2. foo = <&label1 &label2>, <&label3 &label4>; // Okay for 'type: phandles'
    3. foo = <&label1 1 2>, <&label2 3 4>; // Okay for 'type: phandle-array'

    别名和选择节点

    除了节点标签之外,还有两种额外的方法来引用特定节点而不指定其整个路径: ```c /dts-v1/;

/ { chosen { zephyr,console = &uart0; };

  1. aliases {
  2. my-uart = &uart0;
  3. };
  4. soc {
  5. uart0: serial@12340000 {
  6. ...
  7. };
  8. };

}; `` aliaseschosen节点不是指实际的硬件设备。它们的目的是在设备树中指定其他节点。<br />上面的zephyr,consolemy-uart节点,可以替代/soc/serial@12340000`节点

输入和输出文件

13.zephyr的设备树简介 - 图4

输入文件

有四种类型的设备树输入文件:

  • 设备树源文件(.dts)
  • 设备树源包括文件 (.dtsi)
  • 设备树覆盖文件 (.overlay)
  • 设备树绑定文件 (.yaml)

一般.dts位于板子的目录下,例如zephyr\boards\arm\nrf5340dk_nrf5340\nrf5340dk_nrf5340_cpuapp.dts
.dts文件中会包含.dtsi文件。
设备树覆盖文件的作用是在应用程序目录下去覆盖板子默认设备树。
设备树绑定文件的作用是对各种兼容属性下的属性类型进行规范。

输出文件

它们是在应用程序的生成目录中创建的。

  • <build>/zephyr/zephyr.dts.pre:预处理的DTS源。这是一个中间输出文件,用于创建zephyr.dtsdevicetree_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:最终合并的设备树。