NCS-OS系列3 :devicetree介绍
文章目录
- NCS-OS系列3 :devicetree介绍
- 前言
- 一、devicetree概念
- 二、语法和结构
- 三、Unit address示例
-
- 3.1 内存映射外设
- 3.2 I2C外设
- 3.3 SPI外设
- 3.4 存储器
- 3.5 内存映射flash
- 3.6 内存分区
- 四、重要属性介绍
-
- 4.1 compatible
- 4.2 label
- 4.3 reg
- 4.4 status
- 4.5 interrupts
- 五、Aliases and chosen nodes
- 六、输入输出文件
-
- 6.1 输入文件
- 6.2 脚本和工具
- 6.3 输出文件
- 6.4 写属性值
前言
ncs 相关文章,部分为原始文档翻译,水平有限,如果有错误,欢迎指出。
本文参考链接:
https://docs.zephyrproject.org/latest/guides/dts/intro.html
一、devicetree概念
devicetree
是描述硬件的层次化的数据结构。Devicetree specification定义了它的源代码和二进制表示。Zephyr使用
devicetree
来描述其支持的单板上可用的硬件,以及该硬件的初始配置。
有两种类型的
devicetree
输入文件:
devicetree sources
和
devicetree bindings
。
devicetree sources
包含
devicetree
本身。
devicetree bindings
描述它的内容,包括数据类型。编译系统使用
devicetree sources
和
devicetree bindings
来创建一个生成的C头文件。生成的头文件的内容被
devicetree.h
API分离出来,可以使用该API从
devicetree
中获取信息。
下面是一个简单的示例图:

