Zephyr 设备树访问

节点标识

在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)
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕咚.萌西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值