Devicetree HOWTOs

This page has step-by-step advice for getting things done with devicetree.

Get your devicetree and generated header

A board’s devicetree (BOARD.dts) pulls in common node definitions via #include preprocessor directives. This at least includes the SoC’s .dtsi. One way to figure out the devicetree’s contents is by opening these files, e.g. by looking in dts/<ARCH>/<vendor>/<soc>.dtsi, but this can be time consuming.

Furthermore, you might want to see the actual generated header file. You might also be working with a board definition outside of the zephyr repository, making it unclear where BOARD.dts is in the first place.

Luckily, there is an easy way to do both: build your application.

For example, using west and the ARM Cortex-M3 Emulation (QEMU) board to build Hello World, forcing CMake to re-run:

west build -b qemu_cortex_m3 -s samples/hello_world --cmake

The build system prints the output file locations:

-- Found BOARD.dts: .../zephyr/boards/arm/qemu_cortex_m3/qemu_cortex_m3.dts
-- Generated zephyr.dts: .../zephyr/build/zephyr/zephyr.dts
-- Generated devicetree_unfixed.h: .../zephyr/build/zephyr/include/generated/devicetree_unfixed.h

Change qemu_cortex_m3 to the board you are using, of course.

Get a struct device from a devicetree node

When writing Zephyr applications, you’ll often want to get a driver-level struct device corresponding to a devicetree node.

For example, with this devicetree fragment, you might want the struct device for serial@40002000:

/ {
        soc {
                serial0: serial@40002000 {
                        status = "okay";
                        current-speed = <115200>;
                        /* ... */
                };
        };

        aliases {
                my-serial = &serial0;
        };

        chosen {
                zephyr,console = &serial0;
        };
};

Start by making a node identifier for the device you are interested in. There are different ways to do this; pick whichever one works best for your requirements. Here are some examples:

/* Option 1: by node label */
#define MY_SERIAL DT_NODELABEL(serial0)

/* Option 2: by alias */
#define MY_SERIAL DT_ALIAS(my_serial)

/* Option 3: by chosen node */
#define MY_SERIAL DT_CHOSEN(zephyr_console)

/* Option 4: by path */
#define MY_SERIAL DT_PATH(soc, serial_40002000)

Once you have a node identifier, get the struct device by combining DT_LABEL() with device_get_binding():

struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));

You can then use uart_dev with UART API functions like uart_configure(). Similar code will work for other device types; just make sure you use the correct API for the device.

There’s no need to override the label property to something else: just make a node identifier and pass it to DT_LABEL to get the right string to pass to device_get_binding().

If you’re having trouble, see Troubleshoot devicetree issues. The first thing to check is that the node is enabled (status = "okay") and has a matching binding, like this:

#define MY_SERIAL DT_NODELABEL(my_serial)

#if DT_HAS_NODE(MY_SERIAL)
struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
#else
#error "Node is disabled or has no matching binding"
#endif

If you see the #error output, something is wrong with either your devicetree or bindings.

Find a devicetree binding

Devicetree binding YAML files document what you can do with the nodes they describe, so it’s critical to be able to find them for the nodes you are using.

If you don’t have them already, Get your devicetree and generated header. To find a node’s binding, open the generated header file, which starts with a list of nodes in a block comment:

/*
 * [...]
 * Nodes in dependency order (ordinal and path):
 *   0   /
 *   1   /aliases
 *   2   /chosen
 *   3   /flash@0
 *   4   /memory@20000000
 *          (etc.)
 * [...]
 */

Make note of the path to the node you want to find, like /flash@0. Search for the node’s output in the file, which starts with something like this if the node has a matching binding:

/*
 * Devicetree node:
 *   /flash@0
 *
 * Binding (compatible = soc-nv-flash):
 *   $ZEPHYR_BASE/dts/bindings/mtd/soc-nv-flash.yaml
 * [...]
 */

See Check for missing bindings for troubleshooting.

Set devicetree overlays

Devicetree overlays are explained in Introduction to devicetree. The CMake variable DTC_OVERLAY_FILE contains a space- or colon-separated list of overlays. If DTC_OVERLAY_FILE specifies multiple files, they are included in that order by the C preprocessor.

