Devicetree is a data format that describes hardware separately from code.
Instead of hardcoding GPIO pin numbers and I2C bus addresses into your C code, you declare them in a .dts file, and Zephyr generates C macros you use in code.
Why this matters: the same driver code works on any board — only the .dts file changes.
Without devicetree (bad):
/* Hardcoded in source — breaks when you change boards */
#define BUTTON_PIN 5
#define LED_PORT GPIOC
#define IMU_I2C_ADDR 0x68
#define IMU_BUS I2C1
gpio_pin_set(GPIOC, 5, 1); /* what if LED moves to GPIOA pin 7 on new board? */
With devicetree (good):
/* Code refers to logical names, never pin numbers */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
gpio_pin_set_dt(&led, 1); /* works on any board where "led0" is defined in DTS */
Devicetree is a tree of named nodes, each with properties:
/dts-v1/;
/ { /* root node */
model = "My STM32 Robot Board";
aliases {
led0 = &green_led; /* logical name → node reference */
};
leds { /* custom node */
green_led: led_0 { /* node label: node_name */
gpios = <&gpioc 7 GPIO_ACTIVE_HIGH>;
/* third arg = flags: which polarity is "on" */
};
};
};
&i2c1 { /* modify existing node (from board DTS) */
status = "okay"; /* enable this peripheral */
clock-frequency = <400000>; /* 400 kHz */
imu: icm42688@68 { /* child node: name@address */
compatible = "invensense,icm42688p";
reg = <0x68>;
int-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;
};
};
Real projects use multiple DTS files that overlay each other:
Board DTS (e.g., stm32f4_disco.dts)
└── defines all peripherals, their base addresses, pinmux defaults
Board DTSI (hardware constants, shared between board variants)
SoC DTSI (stm32f4.dtsi, generated from silicon, rarely modified)
└── defines all hardware blocks: USART1, I2C1, SPI1, etc.
app.overlay ← YOU write this
└── enables/configures only what your app needs
You almost always only write app.overlay. It overlays on top of the board DTS, adding or modifying nodes.
app.overlay You Write/* app.overlay — placed in your app root directory */
/* Enable I2C1 (inherited from board DTS, marked disabled by default) */
&i2c1 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>; /* = 400000 Hz */
imu: icm42688@68 {
compatible = "invensense,icm42688p";
reg = <0x68>;
int-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;
};
};
/* Enable SPI1 for slave operation */
&spi1 {
status = "okay";
};
/* Enable CAN1 */
&can1 {
status = "okay";
bus-speed = <1000000>;
};
/* Custom aliases for convenience */
/ {
aliases {
sensor-i2c = &i2c1;
};
};
Zephyr generates C macros from the DTS at build time. Headers are in zephyr/include/zephyr/dt-bindings/ and auto-generated in build/zephyr/include/generated/.
/* By label */
const struct device *i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1));
/* By alias */
const struct device *led_dev = DEVICE_DT_GET(DT_ALIAS(led0));
/* By node path */
const struct device *spi = DEVICE_DT_GET(DT_PATH(soc, spi_40013000));
/* In DTS: led_0 { gpios = <&gpioc 7 GPIO_ACTIVE_HIGH>; }; */
static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
/* led.port = GPIOC device, led.pin = 7, led.dt_flags = ACTIVE_HIGH */
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
gpio_pin_toggle_dt(&led);
/* In DTS: uart0 { current-speed = <115200>; }; */
#define BAUD DT_PROP(DT_NODELABEL(uart0), current_speed)
/* Note: property name uses underscore in macro, hyphen in DTS */
if (!device_is_ready(i2c)) {
LOG_ERR("I2C not ready");
return -ENODEV;
}
/* In DTS: leds { led_0 { ... }; led_1 { ... }; }; */
#define INIT_LED(node_id) \
{ \
.spec = GPIO_DT_SPEC_GET(node_id, gpios), \
},
static const struct {
struct gpio_dt_spec spec;
} leds[] = {
DT_FOREACH_CHILD(DT_PATH(leds), INIT_LED)
};
Devicetree describes hardware. Kconfig describes software options: which drivers to compile, stack sizes, feature flags.
/* prj.conf */
# Enable I2C API
CONFIG_I2C=y
# Enable CAN
CONFIG_CAN=y
# Enable logging
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3 # 3 = INFO
# Enable ZBus
CONFIG_ZBUS=y
# SPI
CONFIG_SPI=y
# Thread stack sizes (you can also configure per-thread)
CONFIG_MAIN_STACK_SIZE=2048
# Enable stack overflow detection
CONFIG_STACK_SENTINEL=y
# Enable thread analyzer (prints stack usage)
CONFIG_THREAD_ANALYZER=y
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
CONFIG_THREAD_ANALYZER_AUTO=n # manual: call thread_analyzer_print()
# Shell (useful for debug)
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y
# Optional: use 64-bit timestamps
CONFIG_CLOCK_CONTROL=y
Every DTS node with compatible = "vendor,chip" needs a matching binding YAML file that defines its properties.
Zephyr ships thousands of bindings in zephyr/dts/bindings/.
# Example binding: dts/bindings/sensor/invensense,icm42688p.yaml
description: ICM-42688-P 6-axis IMU
compatible: "invensense,icm42688p"
include: i2c-device.yaml # inherits reg, status, etc.
properties:
int-gpios:
type: phandle-array
description: Data-ready interrupt output
required: false
If you’re writing a custom driver and you make a YAML binding file, Zephyr will validate your DTS and generate type-safe macros for your properties.
| File | Whom | What |
|---|---|---|
boards/arm/my_board/my_board.dts |
Board vendor (you for custom boards) | Pin assignments, GPIO numbering, default uart/led nodes |
dts/arm/st/stm32f4.dtsi |
Zephyr project | SoC peripherals: base addresses, interrupts |
app.overlay |
YOU (app developer) | Enable peripherals, add sensor nodes, configure speed |
prj.conf |
YOU (app developer) | Enable/disable software drivers and features |
| Mistake | Build error / symptom | Fix |
|---|---|---|
Forgot status = "okay" |
No device, -ENODEV at runtime |
Add status = "okay" to node |
Wrong compatible string |
Build warning, driver not found | Match exactly to binding YAML |
| Property name with hyphen in C macro | Compile error | Use underscore in C: current_speed not current-speed |
app.overlay not in app root |
Overlay silently ignored | Place next to CMakeLists.txt |
Missing CONFIG_I2C=y in prj.conf |
Linker error, device null | Add driver Kconfig |
device_is_ready returns false |
Init fails silently elsewhere | Add if (!device_is_ready()) check + log |