Zephyr uses the devicetree data structure to describe the hardware available on a board, as well as its initial configuration in an application. Note that “devicetree” – without spaces – is preferred to “device tree”. The Devicetree specification fully defines this data structure and its source and binary representations.


This figure shows how devicetree fits into the Zephyr build system:


Devicetree build flow

Zephyr’s build system generates C preprocessor macros from devicetree definitions. These macros can be referenced in device drivers and other C code. All macro identifiers that are directly generated by the devicetree scripts start with DT_*.

Some devicetree-related identifiers also start with CONFIG_*, which is the identifier prefix used by Kconfig. This happens when devicetree-related information is referenced from Kconfig symbol definitions, via the Kconfig preprocessor.

This differs significantly from how devicetree is used on Linux. The Linux kernel would instead read the entire devicetree data structure in its binary form, parsing it at runtime in order to load and initialize device drivers. Zephyr does not work this way because the size of the devicetree binary and associated handling code would be too large to fit comfortably on the relatively constrained devices Zephyr supports.

As the name indicates, a devicetree is a tree. The human-readable text format for this tree is called DTS (for devicetree source), and is defined in the Devicetree Specification. Here is an example DTS file:


/ {
        a-node {
                subnode_label: a-sub-node {
                        foo = <3>;

This example has three nodes:

  1. A root node

  2. A node named a-node, which is a child of the root node

  3. A node named a-sub-node, which is a child of a-node

Nodes can be given labels, which are unique shorthands that can be used to refer to the labeled node elsewhere in the devicetree. Above, a-sub-node has label subnode_label.

Devicetree nodes have paths identifying their locations in the tree. Like Unix file system paths, devicetree paths are strings separated by slashes (/), and the root node’s path is a single slash: /. Otherwise, each node’s path is formed by concatenating the node’s ancestors’ names with the node’s own name, separated by slashes. For example, the full path to a-sub-node is /a-node/a-sub-node.

Devicetree nodes can also have properties. Properties are name/value pairs. The values are simple byte arrays. Node a-sub-node has a property named foo, whose value is a 32-bit big-endian unsigned integer with value 3. The size and type of foo’s value are implied by the enclosing angle brackets (< and >) in the DTS. Refer to the Devicetree Specification for a complete list of ways to write a property value in a DTS file.

In practice, devicetree nodes correspond to some hardware, and the node hierarchy reflects the hardware’s physical layout. For example, let’s consider a board with three I2C peripherals connected to an I2C bus master on an SoC, like this:

representation of a board with three I2C peripherals

Nodes corresponding to the I2C bus master and each I2C peripheral would be present in this board’s devicetree. Reflecting the hardware layout, the devicetree’s peripheral nodes would be children of the bus master node. Similar conventions exist for representing other types of hardware in devicetree.

The corresponding DTS would look something like this:

/ {
        soc {
                i2c-bus-master {
                        i2c-peripheral-1 {
                        i2c-peripheral-2 {
                        i2c-peripheral-3 {

Properties are used in practice to describe or configure the hardware the node represents. For example, an I2C peripheral’s node has a property whose value is the peripheral’s address on the bus.

Here’s a tree representing the same example, but with real-world node names and properties you might see when working with I2C devices.


I2C devicetree example with real-world names and properties

Above, node names – like i2c@40003000 – are at the top of each node, with a gray background, except for the root node, which is shown using its path /. Properties are shown as name=value pairs below the node names.

Some important properties are:


Says what kind of device the node represents. The value is a string in the format “vendor,device”, like "avago,apds9960", or a sequence of these, like "ti,hdc", "ti,hdc1010". The build system uses the compatible property to find the right binding for the node.


The device’s name according to Zephyr’s Device Driver Model. The value can be passed to device_get_binding() to retrieve the corresponding driver-level struct device*. This pointer can then be passed to the correct driver API by application code to interact with the device. For example, calling device_get_binding("I2C_0") would return a pointer to a device structure which could be passed to I2C API functions like i2c_transfer(). The generated C header will also contain a macro which expands to this string.


Information used to address the device. This could be a memory-mapped I/O address range (as with i2c@40003000’s reg property), an I2C bus address (as with apds9960@39 and its devicetree siblings), a SPI chip select line, or some other value depending on the kind of device the node represents.

This tree has the following DTS.

/ {
        soc {
                i2c@40003000 {
                        compatible = "nordic,nrf-twim";
                        label = "I2C_0";
                        reg = <0x40003000 0x1000>;

                        apds9960@39 {
                                compatible = "avago,apds9960";
                                label = "APDS9960";
                                reg = <0x39>;
                        ti_hdc@43 {
                                compatible = "ti,hdc", "ti,hdc1010";
                                label = "HDC1010;
                                reg = <0x43>;
                        mma8652fc@1d {
                                compatible = "nxp,fxos8700", "nxp,mma8652fc";
                                label = "MMA8652FC";
                                reg = <0x1d>;

Input and output files

The first figure in the Introduction shows how devicetree fits into the Zephyr build system. This section describes the input and output files in more detail.


Devicetree input (green) and output (yellow) files

DTS files usually have a .dts, .dtsi (i for include), or .overlay extension. The C preprocessor is run on all devicetree files to expand macro references. .dts files usually include .dtsi files via the C preprocessor with #include.


DTS also also has a native mechanism, /include/ "<filename>", for including other files, though it is less commonly used.

Each board has a base devicetree, stored in the board’s directory in boards/ as <BOARD>.dts. This base devicetree can be extended or modified with one or more overlays – DTS files with a .overlay extension. Overlays adapt the base devicetree for different board variants or applications. Along with Kconfig, this makes it possible to reconfigure the kernel and device drivers without modifying source code.

The build system automatically picks up .overlay files stored in certain locations. It is also possible to explicitly list the overlays to include, via the DTC_OVERLAY_FILE CMake variable. See Devicetree Overlays and Important Build System Variables for details.

After running the C preprocessor, the resulting <BOARD>.dts and .overlay files are combined by concatenating them, with the overlays put last. This relies on DTS merging multiple definitions of nodes. See Example: FRDM-K64F and Hexiwear K64 for an example of how this works (in the context of .dtsi files, but the principle is the same for overlays). Putting the contents of the .overlay files last allows them to override properties from the base devicetree, if needed.


The preprocessed and concatenated DTS sources are stored in zephyr/<BOARD>.dts.pre.tmp in the build directory. Looking at this file can be handy for debugging.

The merged devicetree, along with any bindings referenced from it, is used to generate C preprocessor macros. This is handled by the libraries and scripts listed below, located in scripts/dts/. Note that the source code has extensive comments and documentation.

A low-level DTS parsing library

A library layered on top of dtlib that uses bindings to interpret properties and give a higher-level view of the devicetree. Uses dtlib to do the DTS parsing.

A script that uses edtlib to generate C preprocessor macros from the devicetree and bindings.

The output from is stored in include/generated/generated_dts_board_unfixed.h in the build directory.


In addition to the Python code above, the standard dtc DTS compiler is also run on the devicetree. This is just to catch any errors or warnings it generates. The output is unused.

Most devices currently use dts_fixup.h files that rename macros from generated_dts_board_unfixed.h to names that are more meaningful for the device. By default, these fixup files are in the board/ and soc/ directories. Any dts_fixup.h files are concatenated and stored as include/generated_dts_board_fixups.h in the build directory.

Fixup files exist for historical reasons, and Zephyr might move away from using them. When writing new code, feel free to create any macro aliases you need in whatever way is handiest for the code.

To reference macros generated from devicetree, code should include the generated_dts_board.h header, which appears on the C preprocessor include path. This file appears at include/generated_dts_board.h and is not a generated file. It includes the generated include/generated_dts_board_unfixed.h and include/generated_dts_board_fixups.h files.


Do not include the generated C headers from the build directory directly. Include generated_dts_board.h instead.

Generated macros

Take the DTS node below as an example.

sim@40047000 {
     compatible = "nxp,kinetis-sim";
     reg = <0x40047000 0x1060>;
     label = "SIM";

Below is sample header content generated for this node, in include/generated_dts_board_unfixed.h in the build directory.

 * Devicetree node:
 *   /soc/sim@40047000
 * Binding (compatible = nxp,kinetis-sim):
 *   $ZEPHYR_BASE/dts/bindings/arm/nxp,kinetis-sim.yaml
 * Dependency Ordinal: 24
 * Requires:
 *   7   /soc
 * Supports:
 *   25  /soc/i2c@40066000
 *   26  /soc/i2c@40067000
 *   ...
 * Description:
 *   Kinetis System Integration Module (SIM) IP node
#define DT_NXP_KINETIS_SIM_40047000_BASE_ADDRESS    0x40047000
#define DT_NXP_KINETIS_SIM_40047000_SIZE            4192
#define DT_INST_0_NXP_KINETIS_SIM_SIZE              DT_NXP_KINETIS_SIM_40047000_SIZE
/* Human readable string describing the device (used by Zephyr for API name) */
#define DT_NXP_KINETIS_SIM_40047000_LABEL           "SIM"
#define DT_INST_0_NXP_KINETIS_SIM                   1

Generated macro names follow the format DT_<node>_<property>. For DT_NXP_KINETIS_SIM_40047000_BASE_ADDRESS, the node part is NXP_KINETIS_SIM_40047000, based on the compatible string that matched a binding for the node (nxp,kinetis-sim) and the node’s unit address (...@4004700).

The *_BASE_ADDRESS part of the identifier is a fixed identifier generated from the special reg property on the node. *_SIZE is also generated from reg. Other suffixes, like *_LABEL, are generated directly from the property name.

The second macro (DT_INST_0_NXP_KINETIS_SIM_BASE_ADDRESS) is an alias for the first macro. The node identifier ...INST_0_NXP_KINETIS_SIM_... means “the first node with compatible string nxp,kinetis-sim.

Aliases are also generated from any properties in the /aliases node. Take the DTS fragment below as an example.

aliases {
     i2c-1 = &i2c;

This would generate additional DT_ALIAS_I2C_1_... aliases for all properties in the output for the node with the devicetree label i2c.

Aliases that replace the property name part can also be generated, e.g. via *-names = "foo", "bar" properties. For example, reg-names = "control", "mem" will generate DT_<node>_CONTROL_BASE_ADDRESS/SIZE and DT_<node>_MEM_BASE_ADDRESS_SIZE aliases.


The above is just a short overview of common ways macro names get generated, and not complete. For the nitty-gritty, see the source code in , and check the output generated for some existing boards and applications.

Zephyr device drivers typically use information from generated_dts_board.h to statically allocate and initialize struct device instances. Property values from generated_dts_board.h are usually stored in ROM in the value pointed to by a device->config->config_info field. For example, a struct device corresponding to an I2C peripheral would store the peripheral address in its reg property there.

Application source code with a pointer to the struct device can then pass it to driver APIs in include/drivers/. These API functions usually take a struct device* as their first argument. This allows the driver API to use information from devicetree to interact with the device hardware.

Example: FRDM-K64F and Hexiwear K64

The FRDM-K64F and Hexiwear K64 board devicetrees are defined in frdm_k64fs.dts and hexiwear_k64.dts respectively. Both boards have NXP SoCs from the same Kinetis SoC family, the K6X.

Common devicetree definitions for K6X are stored in nxp_k6x.dtsi, which is included by both board .dts files. nxp_k6x.dtsi in turn includes armv7-m.dtsi, which has common definitions for Arm v7-M cores.

Since nxp_k6x.dtsi is meant to be generic across K6X-based boards, it leaves many devices disabled by default using status properties. For example, there is a CAN controller defined as follows (with unimportant parts skipped):

can0: can@40024000 {
     status = "disabled";

It is up to the board .dts or application overlay files to enable these devices as desired, by setting status = "okay". The board .dts files are also responsible for any board-specific configuration of the device, such as adding nodes for on-board sensors, LEDs, buttons, etc.

For example, FRDM-K64 (but not Hexiwear K64) .dts enables the CAN controller and sets the bus speed:

&can0 {
     status = "okay";
     bus-speed = <125000>;

The &can0 { ... }; syntax adds/overrides properties on the node with label can0, i.e. the can@4002400 node defined in the .dtsi file.

Other examples of board-specific customization is pointing properties in aliases and chosen to the right nodes (see aliases and chosen nodes), and making GPIO/pinmux assignments.

Devicetree vs Kconfig

Along with devicetree, Zephyr also uses the Kconfig language to configure the source code. Whether to use devicetree or Kconfig for a particular purpose can sometimes be confusing. This section should help you decide which one to use.

In short:

  • Use devicetree to describe hardware and its boot-time configuration. Examples include peripherals on a board, boot-time clock frequencies, interrupt lines, etc.

  • Use Kconfig to configure software support to build into the final image. Examples include whether to add networking support, which drivers are needed by the application, etc.

In other words, devicetree mainly deals with hardware, and Kconfig with software.

For example, consider a board containing a SoC with 2 UART, or serial port, instances.

  • The fact that the board has this UART hardware is described with two UART nodes in the devicetree. These provide the UART type (via the compatible property) and certain settings such as the address range of the hardware peripheral registers in memory (via the reg property).

  • Additionally, the UART boot-time configuration is also described with devicetree. This could include configuration such as the RX IRQ line’s priority and the UART baud rate. These may be modifiable at runtime, but their boot-time configuration is described in devicetree.

  • Whether or not to include software support for UART in the build is controlled via Kconfig. Applications which do not need to use the UARTs can remove the driver source code from the build using Kconfig, even though the board’s devicetree still includes UART nodes.

As another example, consider a device with a 2.4GHz, multi-protocol radio supporting both the Bluetooth Low Energy and 802.15.4 wireless technologies.

  • Devicetree should be used to describe the presence of the radio hardware, what driver or drivers it’s compatible with, etc.

  • Boot-time configuration for the radio, such as TX power in dBm, should also be specified using devicetree.

  • Kconfig should determine which software features should be built for the radio, such as selecting a BLE or 802.15.4 protocol stack.

There are two noteworthy exceptions to these rules:

  • Devicetree’s chosen keyword, which allows the user to select a specific instance of a hardware device to be used for a particular purpose. An example of this is selecting a particular UART for use as the system’s console.

  • Devicetree’s status keyword, which allows the user to enable or disable a particular instance of a hardware device. This takes precedence over related Kconfig options which serve a similar purpose.

Currently supported boards

Devicetree is currently supported on all embedded targets except posix (boards/posix).

Adding support for a board

Adding devicetree support for a given board requires adding a number of files. These files will contain the DTS information that describes a platform, the bindings in YAML format, and any fixup files required to support the platform.

It is best practice to separate common peripheral information that could be used across multiple cores, SoC families, or boards in .dtsi files, reserving the .dts suffix for the primary DTS file for a given board.

Devicetree Source File Template

A board’s .dts file contains at least a version line, optional includes, and a root node definition with model and compatible properties. These property values denote the particular board.


#include <vendor/soc.dtsi>

/ {
        model = "Human readable board name";
        compatible = "vendor,soc-on-your-board's-mcu";
        /* rest of file */

You can use other board .dts files as a starting point.

The following is a more precise list of required files:

  • Base architecture support

    • Add architecture-specific DTS directory, if not already present. Example: dts/arm for Arm.

    • Add target specific devicetree files for base SoC. These should be .dtsi files to be included in the board-specific devicetree files.

    • Add target specific YAML binding files in the dts/bindings/ directory. Create the yaml directory if not present.

  • SoC family support

    • Add one or more SoC family .dtsi files that describe the hardware for a set of devices. The file should contain all the relevant nodes and base configuration that would be applicable to all boards utilizing that SoC family.

    • Add SoC family YAML binding files that describe the nodes present in the .dtsi file.

  • Board specific support

    • Add a board level .dts file that includes the SoC family .dtsi files and enables the nodes required for that specific board.

    • Board .dts file should specify the SRAM and FLASH devices, if present.

      • Flash device node might specify flash partitions. For more details see Flash Partitions

    • Add board-specific YAML binding files, if required. This would occur if the board has additional hardware that is not covered by the SoC family .dtsi/.yaml files.

  • Fixup files

    • Fixup files contain mappings from existing Kconfig options to the actual underlying DTS derived configuration #defines. Fixup files are temporary artifacts until additional DTS changes are made to make them unnecessary.

  • Overlay Files (optional)

    • Overlay files contain tweaks or changes to the SoC and Board support files described above. They can be used to modify devicetree configurations without having to change the SoC and Board files. See Devicetree Overlays for more information on overlay files and the Zephyr build system.

aliases and chosen nodes

Using an alias with a common name for a particular node makes it easier for you to write board-independent source code. Devicetree aliases nodes are used for this purpose, by mapping certain generic, commonly used names to specific hardware resources:

aliases {
   led0 = &led0;
   sw0 = &button0;
   sw1 = &button1;
   uart-0 = &uart0;
   uart-1 = &uart1;

Certain software subsystems require a specific hardware resource to bind to in order to function properly. Some of those subsystems are used with many different boards, which makes using the devicetree chosen nodes very convenient. By doing, so the software subsystem can rely on having the specific hardware peripheral assigned to it. In the following example we bind the shell to uart1 in this board:

chosen {
   zephyr,shell-uart = &uart1;

The table below lists Zephyr-specific chosen properties. The macro identifiers that start with CONFIG_* are generated from Kconfig symbols that reference devicetree data via the Kconfig preprocessor.


Since the particular devicetree isn’t known while generating Kconfig documentation, the Kconfig symbol reference pages linked below do not include information derived from devicetree. Instead, you might see e.g. an empty default:

default "" if HAS_DTS

To see how the preprocessor is used for a symbol, look it up directly in the Kconfig file where it is defined instead. The reference page for the symbol gives the definition location.

chosen node name

Generated macros



























Adding support for devicetree in drivers

As drivers and other source code is converted over to make use of devicetree generated information, these drivers may require changes to match the generated #define information.

Source Tree Hierarchy

The devicetree files are located in a couple of different directories. The directory split is done based on architecture, and there is also a common directory where architecture agnostic devicetree and YAML binding files are located.

Assuming the current working directory is the ZEPHYR_BASE, the directory hierarchy looks like the following:


The common directory contains a skeleton.dtsi which provides devicetree root node definition. The bindings subdirectory contains YAML binding files used to instruct how the python DTS parsing script should extract nodes information in a format that will be usable by the system.

Example: Subset of DTS/YAML files for NXP FRDM K64F (Subject to Change):


Devicetree Bindings

.dts files describe the available hardware devices, but don’t tell the system which pieces of information are useful, or what kind of configuration output (#define’s) should be generated. Bindings provide this information. Bindings are files in YAML format.

Configuration output is only generated for devices that have bindings.

Nodes are mapped to bindings via their compatible string(s). Take the following node as an example:

bar-device {
     compatible = "foo-company,bar-device";

This node would get mapped to a binding with this in it:

compatible: "foo-company,bar-device"

You might also run across this legacy syntax, which works the same way:


        constraint: "foo-company,bar-device"


Bindings are stored in dts/bindings/. The filename usually matches the compatible string.

If a node has more than one compatible string, then the first binding found is used, going from the first string to the last. For example, a node with compatible = "foo-company,bar-device", "generic-bar-device" would get mapped to the binding for generic-bar-device if there is no binding for foo-company,bar-device.

If a node appears on a bus (e.g. I2C or SPI), then the bus type is also taken into account when mapping nodes to bindings. See the description of parent and child in the template below.

Below is a template that shows the format of binding files, stored in dts/binding-template.yaml.

description: |
    Free-form description of the device/node. Can have multiple

    See for formatting help.

# Used to map nodes to bindings
compatible: "manufacturer,device"

# The 'compatible' above would match this node:
#     device {
#         compatible = "manufacturer,device";
#         ...
#     };
# Assuming no binding has 'compatible: "manufacturer,device-v2"', it would also
# match this node:
#     device {
#         compatible = "manufacturer,device-v2", "manufacturer,device";
#         ...
#     };
# Strings in 'compatible' properties on nodes are tried from left to right, and
# the first binding found is used.
# If more than one binding for a compatible is found, an error is raised.

# Bindings can include other files, which can be used to share common
# definitions between bindings.
# Included files are merged into bindings with a simple recursive dictionary
# merge. It is up to the binding author to make sure that the final merged
# binding is well-formed, though it is checked by the code as well.
# It is an error if a key appears with a different value in a binding and in a
# file it includes, with one exception: A binding can have 'required: true' for
# some property for which the included file has 'required: false' (see the
# description of 'properties' below). The 'required: true' from the binding
# takes precedence, allowing bindings to strengthen requirements from included
# files.
# Note that weakening requirements by having 'required: false' where the
# included file has 'required: true' is an error. This is meant to keep the
# organization clean.
# The file base.yaml contains definitions for many common properties. When
# writing a new binding, it is a good idea to check if base.yaml already
# defines some of the needed properties, and including it in that case. Note
# that you can make a property defined in base.yaml obligatory like this
# (taking 'reg' as an example):
#     reg:
#         required: true
# This relies on the dictionary merge to fill in the other keys for 'reg', like
# 'type'.
# When including multiple files, any overlapping 'required' keys on properties
# in the included files are ORed together. This makes sure that a
# 'required: true' is always respected.
include: other.yaml # or [other1.yaml, other2.yaml]

# If the node describes a bus, then the bus type should be given, like below
bus: <string describing bus type, e.g. "i2c">

# If the node appears on a bus, then the bus type should be given, like below.
# When looking for a binding for a node, the code checks if the binding for the
# parent node contains 'bus: <bus type>'. If it does, then only bindings with a
# matching 'on-bus: <bus type>' are considered. This allows the same type of
# device to have different bindings depending on what bus it appears on.
on-bus: <string describing bus type, e.g. "i2c">

# 'properties' describes properties on the node, e.g.
#   reg = <1 2>;
#   current-speed = <115200>;
#   label = "foo";
# This is used to check that required properties appear, and to
# control the format of output generated for them. Except for some
# special-cased properties like 'reg', only properties listed here will
# generate output.
# A typical property entry looks like this:
#   <property name>:
#     required: <true | false>
#     type: <string | int | boolean | array | uint8-array | string-array |
#            phandle | phandles | phandle-array | compound>
#     description: <description of the property>
#     enum:
#       - <item1>
#       - <item2>
#       ...
#       - <itemN>
#     const: <string | int>
#     default: <default>
# These types are available:
#   - 'type: int' is for properties that are assigned a single 32-bit value,
#     like
#         frequency = <100>;
#   - 'type: array' is for properties that are assigned zero or more 32-bit
#     values, like
#         pin-config = <1 2 3>;
#   - 'type: uint8-array' is for properties that are assigned zero or more
#     bytes with the [] syntax, like
#         lookup-table = [89 AB CD EF];
#     Each byte is given in hex.
#     This type is called 'bytestring' in the Devicetree specification.
#   - 'type: string' is for properties that are assigned a single string, like
#         ident = "foo";
#   - 'type: string-array' if for properties that are assigned zero or more
#     strings, like
#         idents = "foo", "bar", "baz";
#   - 'type: boolean' is for properties used as flags that don't take a value,
#     like
#         hw-flow-control;
#     The macro generated for the property gets set to 1 if the property exists
#     on the node, and to 0 otherwise. When combined with 'required: true',
#     this type just forces the flag to appear on the node. The output will
#     always be 1 in that case.
#     Warning: Since a macro is always generated for 'type: boolean'
#     properties, don't use #ifdef in tests. Do this instead:
#         #if DT_SOME_BOOLEAN_PROP == 1
#   - 'type: phandle' is for properties that are assigned a single phandle,
#     like
#         foo = <&label>;
#   - 'type: phandles' is for properties that are assigned zero or more
#     phandles, like
#         foo = <&label1 &label2 ...>;
#   - 'type: phandle-array' is for properties that take a list of phandles and
#     (possibly) 32-bit numbers, like
#         pwms = <&ctrl-1 1 2 &ctrl-2 3 4>;
#     This type requires that the property works in the standard way that
#     devicetree properties like pwms, clocks, *-gpios, and io-channels work.
#     Taking 'pwms' as an example, the final -s is stripped from the property
#     name, and #pwm-cells is looked up in the node for the controller
#     (&ctrl-1/&ctrl-2) to determine the number of data values after the
#     phandle. The binding for each controller must also have a *-cells key
#     (e.g. pwm-cells), giving names to data values. See below for an
#     explanation of *-cells.
#     A *-names (e.g. pwm-names) property can appear on the node as well,
#     giving a name to each entry (the 'pwms' example above has two entries,
#     <&ctrl-1 1 2> and <&ctrl-2 3 4>).
#     Because other property names are derived from the name of the property by
#     removing the final -s, the property name must end in -s. An error is
#     raised if it doesn't.
#     *-gpios properties are special-cased so that e.g. foo-gpios resolves to
#     #gpio-cells rather than #foo-gpio-cells.
#     All phandle-array properties support mapping through *-map properties,
#     e.g. gpio-map. See the devicetree spec.
#   - 'type: path' is for properties that are assigned a path. Usually, this
#     would be done with a path reference:
#         foo = &label;
#     Plain strings are accepted too, and are verified to be a path to an
#     existing node:
#         foo = "/path/to/some/node";
#   - 'type: compound' is a catch-all for more complex types, e.g.
#         foo = <&label>, [01 02];
# 'type: array' and the other array types also allow splitting the value into
# several <> blocks, e.g. like this:
#     foo = <1 2>, <3 4>;                         // Okay for 'type: array'
#     foo = <&label1 &label2>, <&label3 &label4>; // Okay for 'type: phandles'
#     foo = <&label1 1 2>, <&label2 3 4>;         // Okay for 'type: phandle-array'
#     etc.
# The optional 'default:' setting gives a value that will be used if the
# property is missing from the device tree node. If 'default: <default>' is
# given for a property <prop> and <prop> is missing, then the output will be as
# if '<prop> = <default>' had appeared (except YAML data types are used for the
# default value).
# Note that it only makes sense to combine 'default:' with 'required: false'.
# Combining it with 'required: true' will raise an error.
# See below for examples of 'default:'. Putting 'default:' on any property type
# besides those used in the examples will raise an error.
    # Describes a property like 'current-speed = <115200>;'. We pretend that
    # it's obligatory for the example node and set 'required: true'.
        type: int
        required: true
        description: Initial baud rate for bar-device

    # Describes an optional property like 'keys = "foo", "bar";'
        type: string-array
        required: false
        description: Keys for bar-device

    # Describes an optional property like 'maximum-speed = "full-speed";
    # the enum specifies known values that the string property may take
        type: string
        required: false
        description: Configures USB controllers to work up to a specific speed.
           - "low-speed"
           - "full-speed"
           - "high-speed"
           - "super-speed"

    # Describes a required property '#address-cells = <1>';  the const
    # specifies that the value for the property is expected to be the value 1
        type: int
        required: true
        const: 1

        type: int
        required: false
        default: 123

        type: array
        required: false
        default: [1, 2, 3] # Same as 'array-with-default = <1 2 3>'

        type: string
        required: false
        default: "foo"

        type: string-array
        required: false
        default: ["foo", "bar"] # Same as 'string-array-with-default = "foo", "bar"'

        type: uint8-array
        required: false
        default: [0x12, 0x34] # Same as 'uint8-array-with-default = [12 34]'

# 'child-binding' can be used when a node has children that all share the same
# properties. Each child gets the contents of 'child-binding' as its binding
# (though an explicit 'compatible = ...' on the child node takes precedence, if
# a binding is found for it).
# The example below is for a binding for PWM LEDs, where the child nodes are
# required to have a 'pwms' property. It corresponds to this .dts structure
# (assuming the binding has 'compatible: "pwm-leds"'):
# pwmleds {
#         compatible = "pwm-leds";
#         red_pwm_led {
#                 pwms = <&pwm3 4 15625000>;
#         };
#         green_pwm_led {
#                 pwms = <&pwm3 0 15625000>;
#         };
#         ...
# };
    title: PWM LED
    description: LED that uses PWM

            type: phandle-array
            required: true

# 'child-binding' also works recursively. For example, the binding below would
# provide a binding for the 'grandchild' node in this .dts (assuming
# 'compatible: "foo"'):
# parent {
#         compatible = "foo";
#         child {
#                 grandchild {
#                         prop = <123>;
#                 };
#         };
# }
# WARNING: Due to implementation issues with legacy code, only up to two levels
# of 'child-binding:' nesting (like below) is supported. This restriction will
# go away in Zephyr 2.2.
    title: ...
    description: ...


        title: ...
        description: ...

                type: int
                required: true

# If the binding describes an interrupt controller, GPIO controller, pinmux
# device, or any other node referenced by other nodes via 'phandle-array'
# properties, then *-cells should be given.
# To understand the purpose of *-cells, assume that some node has
#     pwms = <&pwm-ctrl 1 2>;
# , where &pwm-ctrl refers to a node whose binding is this file.
# The <1 2> part of the property value is called a *specifier* (this
# terminology is from the devicetree specification), and contains additional
# data associated with the GPIO. Here, the specifier has two cells, and the
# node pointed at by &gpio-ctrl is expected to have '#pwm-cells = <2>'.
# *-cells gives a name to each cell in the specifier. These names are used when
# generating identifiers.
# In this example, assume that 1 refers to a pin and that 2 is a flag value.
# This gives a *-cells assignment like below.
    - channel # name of first cell
    - period  # name of second cell

# If the specifier is empty (e.g. '#clock-cells = <0>'), then *-cells can
# either be omitted (recommended) or set to an empty array. Note that an empty
# array is specified as e.g. 'clock-cells: []' in YAML.

# As a special case, all *-gpio properties map to the key 'gpio-cells',
# regardless of prefix
    - pin
    - flags

# This older syntax is deprecated and will generate a warning when used. It
# works as a catch-all, where the name of the referencing 'phandle-array'
# property doesn't matter.
    - pin
    - flags

Legacy binding syntax

Various parts of the binding syntax were simplified and generalized for the Zephyr 2.1 release.

The binding below shows various legacy syntax.

title: ...
description: ...

    !include foo.yaml

    bus: spi

parent-bus: spi

        constraint: "company,device"
        type: string-array

        type: int
        category: optional

            type: int
            category: required

# Assume this is a binding for an interrupt controller
    - irq
    - priority
    - flags

This should now be written like this:

description: ...

compatible: "company,device"

include: foo.yaml

bus: spi

        type: int
        required: false

    description: ...

            type: int
            required: true

    - irq
    - priority
    - cells

The legacy syntax is still supported for backwards compatibility, but generates deprecation warnings. Support will be dropped in the Zephyr 2.3 release.

GPIO Nexus Nodes

Each board has a set of General Purpose Input/Output (GPIO) peripherals that can be accessed through the GPIO module. Many boards provide headers that allow shields from other vendors to be mounted on their boards. Each shield identifies its hardware in a devicetree overlay.

GPIOs accessed by the shield peripherals must be identified using the shield GPIO abstraction, for example from the arduino-r3-header compatible. Boards that provide the header must map the header pins to SOC-specific pins. This is accomplished by including a nexus node that looks like the following into the board devicetree file:

arduino_header: connector {
        compatible = "arduino-header-r3";
        #gpio-cells = <2>;
        gpio-map-mask = <0xffffffff 0xffffffc0>;
        gpio-map-pass-thru = <0 0x3f>;
        gpio-map = <0 0 &gpioa 0 0>,    /* A0 */
                   <1 0 &gpioa 1 0>,    /* A1 */
                   <2 0 &gpioa 4 0>,    /* A2 */
                   <3 0 &gpiob 0 0>,    /* A3 */
                   <4 0 &gpioc 1 0>,    /* A4 */
                   <5 0 &gpioc 0 0>,    /* A5 */
                   <6 0 &gpioa 3 0>,    /* D0 */
                   <7 0 &gpioa 2 0>,    /* D1 */
                   <8 0 &gpioa 10 0>,   /* D2 */
                   <9 0 &gpiob 3 0>,    /* D3 */
                   <10 0 &gpiob 5 0>,   /* D4 */
                   <11 0 &gpiob 4 0>,   /* D5 */
                   <12 0 &gpiob 10 0>,  /* D6 */
                   <13 0 &gpioa 8 0>,   /* D7 */
                   <14 0 &gpioa 9 0>,   /* D8 */
                   <15 0 &gpioc 7 0>,   /* D9 */
                   <16 0 &gpiob 6 0>,   /* D10 */
                   <17 0 &gpioa 7 0>,   /* D11 */
                   <18 0 &gpioa 6 0>,   /* D12 */
                   <19 0 &gpioa 5 0>,   /* D13 */
                   <20 0 &gpiob 9 0>,   /* D14 */
                   <21 0 &gpiob 8 0>;   /* D15 */

This specifies how Arduino pin references like <&arduino_header 11 0> are converted to SOC gpio pin references like <&gpiob 4 0>.

In Zephyr GPIO specifiers generally have two parameters (indicated by #gpio-cells = <2>): the pin number and a set of flags. The low 6 bits of the flags correspond to features that can be configured in devicetree. In some cases it’s necessary to use a non-zero flag value to tell the driver how a particular pin behaves, as with:

drdy-gpios = <&arduino_header 11 GPIO_ACTIVE_LOW>;

After preprocessing this becomes <&arduino_header 11 1>. Normally the presence of such a flag would cause the map lookup to fail, because there is no map entry with a non-zero flags value. The gpio-map-mask property specifies that, for lookup, all bits of the pin and all but the low 6 bits of the flags are used to identify the specifier. Then the gpio-map-pass-thru specifies that the low 6 bits of the flags are copied over, so the SOC GPIO reference becomes <&gpiob 4 1> as intended.

See nexus node for more information about this capability.

Flash Partitions

Device tree can be used to describe a partition layout for any flash device in the system.

Two important uses for this mechanism are:

  1. To force the Zephyr image to be linked into a specific area on Flash.

    This is useful, for example, if the Zephyr image must be linked at some offset from the flash device’s start, to be loaded by a bootloader at runtime.

  2. To generate compile-time definitions for the partition layout, which can be shared by Zephyr subsystems and applications to operate on specific areas in flash.

    This is useful, for example, to create areas for storing file systems or other persistent state. These defines only describe the boundaries of each partition. They don’t, for example, initialize a partition’s flash contents with a file system.

Partitions are generally managed using device tree overlays. Refer to Devicetree Overlays for details on using overlay files.

Defining Partitions

The partition layout for a flash device is described inside the partitions child node of the flash device’s node in the device tree.

You can define partitions for any flash device on the system.

Most Zephyr-supported SoCs with flash support in device tree will define a label flash0. This label refers to the primary on-die flash programmed to run Zephyr. To generate partitions for this device, add the following snippet to a device tree overlay file:

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                /* Define your partitions here; see below */

To define partitions for another flash device, modify the above to either use its label or provide a complete path to the flash device node in the device tree.

The content of the partitions node looks like this:

partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        partition1_label: partition@START_OFFSET_1 {
                label = "partition1_name";
                reg = <0xSTART_OFFSET_1 0xSIZE_1>;

        /* ... */

        partitionN_label: partition@START_OFFSET_N {
                label = "partitionN_name";
                reg = <0xSTART_OFFSET_N 0xSIZE_N>;


  • partitionX_label are device tree labels that can be used elsewhere in the device tree to refer to the partition

  • partitionX_name controls how defines generated by the Zephyr build system for this partition will be named

  • START_OFFSET_x is the start offset in hexadecimal notation of the partition from the beginning of the flash device

  • SIZE_x is the hexadecimal size, in bytes, of the flash partition

The partitions do not have to cover the entire flash device. The device tree compiler currently does not check if partitions overlap; you must ensure they do not when defining them.

Example Primary Flash Partition Layout

Here is a complete (but hypothetical) example device tree overlay snippet illustrating these ideas. Notice how the partitions do not overlap, but also do not cover the entire device.

&flash0 {
        partitions {
                compatible = "fixed-partitions";
                #address-cells = <1>;
                #size-cells = <1>;

                code_dts_label: partition@8000 {
                        label = "zephyr-code";
                        reg = <0x00008000 0x34000>;

                data_dts_label: partition@70000 {
                        label = "application-data";
                        reg = <0x00070000 0xD000>;

Linking Zephyr Within a Partition

To force the linker to output a Zephyr image within a given flash partition, add this to a device tree overlay:

/ {
        chosen {
                zephyr,code-partition = &slot0_partition;

If the chosen node has no zephyr,code-partition property, the application image link uses the entire flash device. If a zephyr,code-partition property is defined, the application link will be restricted to that partition.

Flash Partition Macros

The Zephyr build system generates definitions for each flash device partition. These definitions are available to any files which include <zephyr.h>.

Consider this flash partition:

dts_label: partition@START_OFFSET {
        label = "def-name";
        reg = <0xSTART_OFFSET 0xSIZE>;

The build system will generate the following corresponding defines:

#define FLASH_AREA_DEF_NAME_LABEL        "def-name"

As you can see, the label property is capitalized when forming the macro names. Other simple conversions to ensure it is a valid C identifier, such as converting “-” to “_”, are also performed. The offsets and sizes are available as well.

MCUboot Partitions

MCUboot is a secure bootloader for 32-bit microcontrollers.

Some Zephyr boards provide definitions for the flash partitions which are required to build MCUboot itself, as well as any applications which must be chain-loaded by MCUboot.

The device tree labels for these partitions are:


This is the partition where the bootloader is expected to be placed. MCUboot’s build system will attempt to link the MCUboot image into this partition.


MCUboot loads the executable application image from this partition. Any application bootable by MCUboot must be linked to run from this partition.


This is the partition which stores firmware upgrade images. Zephyr applications which receive firmware updates must ensure the upgrade images are placed in this partition (the Zephyr DFU subsystem can be used for this purpose). MCUboot checks for upgrade images in this partition, and can move them to slot0_partition for execution. The slot0_partition and slot1_partition must be the same size.


This partition is used as temporary storage while swapping the contents of slot0_partition and slot1_partition.


Upgrade images are only temporarily stored in slot1_partition. They must be linked to execute of out of slot0_partition.

See the MCUboot documentation for more details on these partitions.

File System Partitions


This is the area where e.g. NFFS expects its partition.