Here are some ways to set it:

  1. on the cmake build command line (-DDTC_OVERLAY_FILE=file1.overlay;file2.overlay)

  2. with the CMake set() command in the application CMakeLists.txt, before including zephyr’s boilerplate.cmake file

  3. using a DTC_OVERLAY_FILE environment variable (deprecated)

  4. create a boards/<BOARD>.overlay file in the application folder, for the current board

  5. create a <BOARD>.overlay file in the application folder

Here is an example using west build. However you set the value, it is saved in the CMake cache between builds.

The build system prints all the devicetree overlays it finds in the configuration phase, like this:

-- Found devicetree overlay: .../some/file.overlay

Use devicetree overlays

See Set devicetree overlays for how to add an overlay to the build.

Overlays can override node property values in multiple ways. For example, if your BOARD.dts contains this node:

/ {
        soc {
                serial0: serial@40002000 {
                        status = "okay";
                        current-speed = <115200>;
                        /* ... */
                };
        };
};

These are equivalent ways to override the current-speed value in an overlay:

/* Option 1 */
&serial0 {
     current-speed = <9600>;
};

/* Option 2 */
&{/soc/serial@40002000} {
     current-speed = <9600>;
};

We’ll use the &serial0 style for the rest of these examples.

You can add aliases to your devicetree using overlays: an alias is just a property of the /aliases node. For example:

/ {
     aliases {
             my-serial = &serial0;
     };
};

Chosen nodes work the same way. For example:

/ {
     chosen {
             zephyr,console = &serial0;
     };
};

To delete a property (in addition to deleting properties in general, this is how to set a boolean property to false if it’s true in BOARD.dts):

&serial0 {
     /delete-property/ some-unwanted-property;
};

You can add subnodes using overlays. For example, to configure a SPI or I2C child device on an existing bus node, do something like this:

/* SPI device example */
&spi1 {
     my_spi_device: temp-sensor@0 {
             compatible = "...";
             label = "TEMP_SENSOR_0";
             /* reg is the chip select number, if needed;
              * If present, it must match the node's unit address. */
             reg = <0>;

             /* Configure other SPI device properties as needed.
              * Find your device's DT binding for details. */
             spi-max-frequency = <4000000>;
     };
};

/* I2C device example */
&i2c2 {
     my_i2c_device: touchscreen@76 {
             compatible = "...";
             label = "TOUCHSCREEN";
             /* reg is the I2C device address.
              * It must match the node's unit address. */
             reg = <76>;

             /* Configure other I2C device properties as needed.
              * Find your device's DT binding for details. */
     };
};

Other bus devices can be configured similarly:

  • create the device as a subnode of the parent bus

  • set its properties according to its binding

Assuming you have a suitable device driver associated with the my_spi_device and my_i2c_device compatibles, you should now be able to enable the driver via Kconfig and get the struct device for your newly added bus node, then use it with that driver API.

Create struct devices in a driver

If you’re writing a device driver, it should be devicetree aware so that applications can configure it and access devices as described above. In short, you must create a struct device for every enabled instance of the compatible that the device driver supports, and set each device’s name to the DT_LABEL() of its devicetree node.

The devicetree.h API has helpers for writing device drivers based on DT_INST node identifiers for each of the possible instance numbers on your SoC.

Assuming you’re using instances, start by defining DT_DRV_COMPAT at the top of the file to the lowercase-and-underscores version of the compatible that the device driver is handling. For example, if your driver is handling nodes with compatible "vnd,my-device", you should put this at the top of your driver:

#define DT_DRV_COMPAT vnd_my_device

Important

The DT_DRV_COMPAT macro should have neither quotes nor special characters. Remove quotes and convert special characters to underscores.

The typical pattern after that is to define the API functions, then define a macro which creates the device by instance number, and then call it for each enabled instance. Currently, this looks like this:

#include <drivers/some_api.h>

#include <devicetree.h>
#define DT_DRV_COMPAT vnd_my_device

/*
 * Define RAM and ROM structures:
 */

struct my_dev_data {
     /* per-device values to store in RAM */
};

struct my_dev_cfg {
     u32_t freq; /* Just an example: clock frequency in Hz */
     /* other device configuration to store in ROM */
};

/*
 * Implement some_api.h callbacks:
 */

struct some_api my_api_funcs = { /* ... */ };

/*
 * Now use DT_INST APIs to create a struct device for each enabled node:
 */