所有Zephyr和应用程序的源代码文件都可以include并使用
devicetree.h
。这包括设备驱动程序、应用程序、测试、内核等。
API本身是基于C宏定义的。宏名都以
DT_
开头。通常,在Zephyr源文件中看到一个以
DT_
开头的宏,那么它可能是一个
devicetree.h
宏。生成的C头文件也包含以
DT_
开头的宏,这些可能会在编译器的错误信息中看到。生成的宏和非生成的宏可以通过这种方式来区分:生成的宏有一些小写字母,而
devicetree.h
宏的名字都是大写字母。
二、语法和结构
如名字所示,
devicetree
是一个树型结构,这个树的人类可读的文本格式称为DTS(用于
devicetree source
),并在Devicetree specification中定义。
下面是一个示例的DTS文件:
/dts-v1/;
/ {
a-node {
subnode_label: a-sub-node {
foo = <3>;
};
};
};
/ dts-v1 /;
表示文件的内容是使用DTS语法的版本1,它取代了现在已经过时的“版本0”。
这个树有三个节点:
- 1个根节点:
/
- 一个根节点的子节点,名字为:
a-node
- 一个
的子节点,名字为:a-node
节点可以a-sub-node
节点可以被赋予
labels
,标签是用来在设备树的其他地方引用标签节点的唯一的简写。上面的DTS文件中,
a-sub-node
的
label
就是
subnode_label
。一个节点可以有0、1或多个节点
label
。
Devicetree节点具有标识其在树中的位置的路径。与Unix文件系统路径一样,Devicetree路径是由斜杠(
/
)分隔的字符串,根节点的路径是单个斜杠:
/
。每个节点的路径将由节点祖先节点的名称与节点自己的名称(用斜杠分隔)连接而成。例如,到
a-sub-node
的全路径为
“/a-node/a-sub-node”
。
节点
a-sub-node
有一个名为
foo
的属性,它的值是一个值为
3
的单元。
foo
值的大小和类型由DTS中的尖括号(
<和>
)包含。有关更多属性值的示例,请参阅下面的 Writing property values 部分。
在实际应用中,
devicetree
节点通常对应于某些硬件,节点的层次结构反映了硬件的物理布局。假设一块板,它有三个I2C外设连接到一个SoC上的I2C总线控制器上,如下所示:
对应的I2C总线控制器和每个I2C外围设备的节点将出现在
devicetree
中。反映硬件的布局,I2C外设节点将是总线控制器节点的子节点。类似的规则也适用于表示其他类型的硬件。
上面的DTS文件将会如下所示:
/dts-v1/;
/ {
soc {
i2c-bus-controller {
i2c-peripheral-1 {
};
i2c-peripheral-2 {
};
i2c-peripheral-3 {
};
};
};
};
属性在实际应用中用于描述或配置节点表示的硬件。例如,I2C外设节点具有一个属性,其值是总线上的外设地址。
下面是一个和上面功能一样的树,但其中包含使用I2C设备时可能看到的真实节点名称和属性:
上面的
devicetree
转换成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>;
};
};
};
};
除了显示更真实的名称和属性外,上面的示例还引入了一个新的
devicetree
概念:
unit addresses
。
unit addresses
是在“at”符号(
@
)之后的节点名的部分,如
[email protected]
中的
40003000
,或
[email protected]
中的
39
。
unit addresses
是可选的:soc节点没有
unit addresses
。
三、Unit address示例
在
devicetree
中,
Unit address
给出了在其父节点的地址空间中的节点地址。下面是一些不同类型硬件的
Unit address
示例
3.1 内存映射外设
外设的寄存器映射基地址。例如,名称为
[email protected]
的节点代表一个寄存器映射基地地址为
0x40003000
的I2C控制器。
3.2 I2C外设
I2C总线上的外设地址。例如,上一节中I2C控制器的子节点
[email protected]
的I2C地址是
0x39
。
3.3 SPI外设
代表外围设备的芯片
CS线
的索引。(如果没有芯片
CS线
,则使用0。)
3.4 存储器
物理起始地址。例如,一个名为
[email protected]
的节点表示从物理地址
0x2000000
开始的RAM。
3.5 内存映射flash
像RAM一样,物理起始地址。例如,名称为
[email protected]
的节点代表物理起始地址为
0x8000000
的flash设备。
3.6 内存分区
这适用于
devicetree
用于存储flash分区表的时候。
unit address
是
flash
中分区的开始偏移量。例如,以这个闪存设备及其分区为例:
flash@8000000 {
/* ... */
partitions {
partition@0 { /* ... */ };
partition@20000 { /* ... */ };
/* ... */
};
};
名为
[email protected]
的节点从其flash设备的开始处偏移量为0,因此其基址为
0x8000000
。类似地,名为
[email protected]
的节点的基址是
0x8020000
。
四、重要属性介绍
4.1 compatible
compatible
是节点所代表的硬件设备的名称,推荐的格式是
"vendor,device"
,如
“avago,apds9960”
,或这些的序列,如
“ti,hdc”
,
“ti,hdc1010”
。vendor部分是厂商名称的缩写。文件
dts/bindings/vendor-prefixes.txt
中包含了一个通用的的供应商名称列表。device部分通常取自设备的datasheet。
当硬件的表现是通用的时,它也可以是像
gpio-keys
、
mmio-sram
或
fixed-clock
这样的值。
编译系统使用
compatible
属性为节点找到正确的
bindings
。设备驱动程序使用
devicetree.h
来查找具有相关
compatibles
的节点,以便确定要管理的可用硬件。
compatible
属性可以有多个值。当设备是一个通用系列的特定实例时,会使用附加值以允许系统从最特定的设备驱动程序到最不特定的设备驱动程序匹配。
在Zephyr的绑定语法中,此属性的类型为
string-array
。
4.2 label
label
是根据Zephyr的设备驱动程序模型实现的设备的名称。这个值可以传递给
device_get_binding()
来检索相应的驱动程序级结构
device*
。这个指针可以通过应用程序代码传递给正确的设备驱动程序API来与设备交互。例如,调用
device_get_binding("I2C_0")
将返回一个指向设备结构的指针,该结构可以传递给
I2C API
函数,如
i2c_transfer()
。生成的C头文件同样会包含一个可以描述这个字符串的宏。
4.3 reg
reg
是用于给设备寻址的信息。该值是给到特定设备的(即根据
compatible
属性不同而不同)。
reg
属性是一个由
(address, length)
对组成的序列。每一对被称为一个
“register block”
。下面是一些常见的模式:
- 通过内存映射I/O寄存器访问的设备(如
):[email protected]
通常是I/O寄存器空间的基址,而address
是寄存器所占用的字节数。length
- I2C设备(如
及其系列):[email protected]
是I2C总线上的从地址。没有address
值。length
- SPI设备:
是芯片CS线,没有address
上面描述的length
属性和reg
之间有一些相似之处,unit addresses
属性可以被看作是设备中比reg
更详细的可寻址资源。unit addresses
4.4 status
status
是描述节点是否启用的字符串,devicetree规范允许该属性的值可以为
“okay”
、
“disabled”
、
“reserved”
、
“fail”
和
“fail-sss”
。目前只有
“okay”
和
“disabled”
值与Zephyr相关,使用其他值会导致未定义的行为。
如果一个节点的
status
属性是
“okay”
或者没有定义(即在
devicetree source
中不存在),那么它就被认为是
enabled
。状态为
“disabled”
的节点被显式禁用。(为了向后兼容,值
“ok”
被视为与
“okay”
相同,但这种用法已弃用),在分配和初始化Zephyr驱动模型中的相应
struct device
时,必须启用与物理设备对应的Devicetree节点。
4.5 interrupts
interrupts
是由设备产生的中断的信息,编码为一个或多个中断指示符的数组。每个中断指示符都有一定数量的单元。更多细节请参见 Devicetree Specification release v0.3中section 2.4, Interrupts and Interrupt Mapping章节。
五、Aliases and chosen nodes
除了
node label
之外,还有另外两种方法可以引用特定的节点,而无需指定其整个路径: 通过
alias
或
chosen node
。
下面的devicetree使用了这两个概念:
/dts-v1/;
/ {
chosen {
zephyr,console = &uart0;
};
aliases {
my-uart = &uart0;
};
soc {
uart0: serial@12340000 {
...
};
};
};
/alias
和
/chosen
节点并不指向实际的硬件设备,它们的目的是指定
devicetree
中的其他节点。
上面的dts文件中,
my_uart
是路径为
/soc/[email protected]
的设备的alias,通过使用它的
node label
值
uart0
,
zephyr,console
这个
chosen node
被设置为同样的值。
Zephyr示例应用程序有时使用别名以允许以通用方式来取代应用程序使用的特定硬件设备。例如,Blinky例程使用这个来抽象LED通过
led0 alias
闪烁。
/chosen
节点属性用于配置系统或子系统范围的值。更多信息,请参阅Chosen nodes。
六、输入输出文件
本节更详细地描述了本介绍开头的图中所示的输入和输出文件。
6.1 输入文件
有四种类型的devicetree输入文件:
- sources (.dts)
- includes (.dtsi)
- overlays (.overlay)
- bindings (.yaml)
ncs目录中的设备树文件是这样的:
ncs/zephyr/boards/<ARCH>/<BOARD>/<BOARD>.dts
ncs/zephyr/dts/common/skeleton.dtsi
ncs/zephyr/dts/<ARCH>/.../<SOC>.dtsi
ncs/zephyr/dts/bindings/.../binding.yaml
还有:
ncs/nrf/boards/<ARCH>/<BOARD>/<BOARD>.dts
ncs/nrf/dts/bindings/.../binding.yaml
一般来说,每一个支持的
BOARD
,都要有一个对应的
BOARD.dts
文件来描述它的硬件结构,例如
reel_board
的dts文件为
boards/arm/reel_board/reel_board.dts
BOARD.dts
包括一个或多个
.dtsi
文件,这些
.dtsi
文件或者通过
include
其他
.dtsi
文件来描述运行Zephyr的CPU或片上系统。它们还可以描述由多个板共享的其他常见硬件特征。除了这些,
BOARD.dts
也描述了板子的具体硬件。
dts/common
目录包含
skeleton.dtsi
,
skeleton.dtsi
是用于定义完整
devicetree
的最小包含文件。特定体系结构的子目录(
dts/<ARCH>
)包含了针对
CPUs
或
SoCs
的对
skeleton.dtsi
扩展的
.dtsi
文件。
C预处理器在所有
devicetree
文件上运行以展开宏引用,而
include
通常使用
#include <filename>
指令来完成,即使DTS有
/include/ "<filename>"
语法。
BOARD.dts
可以使用
overlays
文件进行扩展或修改。Overlay也是DTS文件,
overlay
扩展只是一个是目的更明确的惯例。Overlays调整基础的
devicetree
以适应不同的目的:
- Zephyr应用程序可以使用
来启用默认禁用的外围设备,为特定的应用程序选择板上的传感器,等等。通过使用overlay
,可以重新配置内核和设备驱动程序,而无需修改源代码。Kconfig
- 在定义
时也使用Shields
Overlays
。
编译系统自动获取存储在特定位置的
文件。也可以通过.overlay
这个CMake变量显式列出要包含的DTC_OVERLAY_FILE
。详情参见Set devicetree overlay。overlays
编译系统将
BOARD.dts
和其他
.overlay
文件进行合并,
overlay
放在最后,这依赖于DTS允许合并
devicetree
中节点的重复定义的语法。见示例:FRDM-K64F and Hexiwear K64,这是一个在
.dtsi
文件的环境中如何工作的示例,但原理与
overlays
相同,把
.overlay
文件的内容放在最后允许它们覆盖
BOARD.dts
。
Devicetree bindings(
YAML文件
)本质上是
glue
。它们以一种允许编译系统生成设备驱动程序和应用程序可用的C宏的方式来描述
devicetree sources
,
includes
, 和
overlays
的内容。
dts/bindings
目录包含了相关的
bindings
信息。
Zephyr目前使用
dts_fixup.h
文件将
devicetree_unfixed.h
中的宏重命名为C代码当前使用的名称。默认情况下,构建系统在
zephyr/boards/
和
zephyr/soc/
目录中查找修复文件。修复文件的存在是由于历史原因,新代码通常应该避免使用它们。
6.2 脚本和工具
下面的库和脚本位于
scripts/dts/
中,它们使用输入文件来创建输出文件。
dtlib.py : 底层DTS解析库
edtlib.py : 位于dtlib之上的库,它使用
bindings
来解析属性,并提供
devicetree
的更高层次视图,使用dtlib进行DTS解析。
gen_defines.py :使用edtlib从
devicetree
和
bindings
生成C预处理器宏的脚本
另外,如果在您的系统上安装了标准的dtc(
devicetree compiler
)工具,那么它将在最终的
devicetree
上运行,这是为了捕获错误或警告。输出信息没有被使用。板子可能需要传递dtc附加标志,例如
warning suppression
。单板目录中可以包含一个名为
pre_dt_board.cmake
的文件,它配置了这些额外的标志,像这样:
6.3 输出文件
下面这些是在应用程序编译文件夹中创建的
<build>/zephyr/include/generated/devicetree_unfixed.h
生成的宏和描述
devicetree
的附加注释,通过
devicetree.h
进行
include
。
<build>/zephyr/include/generated/devicetree_fixups.h
dts_fix.h
文件的内容,通过
devicetree.h
进行
include
。
<build>/zephyr/zephyr.dts
最终合并后的
devicetree
。这个文件由
gen_definitions .py
输出,作为调试目的使用,没有其他用处。
<build>/zephyr/<BOARD>.dts.pre.tmp
预处理和连接的DTS
sources
和
overlays
。这是一个中间输出文件,用于创建
zephyr.dts
和
devicetree_unfixed.h
。
6.4 写属性值
下面是一些以DTS格式写入属性值的示例方法。为了尽量简单,省略了一些细节;如果对细节感兴趣,请参阅devicetree specification。
32位无符号整数数组或
cells
,可以写在尖括号(
<和>
)之间,并用空格分隔:
foo属性值有三个
cells
,值依次为
0xdeadbeef
、
1234
和
。注意,这里允许使用十六进制数和十进制数,并且可以混合使用。由于Zephyr将DTS转换为C源文件,因此没有必要在这里指定单个cell的字节顺序。
64位整数按大端顺序写成两个32位单元格。值
0xaaaa0000bbbb1111
写为
<0xaaaa0000 0xbbbb1111>
。
允许使用圆括号、算术操作符和位操作符。下面
bar
属性包含一个值为64的单元格:
字符串用双引号括起来:
字符串数组用逗号分隔:
字节数组以十六进制形式写入,方括号(
[和]
)之间不包含前导0x。下面的属性
a-byte-array
是三个字节0x00, 0x01, 0xab:
属性可以通过它们的
phandles
引用
devicetree
中的其他节点。可以使用
&label
写一个
phandle
,就像在这个
devicetree
片段中:
baz: device@0 {
/* ... */
};
device@1 {
sibling = <&baz 1 2>;
/* ... */
};
节点
[email protected]
的
sibling
属性包含三个单元格:
-
节点的[email protected]
。每个phandle
占据1个cell。phandle
标签用于在baz
属性中写入phandle值sibling
。&baz
- 值1和2,每个都按照这个顺序在自己的单元格中。