☰
Zephyr BSP: 18-Zephyr Devicetree 宏展开全解析
2026/9/29 6:30:50 网站建设 项目流程

摘要:本文源码级拆解DT_NODELABEL→devicetree_generated.h→ dependency ordinal →__device_dts_ord_xx→struct device的完整编译期映射链路,并手把手教你用grep/nm定位undefined reference to __device_dts_ord_xx链接错误。适合 Zephyr BSP 开发者。

18 — 手动拆解 devicetree_generated.h:从 DT_NODELABEL 到 struct device
这一篇非常关键。
前面你已经把 Zephyr 的链条一路追到了:

Devicetree ↓ DT compiler ↓ devicetree_generated.h ↓ DT_NODELABEL(...)↓ dependency ordinal ↓ device handle ↓ struct device

这一篇我们不再停留在宏 API 层面,而是直接"手撕"生成出来的 devicetree_generated.h,看看一个:

DT_NODELABEL(uart1)

最后到底是怎么找到:

struct device

的。

一、先建立目标:我们到底要追什么?
假设 Board 的 DTS 中有:

&usart1{status="okay";current-speed=<115200>;};

或者 SoC DTS 中:

usart1:usart@40013800{compatible="st,stm32-usart";reg=<0x400138000x400>;status="okay";};

应用程序可能写:

#defineUART_NODEDT_NODELABEL(usart1)conststructdevice*uart=DEVICE_DT_GET(UART_NODE);

然后:

if(!device_is_ready(uart)){return;}

我们真正要回答的问题是:
DT_NODELABEL(usart1) 明明只是一个宏,它怎么最终变成一个真实的 struct device *?

二、第一层:DT_NODELABEL(usart1) 到底是什么?

先看:

DT_NODELABEL(usart1)

表面上你可能认为:

usart1 ↓ 某个节点

但实际上它最终会映射到一个类似这样的内部 token:

DT_N_NODELABEL_usart1

也就是说:

DT_NODELABEL(usart1)

本质上是在生成一个:

DT_N_NODELABEL_usart1

这样的Devicetree node identifier。

三、注意:这个东西不是 struct device

这是理解 Zephyr Devicetree 的第一个关键点。

DT_NODELABEL(usart1)

得到的不是:

struct device *

甚至不是:

struct device

它只是一个:
Devicetree Node Identifier

也就是:

"Devicetree 中的这个节点是谁?"

例如:

DT_NODELABEL(usart1)│ ▼ Devicetreenode│ │ ▼ /soc/usart@40013800

所以:

DT_NODELABEL(usart1)

解决的是:
Node identification

而:

DEVICE_DT_GET(...)

解决的是:
Node → Device

这两个阶段不要混在一起。

四、第二层:打开 devicetree_generated.h

Zephyr build 完以后,可以找到类似:

build/zephyr/include/generated/zephyr/devicetree_generated.h

不同 Zephyr 版本/build system 路径可能略有区别。

你可以直接:

findbuild-namedevicetree_generated.h

然后:

grep-n"NODELABEL_usart1"\build/zephyr/include/generated/zephyr/devicetree_generated.h

你会看到大量类似:

# define DT_N_NODELABEL_usart1 ...

以及各种:

DT_N_S_soc_S_usart_40013800

之类的定义。

五、为什么生成文件里面名字这么奇怪?

例如:

/soc/usart@40013800

可能被编码成:

DT_N_S_soc_S_usart_40013800

这里:

DT_N

表示 Devicetree node。

S_soc

表示:

soc
S_usart_40013800

表示:

usart@40013800

因此你可以把它理解成:

DTS path ↓ /soc/usart@40013800 ↓ C identifier ↓ DT_N_S_soc_S_usart_40013800

这就是 Zephyr 为什么能够把 DTS 节点变成 C 预处理器可以操作的东西。

六、DT_NODELABEL() 实际上做了什么?

Zephyr 的 Devicetree macro system 会把:

DT_NODELABEL(usart1)

映射到:

DT_N_NODELABEL_usart1

然后:

DT_N_NODELABEL_usart1

又会对应到真正的 node identifier。
概念上可以理解成:

DT_NODELABEL(usart1)│ ▼ DT_N_NODELABEL_usart1 │ ▼ DT_N_S_soc_S_usart_40013800

于是:

# define UART_NODE DT_NODELABEL(usart1)

实际上已经把:

UART_NODE

绑定到了:

/soc/usart@40013800

这个 Devicetree node。

七、第三层:Node Identifier 怎么产生 dependency ordinal?
这里就是你上一章:
phandle → dependency ordinal → device handle

开始真正发挥作用的地方。
Zephyr 会给 Devicetree nodes 建立 dependency graph。

例如:

