Mapping Hardware to Software: Understanding Devicetrees

 

We learned how to enable and disable particular Zephyr subsystems using the "Kconfig" infrastructure in the previous blog post, "Getting Started With Zephyr: Kconfig." In particular, we looked at the Kconfig infrastructure's three primary components. To enable the LED subsystem, we first looked at an example of a Kconfig file and how the Kconfig infrastructure is constructed. Second, we observed how the Nordic VS Code Extension's GUI-based Kconfig interface can be used to enable or disable particular Zephyr subsystems. Lastly, we observed a way to modify Zephyr that can be integrated into systems that rely on CI/CD.

 


Adding support for communication buses and peripherals is a standard step in embedded software development, in addition to modifying an RTOS to enable or disable particular subsystems. Other MCU vendors integrate customization features into their IDE, such as enabling communication buses and configuring their parameters, as was discussed in the previous blog post. However, Zephyr also uses the "Devicetree," another construct from the Linux kernel (keep in mind that Kconfig also comes from the Linux kernel). Devicetree, or DT for short, is a data structure that describes hardware using its own language.

 

As ARM-based System-on-Chips (SoCs) and System-on-Modules (SoMs) running embedded Linux gained popularity, so did Devicetrees in embedded software. Bus designs that enabled automatically identifying the hardware connected to the CPU were used when Linux was first developed on Intel-based CPUs. The hardware did not need to be described in advance. However, new communication protocols and buses without any detection mechanism were introduced when ARM exploded onto the scene. As a result, the Linux kernel required a tool to recognize these buses and the devices they were connected to. Though Zephyr borrowed the devicetree concept from the Linux kernel, it is important to remember that Zephyr's use of the devicetree differs sufficiently from the Linux kernel.

 

BASIC SYNTAX

One of the most crucial things to keep in mind about the devicetree—which typically causes anxiety for embedded software engineers who are not familiar with Linux—is that it is neither magical nor enigmatic. All it is is a file with a vague structure. Each node in the file contains a specific set of definitions. Other nodes can include and reference a node. For instance, the LEDs found on a Nordic nRF52840 development kit are described in the following excerpt.

 (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk):

 leds {
        compatible = "gpio-leds";
        led0: led_0 {
            gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
            label = "Green LED 0";
        };
        led1: led_1 {
            gpios = <&gpio0 14 GPIO_ACTIVE_LOW>;
            label = "Green LED 1";
        };
        led2: led_2 {
            gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
            label = "Green LED 2";
        };
        led3: led_3 {
            gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
            label = "Green LED 3";
        };
};

The "leds" node describes the four LEDs onboard the development kit.

As an aside, if you have experience with Linux, you will be familiar with the aforementioned "compatible" property. To control the GPIOs, Zephyr "finds" a device driver with a corresponding "compatible" value and calls the functions listed in the device driver. This is what you might assume right away, as I did. This isn't right! We will go into greater detail in the upcoming blog post about Zephyr's "bindings," which are its own methods for linking devicetree entries to source code. For the time being, it is important to keep in mind that Zephyr and Linux use devicetrees in sufficiently different ways.

Going back to our example, the four LEDs that the "leds" node mentions also mention nodes! For instance, in the line that follows, the "led0" node indicates the precise GPIO that is utilized to control that specific LED:

 led0: led_0 {
        gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
        label = “Green LED 0”;
}

In the above example, GPIO0_13 controls LED0 on the board, an active low GPIO. This node consists of the following elements:

  • "led0" is the "node label", and is used to reference the Devicetree node.
  • “led_0” is the node's "full name." Typically, a node's full name consists of the node name and a unit address (for example, "my-node@12345678"), but a node without an address is acceptable as well (sometimes an address doesn't make sense, as is the case here).
  • ·“gpios” is an example of a "property" of the "led0" node. A property is ultimately used by source code to control hardware in some manner. In this instance, the "gpios" property defines a GPIO port, pin, and the active state. The device driver uses this information to control the GPIO.
  • ·“label” is also a property of the "led0" node. Specifically, the label property can retrieve a more descriptive name for a node. In this example, "Green LED 0" can reference the node instead of "led0".

DEVICETREE LOCATION AND HIERARCHY

An illustration of a devicetree node for the Nordic nRF52840 development kit's LEDs was shown in the section above. Looking through the various devicetrees in the Zephyr repository can be useful (we will see why in a future blog post). The Zephyr repository contains the devicetrees in two main locations. The "boards" directory in the corresponding processor architecture directory contains the devicetrees for the many boards that Zephyr supports. The top-level devicetree for the nRF52840 development kit, for instance, can be found under "boards/arm/nrf52840dk_nrf52840":

https://d23s79tivgl8me.cloudfront.net/user/163185/zephyr_nrf52840dk_dts_directory_76149.png

".dts" is the extension for the "top-level" devicetree file, and ".dtsi" is the extension for intermediate devicetree files that are referenced by the top-level devicetree. It is possible for intermediate devicetree files to contain other intermediate files. The node that represents the LEDs on the board is visible in the nRF52840 development kit's top-level devicetree file, "nrf52840dk_nrf52840.dts," when we open it in the example above!

 

https://d23s79tivgl8me.cloudfront.net/user/163185/zephyr_nrf52840dk_dts_top_level_79004.png

If we scroll to the top of this file, we see the following statements:

/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "nrf52840dk_nrf52840-pinctrl.dtsi"

 

We discovered how to incorporate support for custom hardware into our Zephyr-based firmware using the "Devicetree" in this blog post. We learned how to describe LEDs on a board and specify the corresponding GPIOs from a "blinky" example. Additionally, we discovered where devicetree files are located within the Zephyr repository and how their arrangement enables the description of boards, beginning with the CPU.

 Utilizing device drivers created by others is one of the biggest benefits of using devicetree files in Zephyr over other mechanisms provided by MCU vendors in their IDEs. Usually, we have to write driver code from scratch or import libraries in various MCU-specific environments and IDEs. Instead, we can take advantage of other people's work by using Zephyr's devicetrees and device driver model. We can concentrate on crafting our final application's business logic. We will examine Zephyr's internal workings in the upcoming blog post to learn how it uses devicetrees and how it varies from the Linux kernel.

Ready to integrate your custom hardware into Zephyr-based firmware efficiently? At Silicon Signals, we excel in embedded systems development, offering expertise in hardware design, firmware development, and system optimization. Leverage our capabilities to streamline your projects, utilizing tools like Zephyr's devicetrees and driver models to focus on what truly matters—your application's core functionality.

👉 Get in Touch to explore how we can help you accelerate your embedded solutions!

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