Getting Started with Zephyr: Devicetree Overlays

Getting Started with Zephyr: Devicetree Overlays

Introduction

In the previous two articles (Getting Started with Zephyr: Devicetrees and Getting Started with Zephyr: Devicetree Bindings), I demonstrated the “Devicetree” in the Zephyr Project real-time operating system. Through the Devicetree, we can describe the hardware in the system to the operating system. As mentioned in the first article, Zephyr supports many popular System-on-Chip (SoC) and corresponding development boards. Therefore, Zephyr provides a Devicetree for each board and its corresponding SoC. When I start writing firmware for a new project, my goal is to include the development board for the SoC that will be used in the final product design. However, the configuration of the development board for the final design (such as pin layout or peripheral features) often differs from the development board. To address these differences, I use “Devicetree overlays”, a mechanism supported by Zephyr to override the existing Devicetree configuration. In this blog post, I will demonstrate how to use Devicetree overlays.

I2C on Nordic nRF52840

The best way to illustrate the use of Devicetree overlays is through examples. In this article, I will introduce how to modify the pins for an I2C peripheral on the Nordic nRF52840 development kit (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk). First, we need to identify the default pins for the “I2C0” peripheral on the nRF52840 development kit. As I mentioned in the previous blog post, the Devicetree for this board is specified in the file located at boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts in the Zephyr checkout. If we open that file and search for “i2c0”, we will 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 by the i2c0 peripheral are specified by the “pinctrl-0” and “pinctrl-1” items in the node, which point to the “i2c0_default” and “i2c0_sleep” nodes, respectively. However, the “i2c0_default” is not found in that file. Instead, it is located in the “nrf52840dk_nrf52840-pinctrl.dtsi” file in the same directory. If we open that file and search for “i2c0”, we will find the following entries:

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 list tells us that the default pins for the I2C0 peripheral on the nRF52840 development kit are “0, 26 (SDA)” and “0, 27 (SCL)”. In future articles, I will discuss the differences between the “default” node and the “sleep” node, as well as why it is beneficial to specify different pins for these two nodes. We must first build an application targeting the nRF52840 development kit in order to view the final device tree with default configuration. In this article, we will build a blinky application as shown below:

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

Our final Devicetree is the “zephyr.dts” file located in the “build/zephyr” directory:

Getting Started with Zephyr: Devicetree Overlays

If we search for “i2c0” in that file, we will 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 will find the following content, which fully specifies the pins corresponding to the “SDA” and “SCL” lines as “0,26” and “0,27”:

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

Customizing Pins with Overlay

Next, I will demonstrate how to change the default pins for the I2C0 peripheral. First, copy the “blinky” application from the Zephyr source tree to a separate directory:

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

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

[zephyr_4.0 09:39:08]$ mkdir blinky/boards

Finally, let’s create a file named “nrf52840dk_nrf52840.overlay” in “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 with the “&” symbol. It creates a new node named “custom_i2c” within the pinctrl node and specifies 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 above node. We can delete that file and rebuild our custom blinky application:

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

If we open the final Devicetree file (remember it is located under build/zephyr/zephyr.dts), and search for i2c0, we will find 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 generated pin configuration is different from the configuration specified by the “i2c0_default” node:

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

Conclusion

In this article, I demonstrated how to use Devicetree overlays in Zephyr to modify the pin configuration described in the standard device tree, which is used to describe the development boards supported by Zephyr. I showed how to change the default I2C pin configuration on the nRF52840 development board to a custom pin configuration. As I mentioned, in future articles, I will demonstrate how to implement power-saving features by setting different pin configurations for the “default” and “sleep” modes.

Leave a Comment