节点标识
在C/C++中访问设备树节点需要引用节点标识,节点标识本身不是一个值,不能够使用变量进行保存。
节点标识的获取方式
获取设备节点通常有以下几种方式:
- DT_PATH(path,node_name) 通过节点的完整路径进行引用。
- DT_ALIAS(alias) 通过查找/aliases下的同名节点进行引用。
- DT_NODELABEL(label) 通过节点标签进行引用。
- DT_INST(x, vnd_soc_i2c),通过实例号和节点中的compatible进行引用。
- x代表实例编号,vnd_soc_i2c为compatible对应值(属性中的非字母和数字字符会被替换成下划线,大写字母也会全部替换成小写),实例号以0为开始,为当设备树中有多个节点存在相同属性时候,设备树会对这些节点进行编号,包括未使能的节点,但是具体哪一个设备会分配哪一个实例号,这是不确定的,能确定的是未使能的节点排在使能节点之后。
- DT_CHOSEN(node_name) 通过查找/chosen下的同名节点进行引用。
- DT_PARENT(node)/DT_CHILD(node)) 通过当前节点索引父节点和子节点。
节点标识的本质
- 下面以 DT_NODELABEL 为例探究其展开后的结果,下面是 led0 对应的设备树:
/{
leds{
compatible = "gpio-leds";
led0: blue_led{
gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
label = "blue led";
};
};
};
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#define LED0_NODE DT_NODELABEL(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
int val = DT_NODELABEL(led0);
void main(void)
{
int ret;
if (!gpio_is_ready_dt(&led))
{
printk("led0 not ready\r\n");
return;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0)
{
return;
}
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return;
}
k_msleep(100);
}
}
- 为了直观的看到展开后的结果,我们可以使用变量保存 DT_NODELABEL(led0),这样肯定无法编译通过,但是编译出错后的提示信息可以看到展开过程和展开后的结果。
build/zephyr/include/generated/devicetree_generated.h:2197:29: error: 'DT_N_S_leds_S_blue_led' undeclared here (not in a function); did you mean 'DT_N_S_leds_S_blue_led_ORD'?
2197 | #define DT_N_NODELABEL_led0 DT_N_S_leds_S_blue_led
| ^~~~~~~~~~~~~~~~~~~~~~
workspace/zephyr/zephyr/include/zephyr/devicetree.h:4039:24: note: in expansion of macro 'DT_N_NODELABEL_led0'
4039 | #define DT_CAT(a1, a2) a1 ## a2
| ^~
workspace/zephyr/zephyr/include/zephyr/devicetree.h:192:29: note: in expansion of macro 'DT_CAT'
192 | #define DT_NODELABEL(label) DT_CAT(DT_N_NODELABEL_, label)
| ^~~~~~
workspace/zephyr/example/src/main.c:8:11: note: in expansion of macro 'DT_NODELABEL'
8 | int val = DT_NODELABEL(led0);
- 从上面的展开结果我们知道,DT_NODELABEL 通过将多个字符拼接最后得到 DT_N_S_leds_S_blue_led,然后继续查找 DT_N_S_leds_S_blue_led, 但是这个宏本身是不存在的,因此无法进行赋值,更不能进行输出打印。
- 那么通过 DT_NODELABEL 得到的这个结果有什么用呢?通过全局搜索我们没有找到 DT_N_S_leds_S_blue_led,但是在devicetree_generated.h 中有一堆以 DT_N_S_leds_S_blue_led 为前缀的宏,例如设备节点的路径,名称,父节点等全都可以找到。
- 从这里我们逐渐得到了事情的真相,Zephyr通过节点标识作为前缀,再使用不同功能的宏将字符进行拼接,最终可以得到我们需要的各种属性。
节点属性访问
属性是否存在
DT_NODE_HAS_PROP(node, property)
- 可使用 DT_NODE_HAS_PROP 进行条件编译,输出错误终止编译等,展开后为0或者1
获取普通属性
val = DT_PROP(node, property)
- 可使用 DT_PROP 获取设备树中对应的属性值,如果保存在变量中,变量的类型需要和设备树中属性类型保持一致。
len = DT_PROP_LEN(node, property)
- DT_PROP_LEN 用于获取数组元素个数
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 */
reg 属性
DT_NUM_REGS(node_id)
- DT_NUM_REGS 用于获取reg属性中的寄存器块总数,reg中寄存器块的数量不能使用DT_PROP(node,property)获取。
DT_REG_ADDR(node_id)
- 当寄存器块只有一个时,DT_REG_ADDR用于获取给定节点的寄存器块地址
DT_REG_SIZE(node_id): 它的大小
- 当寄存器块只有一个时,DT_REG_ADDR用于获取给定节点的寄存器数量
DT_REG_ADDR_BY_IDX(node_id, idx)
- 当寄存器块数量大于1个时,使用 DT_REG_ADDR_BY_IDX 替代 DT_REG_ADDR,index只能是常量,不能是变量
DT_REG_SIZE_BY_IDX(node_id, idx): 索引处块的大小idx
- 当寄存器块数量大于1个时,使用 DT_REG_SIZE_BY_IDX 替代 DT_REG_SIZE,index只能是常量,不能是变量
interrupt 属性
DT_NUM_IRQS(node_id)
- 给定节点标识符node_id,DT_NUM_IRQS 可获取节点 interrupts 属性中中断标识的总数。
DT_IRQ_BY_IDX(node_id, idx, val)
- 通常使用 DT_IRQ_BY_IDX 访问中断信号。
- idx为中断数组的索引,它用于访问单个中断。
- val是节点标识中单个单元格的名称。
- 如果需要使用该宏需要检查该节点的绑定文件以查找val。
phandle属性
使用 DT_PHANDLE(), DT_PHANDLE_BY_IDX(), 或 DT_PHANDLE_BY_NAME()可以将对应的phandle 转换为节点标识,具体取决于使用的属性类型。
通常情况下使用 phandle属性 是为了在其他设备树中的硬件中进行引用,这这种情况下,通常需要设备树层的 phandle 转换为驱动层的设备结构体。
其他常见用途是使用 DT_PHA_BY_IDX() 和 DT_PHA() 在 phandle 数组中访问标识的值。
还有一些特定硬件的简写形式:
- DT_GPIO_CTLR_BY_IDX()
- DT_GPIO_CTLR()
- DT_GPIO_PIN_BY_IDX()
- DT_GPIO_PIN()
- DT_GPIO_FLAGS_BY_IDX()
- DT_GPIO_FLAGS()
DT_PHA_HAS_CELL_AT_IDX() 和 DT_PROP_HAS_IDX() 用于检查 phandle 属性中是否存在对应的标识的值。
其他API
DT_CHOSEN(), DT_HAS_CHOSEN():
- 特殊节点 /chosen 的属性
DT_HAS_COMPAT_STATUS_OKAY(), DT_NODE_HAS_COMPAT()
- 全局和某个具体节点是否存在 compatible 相关的属性。
DT_BUS()
- 如果有总线控制器,DT_BUS 用于获取总线控制器
DT_ENUM_IDX()
- 对于其值在固定选择列表中的属性
设备驱动的便捷使用
一些特殊的宏可用于写入设备驱动,通常会依赖于实例的标识。在这种情况下,你首先需要定义 DT_DRV_COMPAT,其值为 DT_INST(idx, compat) 中的compat,然后你可以以更简短的方式访问某个实例的属性。
#include <zephyr/devicetree.h>
#define DT_DRV_COMPAT my_driver_compat
/* This is same thing as DT_INST(0, my_driver_compat): */
DT_DRV_INST(0)
/*
* This is the same thing as
* DT_PROP(DT_INST(0, my_driver_compat), clock_frequency)
*/
DT_INST_PROP(0, clock_frequency)