┌──────────────┐ │ interrupt │ │ controller │ └──────▲───────┘ │ │ interrupt-parent │ │ ┌─────────┐ ┌─────┴─────┐ │ clock │─────▶│ USART1 │ └─────────┘ └───────────┘ │ ▼ pinctrl

Devicetree compiler / Zephyr 的生成过程会根据这些 dependency 建立 ordinal。

例如概念上:

ordinal0interrupt controller ordinal5clock controller ordinal12pinctrl ordinal20USART1

所以 USART1 node 最终会拥有一个:

dependency ordinal=20

注意:
这个 ordinal 不是 UART 编号。

不是:

UART1=ordinal1

完全不是。

它是:
整个 Devicetree dependency graph 中这个 node 的唯一排序标识。

八、为什么需要 ordinal?

因为 Zephyr 最终需要解决一个问题:

Devicetree Node ↓ 对应哪个 struct device?

如果直接拿:

DT_N_S_soc_S_usart_40013800

作为 C symbol,当然也可以。
但 Zephyr 的 device model 还需要表达:

这个 device 依赖哪些 device?

于是 ordinal 就成为一个非常重要的桥梁。
概念上:

Devicetree Node │ ▼ dependency ordinal │ ▼ device object

九、第四层:看 DEVICE_DT_GET()

应用程序:

#defineUART_NODEDT_NODELABEL(usart1)conststructdevice*uart=DEVICE_DT_GET(UART_NODE);

这里是整个链条最重要的地方之一。
不要把:

DEVICE_DT_GET(UART_NODE)

理解成:

device_find("usart1")

它不是运行时查找。
没有:

hashtable lookup search string compare

这些东西。
它本质上是:
编译期把 Devicetree node 映射成一个 C symbol。

十、把宏展开成概念模型

你可以先不要管 Zephyr 真实宏定义里的各种:

DT_CAT(...)DT_DEP_ORD(...)DT_INVALID_NODE

先建立这个简化模型:

DEVICE_DT_GET(node)

≈

&DEVICE_OBJECT_FOR(node)

然后:

DEVICE_DT_GET(DT_NODELABEL(usart1))

≈

&device objectforUSART1

最终类似:

&\__device_dts_ord_20

十·补充、真实宏定义拆解:DT_NODELABEL 与 DEVICE_DT_GET 的源码级展开

上面我们建立了概念模型,现在直接打开 Zephyr 源码,看看这些宏在include/zephyr/devicetree.h和include/zephyr/device.h里到底是怎么定义的。这样你以后读源码时,就不会被层层DT_CAT绕晕。

1)DT_NODELABEL的源码定义(include/zephyr/devicetree.h)

/** * @brief Get a node identifier for a node label */#defineDT_NODELABEL(label)DT_CAT(DT_N_NODELABEL_,label)

逐行拆解:

  • label:宏参数,就是你在 DTS 里写的节点 label,例如usart1。
  • DT_CAT(a, b):Zephyr 的拼接宏,本质是a ## b,把两个 token 粘合成一个。
  • DT_N_NODELABEL_:固定前缀,表示「这是一个通过 node label 索引的 Devicetree node identifier」。

所以:

DT_NODELABEL(usart1)

展开为:

DT_CAT(DT_N_NODELABEL_,usart1)

再展开为:

DT_N_NODELABEL_usart1

而DT_N_NODELABEL_usart1这个宏,是在devicetree_generated.h里由构建系统生成的,它指向真正的 node identifier:

#defineDT_N_NODELABEL_usart1DT_N_S_soc_S_usart_40013800

2)DT_DEP_ORD的源码定义(include/zephyr/devicetree.h)

/** * @brief Get the dependency ordinal of a node */#defineDT_DEP_ORD(node_id)DT_CAT(node_id,_ORD)

逐行拆解:

  • node_id:宏参数,一个 Devicetree node identifier,例如DT_N_S_soc_S_usart_40013800。
  • _ORD:后缀,表示「dependency ordinal」。
  • DT_CAT(node_id, _ORD):把 node identifier 和_ORD拼起来,得到该节点的 ordinal 宏名。

所以:

DT_DEP_ORD(DT_N_S_soc_S_usart_40013800)

展开为:

DT_N_S_soc_S_usart_40013800_ORD

而devicetree_generated.h里会生成:

#defineDT_N_S_soc_S_usart_40013800_ORD20

于是DT_DEP_ORD(...)最终得到整数20。

3)DEVICE_DT_GET的源码定义(include/zephyr/device.h)

/** * @brief Get a device reference from a devicetree node */#defineDEVICE_DT_GET(node_id)\DEVICE_DT_GET_ONE(node_id)

再往下追,DEVICE_DT_GET_ONE最终会落到:

#defineDEVICE_DT_GET_ONE(node_id)\(&

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询