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":

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

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
Post a Comment