Using the device tree

You can find the consolidated device tree for your application in build/zephyr/zephyr.dts after the build phase. This device tree brings together your board-specific device tree, as well as the overlays defined specifically for your application or for additional components that you may declare to be using (such as an Arduino compatible shield).

Zephyr uses C (or C++) macros to access the device tree. The use of macros allows those to be resolved at compile time: a pre-processing phase compiles the device tree into a set of C headers whose content is manipulated through the macros.

An example: GPIOA

Here is a very simplified excerpt of the device tree for your application centered around gpioa:

/ {
  soc {
    pinctrl: pin-controller@48000000 {
      compatible = "st,stm32-pinctrl";

      gpioa: gpio@48000000 {
        compatible = "st,stm32-gpio";
        gpio-controller;
        #gpio-cells = < 0x2 >;
        reg = < 0x48000000 0x400 >;
        clocks = < &rcc 0x4c 0x1 >;
        phandle = < 0xb >;
      };
    };
  };
};

The device tree is made of nodes arranged in a tree fashion: a node may have children. Also, every node can have attributes.

In this example:

  • / represents the root node which is the base of the tree.
  • soc is a child of /, its path is /soc.
  • pin-controller@48000000 is a child of /soc. Its path is /soc/pin-controller@48000000. We also give this node a label pinctrl. From now on, we will be able to reference this node by its label instead of using its path. From other parts of the device tree itself, we may use &pinctrl to reference the node.
  • gpio@48000000 is a child of /soc/pin-controller@48000000. Its path is /soc/pin-controller@48000000/gpio@48000000. Its label is gpioa. It can be referenced by label as &gpioa from anywhere in the tree.

Let's assume that we want to use gpioa in our application. We can reference the gpioa device by its label and ask Zephyr to find it at compile time:

#include <zephyr/device.h>

// Define `GPIOA_NODE` as the node whose label is `gpioa` in the device tree
#define GPIOA_NODE DT_NODELABEL(gpioa)

// Get the struct device pointer to `gpioa` at compile time
static const struct device * const gpioa_dev = DEVICE_DT_GET(GPIOA_NODE);

Now that you have a reference to gpioa, you can setup the led at run-time and make it blink:

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>

// Define `GPIOA_NODE` as the node whose label is `gpioa` in the device tree
#define GPIOA_NODE DT_NODELABEL(gpioa)

// Get the struct device pointer to `gpioa` at compile time
static const struct device * const gpioa_dev = DEVICE_DT_GET(GPIOA_NODE);

int main() {
  if (!device_is_ready(gpioa_dev)) {
    return -ENODEV;  // Device is not ready
  }
  // Configure the led as an output, initially inactive
  gpio_pin_configure(gpioa_dev, 5, GPIO_OUTPUT_INACTIVE);
  // Toggle the led every second
  for (;;) {
    gpio_pin_toggle(gpioa_dev, 5);
    k_sleep(K_SECONDS(1));
  }
  return 0;
}

Note that Zephyr peripherals are initialized during different boot phases. If somehow you have misconfigured a peripheral, it might not be ready when you start, at which case you should abort. This is what is done at the beginning of main.

Ensure that you can make the led blink. Easy, no? Yes, but you can do much better.

Commit, push.

Ensure that you understand everything you've used before going on.