EmbeddedRelated.com
Blogs

Getting Started With Zephyr: Devicetree Overlays

Mohammed BillooSeptember 25, 2023

Introduction

In the previous two blog posts (https://embeddedrelated.com/showarticle/1547.php and https://embeddedrelated.com/showarticle/1562.php), I demonstrated the “Devicetree” in The Zephyr Project RTOS. The Devicetree allows us to describe the hardware in our system to the OS. As we saw in the first blog post, Zephyr supports many popular System-On-Chips (SoCs) and corresponding development boards. Thus, Zephyr already has a Devicetree for each board and corresponding SoC. When I begin writing firmware for a new project, I target a development board containing the SoC that will be used in the final product design. However, the configuration of the development board, such as the pinouts or peripheral characteristics, of the final design are usually not identical to the development board. To address these differences, I use “Devicetree overlays”, a mechanism supported by Zephyr to override the configuration of an existing Devicetree. In this blog post, I will demonstrate how a Devicetree overlay can be used.

I2C On A Nordic nRF52840

The best way to illustrate how to use Devicetree overlays is with an example. In this blog post, I will show how to modify the pin that is used for one of the I2C peripherals on the Nordic nRF52840 development kit (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk). First, we must identify the default pins for the “I2C0” peripheral on the nRF52840 development kit. As I showed in a previous blog post, the Devicetree for this board is specified in our Zephyr checkout under boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts. If we open this file, and search for “i2c0”, we find the following entry:

arduino_i2c: &i2c0 {
        compatible = "nordic,nrf-twi";
        status = "okay";
        pinctrl-0 = <&i2c0_default>;
        pinctrl-1 = <&i2c0_sleep>;
        pinctrl-names = "default", "sleep";
};

The pins used for the i2c0 peripheral are specified by the “pinctrl-0” and “pinctrl-1” entries in the node, which point to the “i2c0_default” and “i2c0_sleep” nodes, respectively. However, “i2c0_default” is not found in this file. Instead, it is found in “nrf52840dk_nrf52840-pinctrl.dtsi” in the same directory. If we open this file, and search again for “i2c0”, we find the following entries:

This article is available in PDF format for easy printing
i2c0_default: i2c0_default {
    group1 {
        psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
                <NRF_PSEL(TWIM_SCL, 0, 27)>;
    };
};
i2c0_sleep: i2c0_sleep {
    group1 {
        psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
                <NRF_PSEL(TWIM_SCL, 0, 27)>;
        low-power-enable;
    };
};

The above listing informs us that the default pins for the I2C0 peripheral on the nRF52840 development kit are 0.26 (for SDA) and 0.27 (for SCL). In a future blog post, I’ll describe the difference between the “default” and “sleep” nodes and why specifying different pins for both may be beneficial. We must first build an application targeting the nRF52840 development kit to view the final device tree using the default configuration. Any example will suffice and in this blog post, we build the blinky application as shown below:

[zephyr_3.2 09:14:57]$ west build -p always -b nrf52840dk_nrf52840 zephyr/samples/basic/blinky/

Our final devicetree is the “zephyr.dts” file, located under the “build/zephyr” directory:

If we search for “i2c0” in this file, we find the following:

i2c0: arduino_i2c: i2c@40003000 {
        compatible = "nordic,nrf-twi";
        #address-cells = < 0x1 >;
        #size-cells = < 0x0 >;
        reg = < 0x40003000 0x1000 >;
        clock-frequency = < 0x186a0 >;
        interrupts = < 0x3 0x1 >;
        status = "okay";
        pinctrl-0 = < &i2c0_default >;
        pinctrl-1 = < &i2c0_sleep >;
        pinctrl-names = "default", "sleep";
};

Similarly, if we search for “i2c0_default”, we find the following, which is the fully specified pin corresponding to “0.26” and “0.27” for the “SDA” and “SCL” lines, respectively:

i2c0_default: i2c0_default {
        phandle = < 0x4 >;
        group1 {
               psels = < 0xc001a >, < 0xb001b >;
        };
};

Pin Customization Using An Overlay

Next, I will demonstrate how the default pins used for the I2C0 peripheral can be changed. First, let’s copy the “blinky” application out of the Zephyr source tree and into a separate directory:

[zephyr_3.2 09:32:31]$ cp -r zephyr/samples/basic/blinky ./

Then, let’s create a “boards” directory inside the application:

[zephyr_3.2 09:39:08]$ mkdir blinky/boards

Finally, let’s create a file called “nrf52840dk_nrf52840.overlay” inside “blinky/boards” with the following content:

&pinctrl {
   custom_i2c: custom_i2c {
       group1 {
           psels = <NRF_PSEL(TWIM_SDA, 1, 15)>,
                   <NRF_PSEL(TWIM_SCL, 1, 14)>;
       };
    };
};
&arduino_i2c {
    pinctrl-0 = <&custom_i2c>;
    pinctrl-1 = <&custom_i2c>;
    pinctrl-names = "default", "sleep";
};

The above overlay references the “pinctrl” node via the “&” symbol. It creates a new node called “custom_i2c” inside the pinctrl node and indicates that pins “1.15” and “1.14” should be used for the SDA and SCL lines, respectively. Then, the file references the “arduino_i2c” node and overrides the existing pin definitions with the node above. We can ave this file and rebuild our custom blinky application:

[zephyr_3.2 09:53:09]$ west build -p always -b nrf52840dk_nrf52840 blinky/

If we open the final Devicetree file (recall that it’s located under build/zephyr/zephyr.dts), and we search for i2c0, we can see that the node references our “custom_i2c” pin definition:

i2c0: arduino_i2c: i2c@40003000 {
      compatible = "nordic,nrf-twi";
      #address-cells = < 0x1 >;
      #size-cells = < 0x0 >;
      reg = < 0x40003000 0x1000 >;
      clock-frequency = < 0x186a0 >;
      interrupts = < 0x3 0x1 >;
      status = "okay";
      pinctrl-0 = < &custom_i2c >;
      pinctrl-1 = < &custom_i2c >;
      pinctrl-names = "default", "sleep";
};

If we search for the “custom_i2c” node, we can confirm that the resulting pin configuration is different from the configuration specified by the “i2c0_default” node:

custom_i2c: custom_i2c {
     phandle = < 0x4 >;
     group1 {
         psels = < 0xc002f >, < 0xb002e >;
     };
};

Summary

In this blog post, I demonstrated how Devicetree overlays in Zephyr can be used to override the pin configuration described by a standard Devicetree that describes a board supported by Zephyr. In this blog post, I showed how the default I2C pins on a nRF52840 development kit can be changed to a custom set in the overlay. As mentioned, in a future blog post, I will demonstrate how different pin configurations for the "default" and "sleep" modes can be valuable as power-saving features.


To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: