The latest development version of this page may be more current than this released 2.2.1 version.

Devicetree HOWTOs

This page has advice for getting things done with Devicetree in Zephyr.

Adding support for a board

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

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.

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 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.

/dts-v1/;

#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.

    • 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.

Note

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

zephyr,flash

DT_FLASH_BASE_ADDRESS/DT_FLASH_SIZE/DT_FLASH_ERASE_BLOCK_SIZE/DT_FLASH_WRITE_BLOCK_SIZE

zephyr,code-partition

DT_CODE_PARTITION_OFFSET/DT_CODE_PARTITION_SIZE

zephyr,sram

CONFIG_SRAM_BASE_ADDRESS/CONFIG_SRAM_SIZE

zephyr,ccm

DT_CCM_BASE_ADDRESS/DT_CCM_SIZE

zephyr,dtcm

DT_DTCM_BASE_ADDRESS/DT_DTCM_SIZE

zephyr,ipc_shm

DT_IPC_SHM_BASE_ADDRESS/DT_IPC_SHM_SIZE

zephyr,console

CONFIG_UART_CONSOLE_ON_DEV_NAME

zephyr,shell-uart

CONFIG_UART_SHELL_ON_DEV_NAME

zephyr,bt-uart

CONFIG_BT_UART_ON_DEV_NAME

zephyr,uart-pipe

CONFIG_UART_PIPE_ON_DEV_NAME

zephyr,bt-mon-uart

CONFIG_BT_MONITOR_ON_DEV_NAME

zephyr,bt-c2h-uart

CONFIG_BT_CTLR_TO_HOST_UART_DEV_NAME

zephyr,uart-mcumgr

CONFIG_UART_MCUMGR_ON_DEV_NAME

Adding support for a device driver

Zephyr device drivers typically use information from devicetree.h to statically allocate and initialize struct device instances. Macros generated from devicetree are usually included via devicetree.h, then 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.

Driver writers should allocate a struct device for each enabled instance of a particular compatible using DT_INST_<instance-number>_<compatible> Node existence flags.

Managing flash partitions

Devicetree 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>;
        };
};

Where:

  • 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;
        };
};

Then, enable the CONFIG_USE_DT_CODE_PARTITION Kconfig option.

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"
#define FLASH_AREA_DEF_NAME_OFFSET_0     0xSTART_OFFSET
#define FLASH_AREA_DEF_NAME_SIZE_0       0xSIZE
#define FLASH_AREA_DEF_NAME_OFFSET       FLASH_AREA_MCUBOOT_OFFSET_0
#define FLASH_AREA_DEF_NAME_SIZE         FLASH_AREA_MCUBOOT_SIZE_0

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:

boot_partition

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.

slot0_partition

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

slot1_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.

scratch_partition

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

Important

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

storage_partition

This is the area where e.g. LittleFS or NVS or FCB expects its partition.