代码版本:
zephyr/VERSION=4.4.99
重点源码:cmake/modules/dts.cmake、scripts/dts/{gen_edt.py,gen_defines.py}、include/zephyr/devicetree.h、include/zephyr/device.h、include/zephyr/init.h、kernel/init.c、kernel/device.c、include/zephyr/pm/device.h
友情提示:这篇文章主要是为了能更好的分析后续蓝牙相关代码写的,并未深入分析dts是怎么生成的,自从有了AI,工具脚本相关代码就不怎么仔细研究了。如果想深入研究的,可以参考该链接:https://www.cnblogs.com/jayant97/articles/17209392.html
先给你一个“看懂设备树”的最短闭环
如果你是第一次接触 Zephyr 设备树,建议不要一上来就死磕所有宏定义。先把“它到底在做什么”这个问题抓住:
- 设备树负责“描述硬件”;
DT_*/DEVICE_DT_*宏负责“把描述变成 C 语言可用的对象”;- 系统启动时,再把这些对象按优先级和初始化阶段执行起来。
最小闭环可以这样理解:
设备树节点(.dts / .dtsi / overlay)
└─ DT_DRV_COMPAT / DT_DRV_INST(inst)
└─ DT_N_INST_0_... -> DT_N_S_...
└─ DEVICE_DT_INST_DEFINE(...)
└─ 生成 struct device + init entry
└─ 启动时由 z_sys_init_run_level() 调用
└─ dev->ops.init(...)
这条链路里,最值得你记住的 5 个点是:
DT_DRV_COMPAT:当前驱动支持哪种 compatible。DT_DRV_INST(inst):当前驱动的第几个实例。DEVICE_DT_INST_DEFINE(...):把设备树节点和驱动数据、配置、API 绑定成一个struct device。Z_DEVICE_INIT_ENTRY_DEFINE():生成 init entry,供系统启动时调用。z_sys_init_run_level():启动阶段按PRE_KERNEL/POST_KERNEL/APPLICATION依次执行。
把它们串起来看,你就会明白:设备树不是“只给你看配置”,而是“把硬件描述和驱动初始化机制真正连接起来”。
一个最贴近真实代码的例子
本文的贯穿实例是 nRF54L 系列 SoC 的 Zephyr 原生软件蓝牙控制器(compatible = zephyr,bt-hci-ll-sw-split)。
它涉及两个文件:
zephyr/subsys/bluetooth/controller/hci/hci_driver.c(驱动侧,负责“生成设备对象”);zephyr/subsys/bluetooth/host/hci_core.c(host 侧,负责“拿到并使用设备对象”)。
/* hci_core.c */
#define BT_HCI_NODE DT_CHOSEN(zephyr_bt_hci)
#define BT_HCI_DEV DEVICE_DT_GET(BT_HCI_NODE)
BT_HCI_DEV 从 chosen node zephyr_bt_hci 取出的设备对象,正是 hci_driver.c 里 DEVICE_DT_INST_DEFINE(...) 生成的那一个 —— 两边通过同一个设备树节点“对上了号”。
具体怎么定义、怎么匹配、何时初始化、如何使用,见第二~五章。
建议的阅读顺序(初学者)
如果你想快速入门,建议按下面顺序看:
- 先看
include/zephyr/devicetree.h:理解DT_*宏是什么。 - 再看
include/zephyr/device.h:理解DEVICE_DT_*宏如何生成设备对象。 - 然后看
kernel/init.c:理解启动时如何调用这些设备。 - 最后再回头看
cmake/modules/dts.cmake和scripts/dts/*.py:理解设备树是如何生成这些宏的。
这样看,知识点会从“宏名看起来非常抽象”变成“从设备树到系统初始化的一条清晰链路”。
一、总体架构
Zephyr 的设备树用于把硬件描述从驱动代码中剥离出来:
硬件事实(SoC/board/shield/app overlay)
├─ 地址、中断、时钟、GPIO、bus 拓扑
├─ compatible 与 binding
└─ status/属性覆盖
构建期转换
├─ 合并 DTS
├─ edtlib 解析 binding
├─ 生成 C 预处理宏
└─ 生成设备对象/初始化入口
驱动代码
├─ 通过 DT_* 宏取属性
├─ 通过 DEVICE_DT_* 宏实例化设备
└─ 通过 DEVICE_DT_GET()/device_is_ready() 使用设备
核心价值:同一驱动适配多个 SoC/板级硬件;硬件配置在构建期检查;驱动不需要硬编码寄存器地址、中断号、GPIO 编号。
本文以 zephyr,bt-hci-ll-sw-split 为贯穿实例,从驱动中最常见的宏 DEVICE_DT_INST_DEFINE() 入手,沿“定义 → 匹配 → 初始化 → 使用”的调用链逐层分析。
二、怎么定义的:从 DTS 节点到驱动代码
以 zephyr,bt-hci-ll-sw-split 为例,一个设备要能存在,需要三处“对齐”:设备树节点(硬件在哪)、binding(属性怎么解释)、驱动代码(谁来实例化)。
2.1 设备树节点:SoC 声明,board 使能
节点首先在 SoC 级 dtsi 中声明,例如 zephyr/dts/vendor/nordic/nrf54l_05_10_15.dtsi:
bt_hci_controller: bt_hci_controller {
compatible = "zephyr,bt-hci-ll-sw-split";
status = "disabled";
};
-
bt_hci_controller:是 node label,后面可用DT_NODELABEL(bt_hci_controller)引用; -
SoC 级
nrf54l_05_10_15.dtsi默认disabled,由 SoC 应用核的zephyr/dts/arm/nordic/nrf54l_05_10_15_cpuapp.dtsi:40使能:&bt_hci_controller { status = "okay"; };在合并后的
build/zephyr/zephyr.dts中可见使能来源标注:status = "okay"; /* in zephyr/dts/arm/nordic/nrf54l_05_10_15_cpuapp.dtsi:40 */; -
本实例最终路径为
/soc/peripheral@50000000/radio@8a000/bt_hci_controller(zephyr.dts:467注释可确认)。
2.2 Binding:compatible 的属性定义
zephyr/dts/bindings/bluetooth/zephyr,bt-hci-ll-sw-split.yaml:
description: Bluetooth HCI provided by the native Zephyr Bluetooth Controller
compatible: "zephyr,bt-hci-ll-sw-split"
include: bt-hci.yaml
properties:
bt-hci-name:
default: "Controller"
bt-hci-bus:
default: "virtual"
bt-hci-quirks:
default: ["no-auto-dle"]
include: bt-hci.yaml引入公共 HCI 属性;- 构建期
gen_defines.py会据此为节点生成DT_N_..._P_bt_hci_quirks等宏。
2.3 驱动侧:DT_DRV_COMPAT + DEVICE_DT_INST_DEFINE
zephyr/subsys/bluetooth/controller/hci/hci_driver.c:
#define DT_DRV_COMPAT zephyr_bt_hci_ll_sw_split
...
static DEVICE_API(bt_hci, hci_driver_api) = {
.open = hci_driver_open,
.close = hci_driver_close,
.send = hci_driver_send,
};
#define BT_HCI_CONTROLLER_INIT(inst) \
static struct bt_hci_driver_data data_##inst; \
static const struct bt_hci_driver_config config_##inst = \
BT_DT_HCI_DRIVER_CONFIG_INST_GET(inst); \
DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &data_##inst, &config_##inst, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&hci_driver_api)
/* Only a single instance is supported */
BT_HCI_CONTROLLER_INIT(0)
要点:
DT_DRV_COMPAT与 binding 的compatible一一对应(zephyr,bt-hci-ll-sw-split→zephyr_bt_hci_ll_sw_split);data_0:驱动运行时数据,struct bt_hci_driver_data第一个成员是 host 注册进来的recv回调;config_0:BT_DT_HCI_DRIVER_CONFIG_INST_GET(0)从设备树生成{ .quirks = ... };hci_driver_api:.open/.close/.send三个回调;- 只实例化
inst = 0(单实例支持,注释已说明)。
三、怎么一步一步匹配上的(宏展开链)
DEVICE_DT_INST_DEFINE(0, ...) 是怎么“认识”到 /soc/.../bt_hci_controller 的?靠的是构建期生成的 devicetree_generated.h。
3.1 第一步:DEVICE_DT_INST_DEFINE → DEVICE_DT_DEFINE(DT_DRV_INST(0), …)
include/zephyr/device.h:
#define DEVICE_DT_INST_DEFINE(inst, ...) \
DEVICE_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__)
3.2 第二步:DT_DRV_INST(0) → DT_INST(0, zephyr_bt_hci_ll_sw_split) → DT_N_INST_0_zephyr_bt_hci_ll_sw_split
include/zephyr/devicetree.h:
#define DT_DRV_INST(inst) DT_INST(inst, DT_DRV_COMPAT)
#define DT_INST(inst, compat) UTIL_CAT(DT_N_INST, DT_DASH(inst, compat))
#define DT_DASH(...) MACRO_MAP_CAT(DT_DASH_PREFIX, __VA_ARGS__)
#define DT_DASH_PREFIX(name) _##name
DT_INST 本质只是“拼宏名”:0 和 zephyr_bt_hci_ll_sw_split 被拼成 DT_N_INST_0_zephyr_bt_hci_ll_sw_split。它自己并不知道节点在哪,真正的映射在下一步。
3.3 第三步:生成头文件把宏名映射到真实节点
build/zephyr/include/generated/zephyr/devicetree_generated.h:
#define DT_N_INST_0_zephyr_bt_hci_ll_sw_split \
DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller
DT_N_S_... 是按路径生成的节点标识符,对应:
/soc/peripheral@50000000/radio@8a000/bt_hci_controller
至此,DT_DRV_INST(0) 才真正指向了那个设备树节点。
3.4 第四步:DEVICE_DT_DEFINE(node_id, …) 生成设备对象与 init entry
#define DEVICE_DT_DEFINE(node_id, init_fn, pm, data, config, level, prio, api, ...) \
DEVICE_DT_DEINIT_DEFINE(node_id, init_fn, NULL, pm, data, config, \
level, prio, api, __VA_ARGS__)
内部展开链:
DEVICE_DT_DEFINE(node_id, NULL, NULL, &data_0, &config_0, POST_KERNEL, ...)
└─ DEVICE_DT_DEINIT_DEFINE(...)
├─ Z_DEVICE_STATE_DEFINE(...) ← struct device_state(RAM)
└─ Z_DEVICE_DEFINE(...)
├─ 可选 Z_DEVICE_DEPS_DEFINE()
├─ 可选 Z_DEVICE_DT_METADATA_DEFINE()
├─ Z_DEVICE_BASE_DEFINE(...) ← struct device(放 iterable section)
│ └─ STRUCT_SECTION_ITERABLE_NAMED_ALTERNATE(device, ...)
└─ Z_DEVICE_INIT_ENTRY_DEFINE() ← struct init_entry(放 init section)
Z_DEVICE_BASE_DEFINE() 生成的设备对象形如:
static const struct device __device_dts_ord_<N> = {
.name = "bt_hci_controller", /* DEVICE_DT_NAME(node_id) */
.config = &config_0,
.api = &hci_driver_api,
.state = &__devstate_dts_ord_<N>,
.data = &data_0,
.ops = { .init = NULL, ... },
.flags = 0,
};
小知识:
__device_dts_ord_<N>里的<N>是节点在依赖图中的 ordinal(DT_DEP_ORD)。所以当DEVICE_DT_GET()报undefined reference to __device_dts_ord_<N>时,说明该节点没有对应的驱动实例化设备。
四、什么时候初始化
4.1 init entry:设备和 SYS_INIT 的统一入口
Z_DEVICE_INIT_ENTRY_DEFINE() 创建初始化入口:
static const Z_DECL_ALIGN(struct init_entry) __used __noasan
Z_INIT_ENTRY_SECTION(level, prio, Z_DEVICE_INIT_SUB_PRIO(node_id))
Z_INIT_ENTRY_NAME(DEVICE_NAME_GET(dev_id)) = {
.init_fn = NULL, /* 设备 init entry 的 init_fn 为 NULL */
.dev = (const struct device *)&DEVICE_NAME_GET(dev_id), /* dev 指向设备对象 */
};
设备 init entry 的 init_fn 为 NULL,dev 指向设备对象;普通 SYS_INIT() 的 entry 则相反(init_fn != NULL, dev = NULL)。
include/zephyr/init.h 当前 struct init_entry:
struct init_entry {
int (*init_fn)(void);
const struct device *dev;
};
4.2 启动时遍历 init entry
本实例注册在 POST_KERNEL 级、CONFIG_KERNEL_INIT_PRIORITY_DEVICE 优先级。kernel/init.c 的 z_sys_init_run_level():
for (entry = levels[level]; entry < levels[level + 1]; entry++) {
const struct device *dev = entry->dev;
int result = 0;
if (dev != NULL) {
/* 设备 init entry:调用 dev->ops.init */
if ((dev->flags & DEVICE_FLAG_INIT_DEFERRED) == 0U) {
result = do_device_init(dev);
}
} else {
/* 普通 SYS_INIT entry */
result = entry->init_fn();
}
}
启动阶段顺序:
z_cstart()
├─ z_sys_init_run_level(EARLY)
├─ arch_kernel_init()
├─ z_device_state_init()
├─ soc_early_init_hook() / board_early_init_hook()
├─ z_sys_init_run_level(PRE_KERNEL_1)
├─ arch_smp_init()
├─ z_sys_init_run_level(PRE_KERNEL_2)
└─ 切到 main thread -> bg_thread_main()
bg_thread_main()
├─ z_sys_init_run_level(POST_KERNEL) ← 本实例在此阶段被“过一遍”
├─ soc_late_init_hook() / board_late_init_hook()
├─ z_sys_init_run_level(APPLICATION)
├─ z_init_static_threads()
└─ CONFIG_SMP: z_sys_init_run_level(SMP)
4.3 do_device_init()
int do_device_init(const struct device *dev)
{
int rc = 0;
if (dev->ops.init != NULL) {
rc = dev->ops.init(dev);
if (rc != 0) {
dev->state->init_res = -rc; /* init 失败时保存正的 errno */
}
}
dev->state->initialized = true; /* 无论成功失败都标记 initialized */
/* 成功后自动执行 runtime PM auto enable */
if (rc == 0) {
(void)pm_device_runtime_auto_enable(dev);
}
return -rc;
}
4.4 本实例的特殊性:boot 时其实“什么都没做”
注意 BT_HCI_CONTROLLER_INIT(0) 传给 DEVICE_DT_INST_DEFINE 的 init_fn = NULL:
- 启动时
dev->ops.init(dev)是空操作,仅把state->initialized置 true; - 真正的初始化发生在 host 调用
bt_enable()→bt_hci_open()时,由hci_driver_open()完成:ll_init()、创建recv_thread/prio_recv_thread等。
这是 HCI“软设备”与传感器驱动最大的不同:传感器在 boot 时就探测硬件;HCI 控制器要等 host 主动打开。
五、后续怎么使用(chosen + DEVICE_DT_GET)
5.1 host 怎么找到它:chosen node
zephyr/subsys/bluetooth/host/hci_core.c:
#if DT_HAS_CHOSEN(zephyr_bt_hci)
#define BT_HCI_NODE DT_CHOSEN(zephyr_bt_hci)
#define BT_HCI_DEV DEVICE_DT_GET(BT_HCI_NODE)
#endif
生成头文件里:
#define DT_CHOSEN_zephyr_bt_hci \
DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller
与驱动实例(3.3 节)指向同一个节点 —— 这就是匹配闭环。
5.2 bt_dev.hci 持有设备指针
struct bt_dev bt_dev = {
...
.hci = BT_HCI_DEV, /* = &__device_dts_ord_<N> */
};
5.3 打开与双向数据流
bt_enable() → bt_hci_open(bt_dev.hci, bt_recv)(hci_core.c):
static inline int bt_hci_open(const struct device *dev, bt_hci_recv_t recv)
{
struct bt_hci_driver_data *data = dev->data;
data->recv = recv; /* host 注册收包回调 */
err = DEVICE_API_GET(bt_hci, dev)->open(dev); /* → hci_driver_open() */
...
}
Host → Controller: bt_hci_send(bt_dev.hci, buf)
└─ DEVICE_API_GET(bt_hci, dev)->send(dev, buf)
└─ hci_driver_send() → cmd_handle()/acl_handle()/iso_handle()
Controller → Host: recv_thread() → bt_hci_recv(dev, buf)
└─ data->recv(dev, buf) /* 即 host 注册的 bt_recv */
注意:
bt_hci_open()通过DEVICE_API_GET(bt_hci, dev)取设备对象里的api字段(&hci_driver_api)再调.open;而data_0里的recv字段则是 host 反向注册给驱动用的。这一“取”一“注册”,正好对应 3.4 节设备对象里的.api与.data。
六、DTS 构建流程
本文 2.3 节 config_0 用到的 BT_DT_HCI_DRIVER_CONFIG_INST_GET(0)、3.3 节的 DT_N_INST_0_... 等宏,最终都来自构建期生成的 devicetree_generated.h(由 binding 的属性默认值展开而来,例如 bt-hci-quirks 的默认 ["no-auto-dle"])。
cmake/modules/dts.cmake 中的关键变量:
set(GEN_EDT_SCRIPT ${DT_SCRIPTS}/gen_edt.py)
set(GEN_DEFINES_SCRIPT ${DT_SCRIPTS}/gen_defines.py)
set(EDT_PICKLE ${PROJECT_BINARY_DIR}/edt.pickle)
set(ZEPHYR_DTS ${PROJECT_BINARY_DIR}/zephyr.dts)
set(DEVICETREE_GENERATED_H ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h)
set(DTS_POST_CPP ${PROJECT_BINARY_DIR}/zephyr.dts.pre)
构建流程:
DTS 输入 C preprocessor gen_edt.py gen_defines.py
├─ SoC .dtsi ──┐ ┌── 输入:zephyr.dts.pre ── 输出: ┌── 输入:edt.pickle
├─ board .dts ├── zephyr.dts.pre │ + bindings dirs ├─ zephyr.dts ├─ 输出:devicetree_generated.h
├─ shield .overlay └─ ──┘ └─ edt.pickle └─ 输出:dts_bindings_used.txt
└─ app overlay
C 代码
└─ #include <zephyr/devicetree.h>
└─ #include <zephyr/devicetree_generated.h>
调试时常看的文件:
| 文件 | 用途 |
|---|---|
build/zephyr/zephyr.dts | 合并后的最终 DTS,适合人工阅读 |
build/zephyr/zephyr.dts.pre | 预处理后的中间 DTS |
build/zephyr/edt.pickle | edtlib 的结构化解析结果 |
build/zephyr/include/generated/zephyr/devicetree_generated.h | C 宏生成结果 |
build/zephyr/dts_bindings_used.txt | 本次构建使用到的 binding 列表 |
七、常用 devicetree 宏
7.1 先看懂 DT_N_... 这类名字
你在生成头文件里经常会看到像下面这样的宏名:
DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller
DT_N_INST_0_zephyr_bt_hci_ll_sw_split
DT_N_NODELABEL_uart0
它们并不是手写出来的,而是构建系统根据设备树生成的 C 预处理符号。可以把它们理解为:
DT_N_:表示“这是一个 devicetree 节点标识符”;DT_N_S_...:按节点路径生成的节点;DT_N_INST_0_<compat>:某个 compatible 的第 0 个实例;DT_N_NODELABEL_<label>:通过 node label 引用的节点;DT_N_ALIAS_<alias>:通过/aliases引用的节点。
例如:
DT_INST(0, zephyr_bt_hci_ll_sw_split)
会被展开为:
DT_N_INST_0_zephyr_bt_hci_ll_sw_split
而这个宏又会在生成头文件中映射到某个实际节点,例如:
#define DT_N_INST_0_zephyr_bt_hci_ll_sw_split \
DT_N_S_soc_S_peripheral_50000000_S_radio_8a000_S_bt_hci_controller
这就是为什么你看见 DT_N_... 这类名字时,先别慌:它们本质上就是“设备树节点的 C 语言名字”。
| 宏 | 用途 |
|---|---|
DT_NODELABEL(label) | 通过 node label 获取节点 ID |
DT_ALIAS(alias) | 通过 /aliases 获取节点 ID |
DT_PATH(...) | 通过路径获取节点 ID |
DT_PROP(node_id, prop) | 读取属性 |
DT_PROP_OR(node_id, prop, default) | 属性不存在时使用默认值 |
DT_REG_ADDR(node_id) / DT_REG_SIZE(node_id) | 读取 reg 地址/大小 |
DT_IRQN(node_id) | 读取中断号 |
DT_INST_PROP(inst, prop) | 读取当前 compatible 实例属性 |
DT_INST_FOREACH_STATUS_OKAY(fn) | 遍历所有 okay 实例 |
DT_NODE_HAS_STATUS(node_id, okay) | 判断节点状态 |
DT_NODE_HAS_PROP(node_id, prop) | 判断属性存在 |
DT_PHA(node_id, phs, cell) | 读取 phandle-array 的 cell |
推荐尽量使用 *_dt_spec 辅助结构:
struct gpio_dt_spec reset = GPIO_DT_SPEC_GET(node_id, reset_gpios);
struct i2c_dt_spec i2c = I2C_DT_SPEC_GET(node_id);
struct spi_dt_spec spi = SPI_DT_SPEC_GET(node_id, SPI_WORD_SET(8), 0);
这些 helper 会把 bus device、地址/chip select、GPIO flags 等组合好,减少手写错误。
八、设备 PM 资源
当前设备 PM 资源在 include/zephyr/pm/device.h 中定义:
#define PM_DEVICE_DT_DEFINE(node_id, pm_action_cb, ...) \
Z_PM_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id), pm_action_cb, ...)
#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb, ...) \
Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx), \
Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)), \
pm_action_cb, ...)
#define PM_DEVICE_GET(dev_id) Z_PM_DEVICE_GET(dev_id)
典型组合(PM 资源和 device 分开定义,以下为通用示例,用传感器 temp_* 命名;本文 HCI 实例未启用 PM,故 2.3 节中 pm 参数直接传 NULL):
static int temp_pm_action(const struct device *dev,
enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
return temp_resume(dev);
case PM_DEVICE_ACTION_SUSPEND:
return temp_suspend(dev);
default:
return -ENOTSUP;
}
}
#define TEMP_DEFINE(inst) \
PM_DEVICE_DT_INST_DEFINE(inst, temp_pm_action); \
DEVICE_DT_INST_DEFINE(inst, \
temp_init, \
PM_DEVICE_DT_INST_GET(inst), \
&temp_data_##inst, \
&temp_cfg_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&temp_api);
DEVICE_DT_INST_DEFINE() 创建 device;PM_DEVICE_DT_INST_DEFINE() 创建 PM resources;PM_DEVICE_DT_INST_GET() 把二者连接起来。
九、设备访问 API
推荐方式 DEVICE_DT_GET():编译期解析,O(1):
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));
if (!device_is_ready(uart)) {
return -ENODEV;
}
device_get_binding() 运行时按名称遍历,适合 shell/调试/兼容旧代码:
const struct device *z_impl_device_get_binding(const char *name)
{
if ((name == NULL) || (name[0] == '\0')) {
return NULL;
}
STRUCT_SECTION_FOREACH(device, dev) {
if ((dev->name == name) || (strcmp(name, dev->name) == 0)) {
return z_impl_device_is_ready(dev) ? dev : NULL;
}
}
return NULL;
}
device_is_ready():
bool z_impl_device_is_ready(const struct device *dev)
{
if (dev == NULL) {
return false;
}
return dev->state->initialized && (dev->state->init_res == 0U);
}
无论 init 成功失败都会标记 initialized = true,失败时 init_res 保存正的 errno 值。device_is_ready() 检查二者都满足才返回 true。
十、Binding YAML
一个现代 binding 示例(通用示例,用传感器 temp_* 命名;本文 HCI 的 binding 见 2.2 节):
# dts/bindings/sensor/vendor,temp-sensor.yaml
compatible: "vendor,temp-sensor"
description: Vendor temperature sensor
include: [sensor-device.yaml, i2c-device.yaml]
properties:
reg:
required: true
resolution:
type: int
default: 12
enum: [9, 10, 11, 12]
description: Temperature conversion resolution in bits.
int-gpios:
type: phandle-array
description: Optional data-ready interrupt GPIO.
Binding 的作用:定义属性类型、默认值、合法枚举;指定 bus 通用属性;让 gen_edt.py 可以结构化解析节点并生成更可靠的 C 宏。
十一、调试技巧
# 查看最终 DTS
west build -b <board> <app>
cat build/zephyr/zephyr.dts
# 查看生成宏
grep -R "DT_N_" build/zephyr/include/generated -n | head
# 或直接打开 build/zephyr/include/generated/zephyr/devicetree_generated.h
# 查看使用到的 binding
cat build/zephyr/dts_bindings_used.txt
针对本文实例,还可以直接验证“匹配闭环”:
# 1. chosen 指向的节点
grep "DT_CHOSEN_zephyr_bt_hci" \
build/zephyr/include/generated/zephyr/devicetree_generated.h
# 2. 驱动实例映射到的节点(应指向同一个 DT_N_S_...)
grep "DT_N_INST_0_zephyr_bt_hci_ll_sw_split" \
build/zephyr/include/generated/zephyr/devicetree_generated.h
# 3. 该实例的设备对象符号(链接后存在即说明驱动被编译)
grep -r "__device_dts_ord_" build/zephyr/zephyr.map | head
在代码中做条件保护:
#if DT_NODE_HAS_STATUS(SENSOR_NODE, okay)
const struct device *sensor = DEVICE_DT_GET(SENSOR_NODE);
#endif
#if DT_NODE_HAS_PROP(SENSOR_NODE, resolution)
#define SENSOR_RESOLUTION DT_PROP(SENSOR_NODE, resolution)
#else
#define SENSOR_RESOLUTION 12
#endif
十二、常见错误
| 现象 | 常见原因 | 修复方向 |
|---|---|---|
DEVICE_DT_GET() 链接错误 | 节点 okay 但没有驱动实例化 device | 检查 compatible、驱动 Kconfig、DT_INST_FOREACH_STATUS_OKAY() |
device_is_ready() 为 false | init 失败或依赖设备未 ready | 看驱动 init 返回值和依赖 bus |
DT_PROP() 编译错误 | 属性不存在或 binding 未定义 | 用 DT_PROP_OR() 或修 binding |
I2C_DT_SPEC_INST_GET() 编译失败 | 节点不在 I2C bus 下或缺少 reg | 检查 DTS 拓扑和 binding include |
| overlay 不生效 | overlay 路径/board/shield 选择错误 | 看 CMake 输出中的 DTS files 列表 |
| PM 编译错误 | 直接把 PM 回调传给 device 宏 | 使用 PM_DEVICE_DT_DEFINE() + PM_DEVICE_DT_GET() |
bt_enable() 报 HCI 设备未 ready / 找不到 HCI | chosen zephyr,bt-hci 未指向 enabled 的 bt_hci_controller 节点 | 检查 DTS chosen、status = "okay"、CONFIG_BT_LL_SW_SPLIT |
DEVICE_DT_GET() 报 undefined reference to __device_dts_ord_<N> | 该节点没有驱动调用 DEVICE_DT_INST_DEFINE() | 确认 DT_DRV_COMPAT 与节点 compatible 一致、驱动被编译 |
十三、源码阅读路线
cmake/modules/dts.cmake:DTS 构建管线。scripts/dts/gen_edt.py:生成zephyr.dts与edt.pickle。scripts/dts/gen_defines.py:生成devicetree_generated.h。include/zephyr/devicetree.h:devicetree C 宏 API。include/zephyr/device.h:device 定义、获取、依赖和 init entry 宏。include/zephyr/init.h:init entry section 规则。kernel/init.c:启动阶段和 init level 遍历。kernel/device.c:device init、binding 查找、ready 判断。include/zephyr/pm/device.h:设备 PM resource 宏和 API。
本文档已按当前 Zephyr 4.4.99 源码更新。

492

被折叠的 条评论
为什么被折叠?