#define CREATE_MY_DEVICE(inst)                                       \
     static struct my_dev_data my_dev_data_##inst = {                \
             /* initialize RAM values as needed */                   \
     };                                                              \
     static const struct my_dev_cfg my_dev_cfg_##inst = {            \
             /* initialize ROM values, usually from devicetree */    \
             .freq = DT_INST_PROP(inst, clock_frequency),            \
             /* ... */                                               \
     };                                                              \
     DEVICE_AND_API_INIT(my_dev_##inst,                              \
                         DT_INST_LABEL(inst),                        \
                         my_dev_init_function,                       \
                         &my_dev_data_##inst,                        \
                         &my_dev_cfg_##inst,                         \
                         MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY,    \
                         &my_api_funcs)

/* Call the device creation macro for every compatible node: */
DT_INST_FOREACH(CREATE_MY_DEVICE);

Notice the use of DT_INST_PROP() and DT_INST_FOREACH(). These are helpers which rely on DT_DRV_COMPAT to choose devicetree nodes of a chosen compatible at a given index.

As shown above, the driver uses additional information from devicetree.h to create struct device instances than just the node label. Devicetree property values used to configure the device at boot time are stored in ROM in the value pointed to by a device->config->config_info field. This allows users to configure your driver using overlays.

The Zephyr convention is to name each struct device using its devicetree node’s label property using DT_INST_LABEL(). This allows applications to Get a struct device from a devicetree node.

Troubleshoot devicetree issues

Here are some tips for fixing misbehaving devicetree code.

Try again with a pristine build directory

See Pristine Builds for examples, or just delete the build directory completely and retry.

This is general advice which is especially applicable to debugging devicetree issues, because the outputs are created at CMake configuration time, and are not always regenerated when one of their inputs changes.

Make sure <devicetree.h> is included

Unlike Kconfig symbols, the devicetree.h header must be included explicitly.

Many Zephyr header files rely on information from devicetree, so including some other API may transitively include devicetree.h, but that’s not guaranteed.

Make sure you’re using the right names

Remember that:

  • In C/C++, devicetree names must be lowercased and special characters must be converted to underscores. Zephyr’s generated devicetree header has DTS names converted in this way into the C tokens used by the preprocessor-based <devicetree.h> API.

  • In overlays, use devicetree node and property names the same way they would appear in any DTS file. Zephyr overlays are just DTS fragments.

For example, if you’re trying to get the clock-frequency property of a node with path /soc/i2c@12340000 in a C/C++ file:

/*
 * foo.c: lowercase-and-underscores names
 */

/* Don't do this: */
#define MY_CLOCK_FREQ DT_PROP(DT_PATH(soc, i2c@1234000), clock-frequency)
/*                                           ^               ^
 *                                        @ should be _     - should be _  */

/* Do this instead: */
#define MY_CLOCK_FREQ DT_PROP(DT_PATH(soc, i2c_1234000), clock_frequency)
/*                                           ^               ^           */

And if you’re trying to set that property in a devicetree overlay:

/*
 * foo.overlay: DTS names with special characters, etc.
 */

/* Don't do this; you'll get devicetree errors. */
&{/soc/i2c_12340000/} {
     clock_frequency = <115200>;
};

/* Do this instead. Overlays are just DTS fragments. */
&{/soc/i2c@12340000/} {
     clock-frequency = <115200>;
};

Validate properties

If you’re getting a compile error reading a node property, remember Not all nodes are usable, then check your node identifier and property. For example, if you get a build error on a line that looks like this:

int baud_rate = DT_PROP(DT_NODELABEL(my_serial), current_speed);

Try checking the node by adding this to the file and recompiling:

#if DT_HAS_NODE(DT_NODELABEL(my_serial)) == 0
#error "whoops"
#endif

If you see the “whoops” error message when you rebuild, the node identifier isn’t referring to a valid node. Get your devicetree and generated header and debug from there.

Some hints:

If you’re sure the property is defined but DT_NODE_HAS_PROP() disagrees, check for a missing binding.

Check for missing bindings

If the build fails to Find a devicetree binding for a node, then either the node’s compatible property is missing, or its value has no matching binding. If the property is set, check for typos in its name. In a devicetree source file, compatible should look like "vnd,some-device"Make sure you’re using the right names.

If your binding file is not under zephyr/dts, you may need to set DTS_ROOT.

Errors with DT_INST_() APIs

If you’re using an API like DT_INST_PROP(), you must define DT_DRV_COMPAT to the lowercase-and-underscores version of the compatible you are interested in. See Create struct devices in a driver.