Tag Archive for: overlay

Getting your ESP32 GPIO pins working with Zephyr is easy, and using a devicetree overlay file to do so makes it painless to change pins (or even boards/architectures) in the future. Today we’re looking at a simple overlay file for the ESP32 architecture and talking about the syntax used to choose input and output pins.

Overlay File Example

Zephyr uses a description language called devicetree to abstract hardware so your code is extremely portable. Since the ESP32 is a common architecture, this abstraction is already done for us. But we need to tell Zephyr what we plan to do with the GPIO pins by writing a devicetree overlay file.

An overlay file assigns an alias to a physical pin on a microcontroller and configures hardware options for that pin. Here’s an example of the overlay file for the two buttons on a TTGO T-Display board which are connected to GPIO0 and GPIO35.

/ {
    aliases {
        sw0 = &button0;
        sw1 = &button1;
    };
    gpio_keys {
        compatible = "gpio-keys";
        button0: button_0 {
            gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
            label = "Button 0";
        };
        button1: button_1 {
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
            label = "Button 1";
        };
    };
};

Let’s focus on assigning the alias to a specific pin. You can see at the top where sw0 and sw1 are set. These names are commonly use in Zephyr samples; for instance, assigning sw0 here will make our board compatible with the basic/button sample. For us, the important parts are lines 9 and 13 where the actual port, pin, and pin properties are assigned. One thing you should notice: what happened to GPIO35? Let’s get into that in the next section.

Please note that explaining every part of this overlay file is beyond the scope of this article, but you can see a functional overview in our overlay file video series and dig deeper with Zephyr’s Introduction to devicetree documentation.

ESP32 Pin Numbering

ESP32 module pinout

ESP32-WROOM32E

In this diagram you can see how the ESP32-WROOM module (PDF) pins are named using an IO# format. The ESP32 splits these 39 GPIO pins between two different GPIO ports. In Zephyr, the port for the first 32 GPIO pins is called &gpio0 (zero indexed), and the port for the last 7 GPIO pins are called &gpio1 (once again, zero indexed)

  • GPIO0..GPIO31 → port &gpio0, pins 0..31
  • GPIO32..GPIO39 → port &gpio1, pins 0..7

Following that scheme, it’s easy to translate the pins I need from the TTGO T-Display board (GPIO0 and GPIO35) into the numbers the devicetree understands. Because I’m using one of the pins numbered higher than GPIO31, I must switch to port 1 and adjust the pin number to begin counting again from zero – just subtract 32 from GPIO35 to get pin number 3.

  • &gpio0 0
  • &gpio1 3

Pin Functions

The overlay file is also where you want to set up the expected behavior of the pin. Most importantly, this tells Zephyr if the pin will be active high or active low. This pin status addition is valid whether it’s an input or an output.

In the case of my TTGO T-Display board, there are pull-up resistors on the circuit board and pressing the button pulls the pin low, so these are “active low” buttons. Other boards might have pull-down resistors where pressing the button pulls the pin high. Zephyr lets you use the same application code for both boards, and the actual state is translated correctly by the overlay file.

There are other options available, including the ability to turn the ESP32’s internal pull-up/down resistors on. These flags can be added using logical OR:

gpios = <&gpio0 25 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;

Zephyr also makes interrupt-drive GPIO a snap. For more on this, study the basic/button sample which turns on the interrupts and adds a callback to the sw0 pin.

Initializing and Calling the Pins in Your Program

Before we finish up, let’s talk about accessing the pins you have set up in your overlay file. In main.c we need to get the alias and set up a struct that understands devicetree. From there, it’s the normal procedure of initializing the GPIO, and then acting on it: polling an input pin, or changing the state of an output.

First, we can define a node from the overlay file alias (esp32.overlay). That node is used to populate a struct that holds the port, pin number, and configuration of the GPIO pin.

#include <drivers/gpio.h>
#define SW0_NODE DT_ALIAS(sw0)
static const struct gpio_dt_spec button0 = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});

Second, we need to first initialize the pin in main.c. This function passes the port, pin number, and flags (ie: GPIO_ACTIVE_LOW). Note that we’ve added GPIO_INPUT here as an extra flag that sets the direction of the pin.

