Zephyr可以通过API来获取设备树中定义的信息。

节点标识符

若要获取有关特定设备树节点的属性信息,需要为其提供节点标识符
以下是获取节点标识符的主要方法:

  • 按路径: 从根节点开始,在设备树中使用DT_PATH()以及节点的完整路径。
  • 按节点标签: 使用DT_NODELABEL()从节点标签中获取节点标识符。
  • 按别名: 使用DT_ALIAS()获取在/aliases节点下的特殊节点属性的节点标识符。
  • 按实例编号: 实例编号是一种基于匹配兼容性引用单个节点的方法。使用DT_INST()获取这些内容。
  • 按选择节点: 使用DT_CHOSEN() 获取在/chosen节点下的节点标识符。
  • 按父/子节点: 使用DT_PARENT()DT_CHILD()从已有的节点标识符开始,获取父节点或子节点的节点标识符。

引用同一节点的两个节点标识符是相同的,可以互换使用。
示例:

  1. /dts-v1/;
  2. / {
  3. aliases {
  4. sensor-controller = &i2c1;
  5. };
  6. soc {
  7. i2c1: i2c@40002000 {
  8. compatible = "vnd,soc-i2c";
  9. label = "I2C_1";
  10. reg = <0x40002000 0x1000>;
  11. status = "okay";
  12. clock-frequency = < 100000 >;
  13. };
  14. };
  15. };

以下是获取i2c@40002000节点标识符的几种方法:

  • DT_PATH(soc, i2c_40002000)
  • DT_NODELABEL(i2c1)
  • DT_ALIAS(sensor_controller)
  • DT_INST(x, vnd_soc_i2c)对于某个未知数字

设备树名称中的非字母数字字符将转换为下划线_。DTS 中的名称也会转换为小写。

基于实例的节点标识符

  1. serial@40001000 {
  2. compatible = "vnd,serial";
  3. status = "okay";
  4. current-speed = <115200>;
  5. };

这里基于实例获取serial@40001000的节点标识符和实例属性,如下:

  1. #define DT_DRV_COMPAT vnd_serial
  2. DT_DRV_INST(0) // node identifier for serial@40001000
  3. 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来获取实例里的属性了。

节点标识符的引用

无法使用变量来存储一个节点标识符,例如以下的写法都会报错:

  1. /* These will give you compiler errors: */
  2. void *i2c_0 = DT_INST(0, vnd_soc_i2c);
  3. unsigned int i2c_1 = DT_INST(1, vnd_soc_i2c);
  4. long my_i2c = DT_NODELABEL(i2c1);

可以使用宏来标识一个节点标识符:

  1. /* Use something like this instead: */
  2. #define MY_I2C DT_NODELABEL(i2c1)
  3. #define INST(i) DT_INST(i, vnd_soc_i2c)
  4. #define I2C_0 INST(0)
  5. #define I2C_1 INST(1)

节点属性的访问

检查属性和值

可以使用DT_NODE_HAS_PROP()检查节点是否具有属性。

  1. DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), clock_frequency) /* expands to 1 */
  2. DT_NODE_HAS_PROP(DT_NODELABEL(i2c1), not_a_property) /* expands to 0 */

简单属性

DT_PROP(node_id, property)用于读取:

  • 基本整数
  • 布尔值
  • 字符串
  • 数值数组
  • 字符串数组
    1. DT_PROP(DT_PATH(soc, i2c_40002000), clock_frequency) /* This is 100000, */
    2. DT_PROP(DT_NODELABEL(i2c1), clock_frequency) /* and so is this, */
    3. DT_PROP(DT_ALIAS(sensor_controller), clock_frequency) /* and this. */
    对于字符串,宏将扩展为字符串文本
    对于布尔值,宏将扩展为数字01

不要将DT_NODE_HAS_PROP()用于布尔属性。请改用DT_PROP()

对于数组类型的值,宏将扩展为数组。例如:

  1. foo: foo@1234 {
  2. a = <1000 2000 3000>; /* array */
  3. b = [aa bb cc dd]; /* uint8-array */
  4. c = "bar", "baz"; /* string-array */
  5. };

可以按如下方式访问其属性:

  1. #define FOO DT_NODELABEL(foo)
  2. int a[] = DT_PROP(FOO, a); /* {1000, 2000, 3000} */
  3. unsigned char b[] = DT_PROP(FOO, b); /* {0xaa, 0xbb, 0xcc, 0xdd} */
  4. char* c[] = DT_PROP(FOO, c); /* {"foo", "bar"} */

对于数组类型,也可以使用DT_PROP_LEN()获取以元素数为单位的逻辑数组长度。

  1. size_t a_len = DT_PROP_LEN(FOO, a); /* 3 */
  2. size_t b_len = DT_PROP_LEN(FOO, b); /* 4 */
  3. 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处的寄存器块的地址大小

其中索引必须是整数或者确定整数的宏,不能是变量或者表达式。以下的做法是错误的:

  1. /* This will cause a compiler error. */
  2. for (size_t i = 0; i < DT_NUM_REGS(node_id); i++) {
  3. size_t addr = DT_REG_ADDR_BY_IDX(node_id, i);
  4. }

interrupts属性

给定一个node_id节点标识符,DT_NUM_IRQS(node_id)是节点属性中中断说明符的总数。
用于访问interrupts属性API宏DT_IRQ_BY_IDX()

  1. DT_IRQ_BY_IDX(node_id, idx, val)
  • node_id:代表节点标识符
  • idx:代表此节点中interrupts属性里数组的逻辑索引
  • val: 代表选定的数组值中的val名称

大多数Zephyr设备树绑定都有一个名为irq的单元,即中断数。可以使用DT_IRQN()作为获取此值。

处理包含节点的属性值

例如:

  1. pwmleds {
  2. compatible = "pwm-leds";
  3. red_pwm_led {
  4. pwms = <&pwm3 4 15625000>;
  5. };
  6. green_pwm_led {
  7. pwms = <&pwm3 0 15625000>;
  8. };
  9. /* ... */
  10. };

在上面的示例中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里查看。