Adapting to Changing Hardware: Devicetree Overlays

 

Introduction

We showed off the "Devicetree" in the Zephyr Project RTOS in the earlier blogs. We can tell the OS about the hardware in our system thanks to the Devicetree. Zephyr supports a large number of well-known System-On-Chips (SoCs) and related development boards, as was evident in the first blog post. For every board and associated SoC, Zephyr already has a Devicetree. A development board with the SoC that will be utilized in the finished product design is the first thing I aim for when I start writing firmware for a new project.



That being said, the development board's configuration—including pinouts and other peripheral features—usually differs from the final design. I use "Devicetree overlays," a Zephyr-supported method of overriding an existing Devicetree's configuration, to address these discrepancies. I'll walk through how to use a Devicetree overlay in this blog post.

 

I2C On A Nordic nRF52840

An example is the most effective way to demonstrate how to use Devicetree overlays. This blog post will demonstrate how to change the pin on the Nordic nRF52840 development kit (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk) that is utilized for one of the I2C peripherals. Finding the default pins for the nRF52840 development kit's "I2C0" peripheral is the first step. The Devicetree for this board is listed in our Zephyr checkout under boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts, as I demonstrated in a prior blog post. Searching for "i2c0" in this file yields 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 "pinctrl-0" and "pinctrl-1" entries in the node, which point to the "i2c0_default" and "i2c0_sleep" nodes, respectively, specify the pins used for the i2c0 peripheral. Nevertheless, this file does not contain "i2c0_default." Rather, it can be found in the same directory under "nrf52840dk_nrf52840-pinctrl.dtsi." The following entries appear if we open this file and perform another search for "i2c0.":

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;

    };

};

According to the listing above, the nRF52840 development kit's I2C0 peripheral's default pins are 0.26 (for SDA) and 0.27 (for SCL). I'll explain the distinction between the "default" and "sleep" nodes in a later blog post, along with why it might be advantageous to designate distinct pins for each. To view the final device tree with the default configuration, we must first create an application that targets the nRF52840 development kit. Any example will do, and we've built the blinky application as follows in this blog post:

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

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

The fully specified pin that corresponds to "0.26" and "0.27" for the "SDA" and "SCL" lines, respectively, can be found by searching for "i2c0_default."

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

Pin Customization Using An Overlay

I will then show you how to modify the I2C0 peripheral's default pin configuration. Let's start by moving the "blinky" application to a different directory from the Zephyr source tree:

 [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 "&" symbol in the overlay above refers to the "pinctrl" node. Inside the pinctrl node, it generates a new node named "custom_i2c" and specifies that pins "1.15" and "1.14" should be used for the SDA and SCL lines, respectively. The "arduino_i2c" node is then referenced in the file, replacing the current pin definitions with the node above. This file can be saved, and our custom blinky application can be rebuilt:

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

Searching for i2c0 in the final Devicetree file (keep in mind that it's under build/zephyr/zephyr.dts) reveals that the node refers to 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 showed how to use Zephyr Devicetree overlays to override the pin configuration that a standard Devicetree describes for a board that Zephyr supports. In this blog post, I demonstrated how to modify the nRF52840 development kit's default I2C pins to a custom set in the overlay. As previously stated, I will show how various pin configurations for the "default" and "sleep" modes can be useful as power-saving features in a subsequent blog post.

Adapting hardware configurations efficiently is crucial for ensuring the success of your embedded projects. At Silicon Signals, we specialize in creating custom solutions tailored to your needs, from Devicetree overlays to complete hardware and software development. Whether you're looking to modify peripherals or optimize your power-saving features, our team is here to guide you through the complexities and help you build the perfect solution.

👉 Contact Us Today and let's work together to enhance your embedded systems with expert hardware design and software development!

Comments

Popular posts from this blog

How Android System Services Connect Apps and HAL: A Deep Dive

AOSP Passthrough HAL: Architecture, Use Cases & Performance Guide

Getting Started with AOSP: Build Custom Android Solutions