gpio_pin_configure_dt(&button0, GPIO_INPUT);

To act on the changes to the pin, we can poll its value do something if it is in the active state.

if (gpio_pin_get_dt(&button0) > 0)
{
    LOG_INF("Button0");
}

If we had set this pin as an output, the state changes happen in much the same way, but utilize a function to set the state: gpio_pin_set_dt(spec, value). Notice that the functions and macros that Zephyr uses here all include “dt” in them. This indicates that they are specialized to operate with the devicetree. There are functions to directly set up and manipulate GPIO without using an overlay file, but then you also lose out on the benefits of software portability. To fill in your knowledge around this, take a look at the GPIO section of the Zephyr docs.

ESP32 Overlays are a Snap

Pulling off an ESP32 demo is a snap now that you know how to address the pins. This is a great first adventure into how overlay files work, but of course they are far more powerful than buttons and LEDs. Sensors, displays, UARTs and just about everything else can be plumbed into Zephyr using the overlay file in order to take advantage of the libraries and drivers already present in the RTOS.

A possible solution

Let’s pretend you’re in the middle of a global chip shortage.

Surprise! There’s no need to pretend, as we are all currently in the middle of a global chip shortage. Right now it’s very difficult to source certain components.

“Why don’t hardware makers just switch out the components when they can’t source them during the chip shortage? In fact, why don’t people switch chips on a regular basis?”

As a generalization, switching costs for embedded devices are very high. If we were able to magically solve all of the switching costs for the hardware, you’d still need to deal with the switching costs of firmware and cloud platforms. This often is even more dire than than the hardware switching costs. It’s significant at both an individual level (rewriting firmware to target different architectures and board setups) and at an institutional level (maintaining different platforms and interoperability).

Operating systems and Real Time Operating Systems (RTOS) help by abstracting away a lot of the individual hardware details. When a new device is added to an RTOS, it needs to fit within the constraints of the system. If your board has an i2c sensor on it, you need to ensure your supporting firmware for that board or chipset capable of working with the elements of the RTOS. Then you can take advantage of the drivers already written for other boards/chipsets on the platform. Assuming you are willing to work within that system, you can start to supercharge your development. It’s possible to switch out components quickly and confidently, helping to alleviate the woes of the chip shortage currently underway.

Making the switch

Let’s say you have a board using an ESP32 module. Due to sourcing problems, you can no longer source a particular LED on your board, but you don’t want to change your PCB. Instead, you ask a technician wire in an extra LED to a spare pin you have on your production board that has a larger landing area. You need to build a firmware image to drive a different pin on the microcontroller than you previously were using. Now “LED2” (as it’s called in your program) is not driving pin 18, but is instead driving pin 22. With Zephyr overlays, the switch will take 5 minutes. As Marcin shows in the video below, the device tree overlay is where we map the signals internal to the firmware to the physical pins being used.

Now let’s say you cannot source the ESP32 at all, for some reason. You could create a new overlay file for a different target that works with Zephyr, assign the pins to target the functions you need on a new PCB containing a different chip, and then target that device. The time consuming aspect would be checking all of the functions are performing the same as your previous platform. But once you have decided on a new platform, assigning pins and functions to your new device would occur through overlay files.

How to use Zephyr Overlays

In the video below, we  walk through the location and function of overlays in Zephyr. Marcin explains that customization of firmware images for particular hardware targets can be as simple as a different flag on the command line. In this particular example, we are showing how to change the pins for the ESP32 demo. Previously the ESP32 overlay was shown as part of our our LightDB Sample code (docs), which targeted an ESP32-DevKitC in that video.

About The Zephyr Project

The Zephyr Project is a popular and open source Real Time Operating System (RTOS) that enables complex features and easy connectivity to embedded devices.  The project is focused on vendor participation, long-term support, and in-depth security development life cycles for products.

About Golioth

Golioth helps users to speed up development and increase the chances that pilots will be put into production with a commercial IoT development platform built for scale. We offer standardized interfaces for connecting embedded devices to the cloud and build out software ecosystems that allow your projects to get to market faster. Golioth uses Zephyr as part of the Golioth SDK to bootstrap application examples and show how to utilize the range of networking features Golioth enables via APIs.