节点标识符
若要获取有关特定设备树节点
的属性信息,需要为其提供节点标识符
。
以下是获取节点标识符的主要方法:
- 按路径: 从
根节点
开始,在设备树中使用DT_PATH()
以及节点的完整路径。 - 按节点标签: 使用
DT_NODELABEL()
从节点标签中获取节点标识符。 - 按别名: 使用
DT_ALIAS()
获取在/aliases
节点下的特殊节点属性的节点标识符。 - 按实例编号:
实例编号
是一种基于匹配兼容性
引用单个节点的方法。使用DT_INST()
获取这些内容。 - 按选择节点: 使用
DT_CHOSEN()
获取在/chosen
节点下的节点标识符。 - 按父/子节点: 使用
DT_PARENT()
和DT_CHILD()
从已有的节点标识符开始,获取父节点或子节点的节点标识符。
引用同一节点的两个节点标识符是相同的,可以互换使用。
示例:
/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)对于某个未知数字
设备树名称中的非字母数字字符
将转换为下划线_
。DTS 中的名称也会转换为小写。
基于实例的节点标识符
serial@40001000 {
compatible = "vnd,serial";
status = "okay";
current-speed = <115200>;
};
这里基于实例获取serial@40001000
的节点标识符和实例属性,如下:
#define DT_DRV_COMPAT vnd_serial
DT_DRV_INST(0) // node identifier for serial@40001000
DT_INST_PROP(0, current_speed) // 115200
这里通过将DT_DRV_COMPAT
定义为设备树中compatible
属性的值,就代表我们要查vnd_serial
的实例,由于设备树中只有一个compatible
的属性为vnd,serial
,因此只有一个实例也就是serial@40001000
的节点标识符,之后我们用DT_DRV_INST
来定义实例serial@40001000
。之后就可以通过DT_INST_PROP
来获取实例里的属性了。
节点标识符的引用
无法使用变量来存储一个节点标识符,例如以下的写法都会报错:
/* These will give you compiler errors: */
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);
可以使用宏来标识一个节点标识符:
/* Use something like this instead: */
#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)
节点属性的访问
检查属性和值
可以使用DT_NODE_HAS_PROP()
检查节点是否具有属性。
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 */
简单属性
DT_PROP(node_id, property)
用于读取:
- 基本整数
- 布尔值
- 字符串
- 数值数组
- 字符串数组
对于字符串,宏将扩展为DT_PROP(DT_PATH(soc, i2c_40002000), clock_frequency) /* This is 100000, */
DT_PROP(DT_NODELABEL(i2c1), clock_frequency) /* and so is this, */
DT_PROP(DT_ALIAS(sensor_controller), clock_frequency) /* and this. */
字符串文本
。
对于布尔值,宏将扩展为数字0
或1
。
不要将
DT_NODE_HAS_PROP()
用于布尔属性。请改用DT_PROP()
对于数组类型的值,宏将扩展为数组。例如:
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"} */
对于数组类型,也可以使用DT_PROP_LEN()
获取以元素数为单位的逻辑数组长度。
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 */
reg属性
给定一个node_id
节点标识符,使用DT_NUM_REGS(node_id)
可以获取reg
节点属性中寄存器块的总数。reg
属性是无法使用DT_PROP
来获取值的。必须使用DT_REG_ADDR()
或DT_REG_SIZE()
。
如果节点只有一个寄存器块,则使用:
DT_REG_ADDR(node_id)
:给定节点的寄存器块地址DT_REG_SIZE(node_id)
:获取给定节点寄存器块地址的大小
如果节点有多个寄存器块,则使用:
DT_REG_ADDR_BY_IDX(node_id, idx)
:索引idx
处寄存器块的地址DT_REG_SIZE_BY_IDX(node_id, idx)
:索引idx
处的寄存器块的地址大小
其中索引必须是整数或者确定整数的宏,不能是变量或者表达式。以下的做法是错误的:
/* This will cause a compiler error. */
for (size_t i = 0; i < DT_NUM_REGS(node_id); i++) {
size_t addr = DT_REG_ADDR_BY_IDX(node_id, i);
}
interrupts属性
给定一个node_id
节点标识符,DT_NUM_IRQS(node_id)
是节点属性中中断说明符的总数。
用于访问interrupts属性
的API宏
是DT_IRQ_BY_IDX()
:
DT_IRQ_BY_IDX(node_id, idx, val)
- node_id:代表节点标识符
- idx:代表此节点中
interrupts
属性里数组的逻辑索引 - val: 代表选定的数组值中的
val
名称
大多数Zephyr设备树绑定都有一个名为irq
的单元,即中断数。可以使用DT_IRQN()
作为获取此值。
处理包含节点的属性值
例如:
pwmleds {
compatible = "pwm-leds";
red_pwm_led {
pwms = <&pwm3 4 15625000>;
};
green_pwm_led {
pwms = <&pwm3 0 15625000>;
};
/* ... */
};
在上面的示例中pwms
就是包含节点的属性值,这里的节点就是pwm3
。
可以使用DT_PHANDLE()
、DT_PHANDLE_BY_IDX()
或DT_PHANDLE_BY_NAME()
将节点值转换为节点标识符,具体取决于所使用的属性类型。
另一个常见用例是访问节点数组中的说明符。用于此目的的通用API DT_PHA_BY_IDX()
和DT_PHA()
。还有特定于硬件的API,如DT_GPIO_CTLR_BY_IDX()
、DT_GPIO_CTLR()
、DT_GPIO_LABEL_BY_IDX()
、DT_GPIO_LABEL()
、DT_GPIO_PIN_BY_IDX()
、DT_GPIO_PIN()
、DT_GPIO_FLAGS_BY_IDX()
和DT_GPIO_FLAGS()
。
设备树其他API
上面我们介绍的都是一些设备树的基本API,还有一些其他的API,可以到Zephyr的设备API里查看。