目录
1 介绍
2 dts构建流程
3 dts binds
3.1 作用
3.2 如何匹配设备树节点
4 .overlays使用
4.1 重写节点属性
4.2 添加aliase和chosen节点
4.3 添加子节点
5 API使用
5.1 使用注意事项
5.2 获取节点标识符
5.3 DT_INST
5.4 获取节点属性
1)节点是否有该属性
2)简单属性获取
3)reg属性获取
4)获取struct device
zephyr的设备树管理和linux类似,需要提前了解一些linux设备树的知识。设备树简单理解就是将硬件相关数据例如几个I2C,每个I2C控制器的寄存器地址等等,统一按设备树的结构独立于内核进行配置和修改,这里需要了解的就是设备树语法知识。有了设备树文件后,如何让内核代码读取这些信息,进行板级初始化了?linux的做法是编译成DTB文件,然后在内核启动时进行解析,逐一获取硬件数据。Zephyr系统设计的前提是资源受限的小型系统,这里将大量解析工作放到构建编译阶段,通过脚本将设备树文件中的各数据转换为头文件,使用各种宏替代,然后通过API给内核使用者调用。
设备树绑定描述了对节点内容的要求,并提供关于有效节点内容的语义信息。Zephyr设备树绑定是自定义格式的YAML文件(Zephyr不使用Linux内核使用的dt-schema工具)
在配置阶段,构建系统尝试将设备树中的每个节点匹配到对应绑定文件。当此操作无误时,构建系统在验证节点的内容和为节点生成宏时都将使用绑定文件中的信息
/* 这是一个设备树文件的一个节点信息 */
bar-device {
compatible = "foo-company,bar-device";
num-foos = <3>;
};
//这是该节点对应的binds文件
//通过属性compatible进行匹配
//约束了节点bar-device相关信息
compatible: "foo-company,bar-device"
properties:
num-foos:
type: int //约束数据类型时int
required: true //约束了该节点必须有num-foos,否则构建时会出错
//在.dts有如下节点
/ {
soc {
serial0: serial@40002000 {
status = "okay";
current-speed = <115200>;
/* ... */
};
};
};
//在.overlays文件中有2种方式重新配置属性
/* Option 1 */
&serial0 {
current-speed = <9600>;
};
/* Option 2 */
&{/soc/serial@40002000} {
current-speed = <9600>;
};
//例如.dts中有&serial0节点,在overlay文件中单独添加aliase和chosen,方便使用
/ {
aliases {
my-serial = &serial0;
};
};
/ {
chosen {
zephyr,console = &serial0;
};
};
经常会在SPI,I2C这样的bus node中添加子设备节点,此时就可以使用到.overlay文件添加.
//例如.dts中有&spi1节点,下面就可以在其添加子设备节点
/* SPI device example */
&spi1 {
my_spi_device: temp-sensor@0 {
compatible = "...";
label = "TEMP_SENSOR_0";
/* reg is the chip select number, if needed;
* If present, it must match the node's unit address. */
reg = <0>;
/* Configure other SPI device properties as needed.
* Find your device's DT binding for details. */
spi-max-frequency = <4000000>;
};
};
前面已经讲过,和linux设备树最大的差异就是,zephyr在构建过程中将设备树文件各节点信息转换为宏定义,最终生成头文件devicetree.h,提供相应的API给内核使用者调用。
1)几种方式
获取一个节点的办法有如下几种:
/dts-v1/;
/ {
aliases {
sensor-controller = &i2c1;
};
soc {
i2c1: i2c@40002000 {
compatible = "vnd,soc-i2c";
label = "I2C_1";
reg = <0x40002000 0x1000>;
status = "okay";
clock-frequency = < 100000 >;
};
};
};
//如何获取i2c@40002000节点了?
//注意:非字母数字的字符都需改为下划线,大写字母都需改为小写字母
DT_PATH(soc, i2c_40002000)
DT_NODELABEL(i2c1)
DT_ALIAS(sensor_controller)
DT_INST(x, vnd_soc_i2c)
2)注意事项
//下面的写法会编译出错
void *i2c_0 = DT_INST(0, vnd_soc_i2c);
unsigned int i2c_1 = DT_INST(1, vnd_soc_i2c);
long my_i2c = DT_NODELABEL(i2c1);
//应该使用宏替代
#define MY_I2C DT_NODELABEL(i2c1)
#define INST(i) DT_INST(i, vnd_soc_i2c)
#define I2C_0 INST(0)
#define I2C_1 INST(1)
其他的API比较好理解,这里单独详细描述下DT_INST()的使用。
在设备树中,具有相同compatible属性的节点,会通过实例号来区分,一般常用与驱动描述中。但存在实例号分配不确定性,看下面的举例就明白了
//具设备树有同样compatible的3个节点,通过实例号0,1,2来区分。
//首先看节点状态,由于节点serial@40002000和serial@40003000是使能的,所有会分配实例号0和1,但不保证谁一定分配0或者1。
//节点serial@40001000状态是不使能的,所有分配最大实例号2.
serial@40001000 {
compatible = "vnd,soc-serial";
status = "disabled";
current-speed = <9600>;
...
};
serial@40002000 {
compatible = "vnd,soc-serial";
status = "okay";
current-speed = <57600>;
...
};
serial@40003000 {
compatible = "vnd,soc-serial";
current-speed = <115200>;
...
};
PS:这里的实例号分配不确定性是官方文档描述的
//节点DT_NODELABEL(i2c1)是否有clock_frequency或者not_a_property属性
DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), clock_frequency) /* expands to 1 */
DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), not_a_property) /* expands to 0 */
PS:该API不能对boolean类型的属性使用
//某节点描述如下
foo: foo@1234 {
a = <1000 2000 3000>; /* array */
b = [aa bb cc dd]; /* uint8-array */
c = "bar", "baz"; /* string-array */
};
//获取一些简单属性方式
#define FOO DT_NODELABEL(foo)
int a[] = DT_PROP(FOO, a); /* {1000, 2000, 3000} */
unsigned char b[] = DT_PROP(FOO, b); /* {0xaa, 0xbb, 0xcc, 0xdd} */
char* c[] = DT_PROP(FOO, c); /* {"foo", "bar"} */
size_t a_len = DT_PROP_LEN(FOO, a); /* 3 */
size_t b_len = DT_PROP_LEN(FOO, b); /* 4 */
size_t c_len = DT_PROP_LEN(FOO, c); /* 2 */
//某设备树节点如下
rcc: rcc@40021000 {
compatible = "st,stm32-rcc";
#clock-cells = < 0x2 >;
reg = < 0x40021000 0x400 >;
};
//获取addr和size
#define RCC DT_NODELABEL(rcc)
DT_REG_ADDR(RCC) //返回0x40021000
DT_REG_SIZE(RCC) //返回0x400
PS:idx不能传递一个变量,必须是一个固定值。
//某设备树节点如下
rcc: rcc@40021000 {
compatible = "st,stm32-rcc";
#clock-cells = < 0x2 >;
reg = < 0x40021000 0x400 0x40022000 0x500>;
};
//获取addr和size
#define RCC DT_NODELABEL(rcc)
DT_NUM_REGS(RCC) //返回2
DT_REG_ADDR_BY_IDX(RCC, 0) //返回0x40021000
DT_REG_SIZE_BY_IDX(RCC, 0) //返回0x400
//如下写法会报错,DT_REG_ADDR_BY_IDX(node_id, idx)中的idx不能为变量
for (size_t i = 0; i < DT_NUM_REGS(node_id); i++) {
size_t addr = DT_REG_ADDR_BY_IDX(node_id, i);
}
1.首先获得节点标识符,可以回顾 “获取节点标识符”
/ {
soc {
serial0: serial@40002000 {
status = "okay";
current-speed = <115200>;
/* ... */
};
};
aliases {
my-serial = &serial0;
};
chosen {
zephyr,console = &serial0;
};
};
//1. 获取节点标识符,下面罗列了4种方式
/* Option 1: by node label */
#define MY_SERIAL DT_NODELABEL(serial0)
/* Option 2: by alias */
#define MY_SERIAL DT_ALIAS(my_serial)
/* Option 3: by chosen node */
#define MY_SERIAL DT_CHOSEN(zephyr_console)
/* Option 4: by path */
#define MY_SERIAL DT_PATH(soc, serial_40002000)
2.有两种方式获取device:
2.1 典型的用法是通过device_get_binding(DT_LABEL(节点标识符))
//PS:DT_LABEL()参数传入设备标识符即可,无需一定使用DT_NODELABEL()获取的标识符
const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
2.2 第二种是使用DEVICE_DT_GET(节点标识符)
const struct device *uart_dev = DEVICE_DT_GET(MY_SERIAL);
if (!device_is_ready(uart_dev)) {
/* Not ready, do not use */
return -ENODEV;
}
3 异常检查
获取struct device切记调用一些API确认该dev能够使用。
//通过DT_NODE_HAS_STATUS()验证
#define MY_SERIAL DT_NODELABEL(my_serial)
#if DT_NODE_HAS_STATUS(MY_SERIAL, okay)
const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
#else
#error "Node is disabled"
#endif
//或者通过device_is_ready()
const struct device *uart_dev = DEVICE_DT_GET(MY_SERIAL);
if (!device_is_ready(uart_dev)) {
/* Not ready, do not use */
return -ENODEV;
}