设备树(Device Tree)是描述硬件配置的树状数据结构。在 Zephyr 中,设备树用于定义板级硬件配置、外设参数和连接关系,与代码分离,使同一套代码可以在不同硬件上运行。
设备树源文件 (.dts)
↓ 编译
设备树 blob (.dtb) - 二进制格式
↓ Zephyr 处理
设备树头文件 (devicetree.h)
↓ C 预处理器
代码中的设备树 API
| 术语 | 说明 | 示例 |
|---|---|---|
| Node | 设备树节点,代表一个设备或总线 | /soc, /soc/i2c@40003000 |
| Property | 节点属性,描述设备特性 | compatible, reg, status |
| Label | 节点标签,便于引用 | i2c0: &i2c@40003000 |
| Phandle | 节点引用标识符 | &i2c0, <&gpio0> |
| Compatible | 兼容性字符串 | "nordic,nrf-twi" |
| 文件类型 | 用途 | 位置 |
|---|---|---|
| .dts | 设备树源文件 | boards/, samples/ |
| .dtsi | 包含文件(通用定义) | dts/, soc/ |
| .overlay | 覆盖文件(修改配置) | samples/, tests/ |
| .yaml | 绑定定义(Schema) | dts/bindings/ |
/ {
// 根节点
model = "Nordic nRF54L15 DK";
compatible = "nordic,nrf54l15-dk";
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
// I2C 外设
i2c0: i2c@40003000 {
compatible = "nordic,nrf-twi";
reg = <0x40003000 0x1000>;
interrupts = <3 1>;
status = "disabled";
sda-pin = <26>;
scl-pin = <27>;
clock-frequency = <I2C_BITRATE_FAST>;
};
};
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
label = "Green LED 0";
};
};
};
my_node {
// 字符串
string-prop = "hello world";
// 整数
int-prop = <42>;
// 布尔(存在即为真)
bool-prop;
// 数组
array-prop = <1 2 3 4>;
// 字节数组
byte-array = [01 02 03 04];
// 字符串数组
string-array = "first", "second", "third";
// 引用(phandle)
ref-prop = <&other_node>;
// 引用数组
ref-array = <&node1 &node2 &node3>;
};
// 包含通用定义
#include <nordic/nrf54l15.dtsi>
#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
// 添加或修改节点
};
// 覆盖已有节点(&引用)
&i2c0 {
status = "okay";
sensor@76 {
compatible = "bosch,bme280";
reg = <0x76>;
status = "okay";
};
};
定义设备的兼容性字符串,用于匹配驱动程序。
// 单一兼容
compatible = "nordic,nrf-twi";
// 多个兼容(优先匹配第一个)
compatible = "nordic,nrf-twim", "nordic,nrf-twi";
定义寄存器地址和大小。
#address-cells = <1>; // 地址用 1 个 cell(32位)
#size-cells = <1>; // 大小用 1 个 cell
// reg = <address size>
i2c@40003000 {
reg = <0x40003000 0x1000>; // 地址 0x40003000,大小 4KB
};
控制设备启用状态。
status = "okay"; // 启用
status = "disabled"; // 禁用(默认)
定义中断配置。
// 单一中断
interrupts = <IRQ_NUMBER IRQ_PRIORITY>;
// 多个中断
interrupts-extended = <&nvic 3 1>, <&nvic 4 2>;
引脚控制配置(现代 Zephyr 推荐)。
#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>
&i2c0 {
pinctrl-0 = <&i2c0_default>;
pinctrl-1 = <&i2c0_sleep>;
pinctrl-names = "default", "sleep";
};
&pinctrl {
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWI_SDA, 0, 26)>,
<NRF_PSEL(TWI_SCL, 0, 27)>;
};
};
};
定义设备属性的 Schema 验证。
# dts/bindings/i2c/nordic,nrf-twi.yaml
description: Nordic TWI (I2C) controller
compatible: "nordic,nrf-twi"
include: i2c-controller.yaml
properties:
reg:
required: true
interrupts:
required: true
sda-pin:
type: int
required: false
description: SDA pin number
scl-pin:
type: int
required: false
description: SCL pin number
clock-frequency:
type: int
required: false
default: 100000
description: I2C clock frequency in Hz
properties:
# 字符串
string-prop:
type: string
# 整数
int-prop:
type: int
# 布尔
bool-prop:
type: boolean
# 数组
array-prop:
type: array
# 元素类型: int, string, phandle
# phandle
ref-prop:
type: phandle
# phandle + spec
gpio-prop:
type: phandle-array
specifier-space: gpio
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
// 通过节点标签获取设备
const struct device *i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c0));
// 检查设备就绪
if (!device_is_ready(i2c_dev)) {
printk("I2C device not ready\n");
return -ENODEV;
}
// 读取整数
uint32_t clock_freq = DT_PROP(DT_NODELABEL(i2c0), clock_frequency);
// 读取数组
uint32_t reg_addr = DT_REG_ADDR(DT_NODELABEL(i2c0));
uint32_t reg_size = DT_REG_SIZE(DT_NODELABEL(i2c0));
// 读取字符串
const char *label = DT_LABEL(DT_NODELABEL(led0));
// 检查布尔属性
#if DT_NODE_HAS_PROP(DT_NODELABEL(my_node), bool_prop)
// bool_prop 存在
#endif
// 检查节点状态
#if DT_NODE_HAS_STATUS(DT_NODELABEL(i2c0), okay)
// i2c0 已启用
#endif
#include <zephyr/drivers/gpio.h>
// 从设备树获取 GPIO 规格
#define LED0_NODE DT_NODELABEL(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
// 初始化
int init_led(void)
{
if (!device_is_ready(led.port)) {
return -ENODEV;
}
return gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
}
// 控制 LED
void toggle_led(void)
{
gpio_pin_toggle_dt(&led);
}
// 定义传感器节点
#define SENSOR_NODE DT_NODELABEL(bme280)
// 获取 I2C 总线
const struct device *i2c_dev = DEVICE_DT_GET(DT_BUS(SENSOR_NODE));
// 获取 I2C 地址
uint8_t i2c_addr = DT_REG_ADDR(SENSOR_NODE);
// 读取传感器数据
uint8_t read_sensor_reg(uint8_t reg)
{
uint8_t val;
i2c_reg_read_byte(i2c_dev, i2c_addr, reg, &val);
return val;
}
// 遍历所有 GPIO LED
#define LED_FOREACH(inst) \
DT_FOREACH_CHILD(DT_PATH(leds), inst)
// 定义 LED 数组
#define LED_DEFINE(node_id) \
GPIO_DT_SPEC_GET(node_id, gpios),
static const struct gpio_dt_spec leds[] = {
LED_FOREACH(LED_DEFINE)
};
#define NUM_LEDS (sizeof(leds) / sizeof(leds[0]))
samples/my_sample/
├── src/
│ └── main.c
├── boards/
│ ├── nrf54l15dk_nrf54l15_cpuapp.overlay # 特定板子覆盖
│ └── nrf52840dk_nrf52840.overlay
└── prj.conf
// boards/nrf54l15dk_nrf54l15_cpuapp.overlay
// 启用 I2C
&i2c0 {
status = "okay";
pinctrl-0 = <&i2c0_default>;
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
status = "okay";
};
};
// 添加自定义节点
/ {
my_custom_hw {
compatible = "vnd,my-hw";
status = "okay";
custom-prop = <100>;
};
};
# 使用覆盖文件编译
west build -b nrf54l15dk/nrf54l15/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=custom.overlay
# 或使用 SHIELD
west build -b nrf54l15dk/nrf54l15/cpuapp -- -DSHIELD=my_shield
// Nordic nRF52840 DK
&uart0 {
status = "okay";
current-speed = <115200>;
tx-pin = <6>;
rx-pin = <8>;
rts-pin = <5>;
cts-pin = <7>;
};
&i2c0 {
status = "okay";
sda-pin = <26>;
scl-pin = <27>;
};
&spi0 {
status = "okay";
sck-pin = <23>;
mosi-pin = <24>;
miso-pin = <25>;
};
// nRF54L15 - 使用 pinctrl
&uart20 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart20_default>;
pinctrl-names = "default";
};
&pinctrl {
uart20_default: uart20_default {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 4)>,
<NRF_PSEL(UART_RX, 1, 5)>;
};
};
};
// 在 board 的 .dts 文件中
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
status = "okay";
};
lis2dh@19 {
compatible = "st,lis2dh";
reg = <0x19>;
status = "okay";
irq-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
};
};
// 在代码中使用
#define BME280_NODE DT_NODELABEL(bme280)
#define LIS2DH_NODE DT_NODELABEL(lis2dh)
const struct device *bme280 = DEVICE_DT_GET(BME280_NODE);
const struct device *lis2dh = DEVICE_DT_GET(LIS2DH_NODE);
// 检查设备就绪
if (!device_is_ready(bme280) || !device_is_ready(lis2dh)) {
printk("Sensors not ready\n");
return -ENODEV;
}
// 自定义硬件板子定义
/ {
sensors {
compatible = "vnd,sensor-array";
temp_sensor: temp {
compatible = "vnd,temp-sensor";
io-channels = <&adc 0>;
status = "okay";
};
light_sensor: light {
compatible = "vnd,light-sensor";
io-channels = <&adc 1>;
status = "okay";
};
};
};
# 编译后查看生成的设备树
west build -t devicetree_target
# 生成的文件在 build/zephyr/zephyr.dts.pre
# 启用详细输出
west build -v
# 检查预处理后的设备树
cat build/zephyr/zephyr.dts.pre
// 检查节点是否存在
#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_twim)
// 存在 nordic,nrf-twim 节点且状态为 okay
#endif
// 获取节点数量
#define NUM_LEDS DT_NUM_INST_STATUS_OKAY(gpio_leds)
// 条件编译
#if DT_NODE_HAS_COMPAT(DT_NODELABEL(i2c0), nordic_nrf_twim)
// 使用 TWIM 驱动
#else
// 使用 TWI 驱动
#endif
Zephyr 设备树是硬件配置的核心:
学习重点:
*学习日期: 2026-03-20* *小白 🤖*