Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 15

ESP32 GPIO Configuration

Recommended resources

Hello! Welcome to the third module of our course.

In the previous module, we focused on the foundational aspects of the ESP-IDF platform, culminating in a lesson on how to read and interpret boot messages. Being able to diagnose startup issues is a critical skill, ensuring you have a stable platform to build upon. Now that you can get your board running reliably, it's time to make it interact with the physical world.

This lesson marks our first step into hardware control. We'll explore the most fundamental peripheral of any microcontroller: the General Purpose Input/Output (GPIO) pins. These pins are the digital "eyes" and "hands" of your ESP32, allowing it to sense switches and control LEDs, motors, and other components. By the end of this lesson, you will be able to configure any of the ESP32's GPIO pins for either input or output modes using the ESP-IDF's core GPIO API.

1. Understanding GPIO Pins

At its core, a GPIO pin is a controllable digital connection. In output mode, you can set its voltage to either HIGH (typically 3.3V on the ESP32) or LOW (0V/Ground). In input mode, you can read the voltage applied to the pin to determine if it's HIGH or LOW. This simple binary control is the basis for almost all digital electronics.

The ESP32 has dozens of these pins, but they are not all identical. Some have special functions or limitations. A pinout diagram is an essential reference for any hardware project.

ESP32 Wroom DevKit Rev2 Full Pinout Diagram
This pinout diagram for a common ESP32 development board shows the location of each pin and its capabilities. Note the color coding, which indicates which pins are input-only (e.g., GPIO34-39) and which are standard input/output. Also, critically, note that the pins are not 5V tolerant.

As you can see, a single physical pin on the board can have multiple functions (e.g., GPIO, ADC, Touch). Our job in software is to tell the ESP32 which function we want to use for a given pin.

For a more detailed list of pin capabilities and restrictions, the official ESP-IDF documentation is the definitive source.

GPIO & RTC GPIO - Official ESP-IDF Documentation

Before you use a pin, it's good practice to check if it has any restrictions. The official ESP-IDF documentation provides a summary table for all GPIOs.

Briefly review the 'GPIO Summary' table. You don't need to memorize it, but get a feel for the kinds of restrictions that exist. For example, notice that GPIOs 34-39 are input-only (GPI), and other pins are 'strapping pins' that affect the boot mode, as we discussed in the last lesson.


2. The GPIO API: A Practical Walkthrough

The ESP-IDF provides a C-language API to control the GPIO hardware. While our course focuses on C++, we'll build our abstractions on top of this C foundation. The main header file for this is driver/gpio.h.

Let's watch a short video that demonstrates the most direct way to configure a pin, showing the common workflow of consulting the documentation to find the right functions and parameters.

ESP32 - How to create your First ESP IDF project (From Scratch)

This video provides a practical demonstration of configuring GPIO pins. It walks through the process of finding the right functions in the official documentation and using them to set a pin's direction and internal pull-up resistor state.

Watch from 7:31 to 10:53. The narrator will configure one pin as an input for a button and another as an output for an LED. Pay attention to the functions used, gpio_set_direction() and gpio_set_pull_mode(), and how the documentation helps in finding the correct parameters like GPIO_MODE_INPUT and GPIO_PULLUP_ONLY.

As you saw in the video, you can configure pins using individual function calls like gpio_set_direction(). This is straightforward for simple cases. However, for more complex setups or for configuring multiple pins at once, the ESP-IDF provides a more powerful and recommended method.


3. A More Robust Method: gpio_config_t

The standard way to configure GPIOs is by populating a gpio_config_t struct and passing it to a single function, gpio_config(). This approach is less error-prone and more efficient, as it sets all properties of one or more pins in a single atomic operation.

Let's look at the official documentation to understand the members of this important struct.

GPIO & RTC GPIO - Official ESP-IDF Documentation

The gpio_config_t structure is the standard way to configure GPIOs in ESP-IDF. Let's look at the official documentation to see how it's defined and what its members do.

Scan the 'Structures' section to find struct gpio_config_t and read the descriptions of its members: pin_bit_mask, mode, pull_up_en, pull_down_en, and intr_type. Then, look at the 'Enumerations' section to see the possible values for gpio_mode_t. This will show you all the options available for configuring a pin's direction.

Putting It All Together: Code Examples

Let's see how to use this struct in practice.

Example 1: Configuring an Output Pin for an LED

Imagine we have an LED connected to GPIO2. We need to configure this pin as a standard output.

#include "driver/gpio.h"

void configure_led() {
    // 1. Create a configuration structure
    gpio_config_t io_conf = {}; // zero-initialize the struct

    // 2. Configure the settings
    // Select the pin to configure
    io_conf.pin_bit_mask = (1ULL << GPIO_NUM_2);
    // Set the mode to output
    io_conf.mode = GPIO_MODE_OUTPUT;
    // Disable pull-up and pull-down resistors
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    // Disable interrupts for this pin
    io_conf.intr_type = GPIO_INTR_DISABLE;

    // 3. Apply the configuration
    gpio_config(&io_conf);
}

Key Points:

  • pin_bit_mask: This is a 64-bit integer where each bit corresponds to a GPIO number. (1ULL << GPIO_NUM_2) creates a mask with only the bit for GPIO2 set. The ULL suffix ensures the 1 is treated as an unsigned long long (64-bit) to prevent overflow on high pin numbers.
  • mode: We set it to GPIO_MODE_OUTPUT.
  • pull_up_en/pull_down_en: For a simple LED output, we don't need internal resistors, so we disable them.

Example 2: Configuring an Input Pin for a Button

Now, let's configure GPIO4 to read a push button. To avoid a "floating" input when the button is not pressed, we will enable the internal pull-up resistor. This pulls the pin's voltage to HIGH by default. When the button is pressed (connecting the pin to Ground), the input will read LOW.

#include "driver/gpio.h"

void configure_button() {
    // 1. Create a configuration structure
    gpio_config_t io_conf = {};

    // 2. Configure the settings
    io_conf.pin_bit_mask = (1ULL << GPIO_NUM_4);
    io_conf.mode = GPIO_MODE_INPUT;
    // Enable the internal pull-up resistor
    io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // Not strictly needed, but explicit
    io_conf.intr_type = GPIO_INTR_DISABLE;

    // 3. Apply the configuration
    gpio_config(&io_conf);
}

Key Point:

  • pull_up_en = GPIO_PULLUP_ENABLE;: This is crucial for reliable button input. Without it, the pin's state would be undefined when the button isn't pressed, leading to random readings.
Test your understanding!

Write the code to configure two pins at the same time using a single gpio_config_t struct. Configure GPIO18 and GPIO19 as outputs.

Show answer

You can combine the pins in the pin_bit_mask using the bitwise OR operator.

#include "driver/gpio.h"

void configure_two_outputs() {
    gpio_config_t io_conf = {};

    // Combine GPIO18 and GPIO19 into a single mask
    io_conf.pin_bit_mask = (1ULL << GPIO_NUM_18) | (1ULL << GPIO_NUM_19);
    
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.intr_type = GPIO_INTR_DISABLE;

    gpio_config(&io_conf);
}

4. Bridging to C++: Encapsulation

As a C++ programmer, you can see that this C-style API, while functional, could be made safer and more expressive. We can wrap this functionality inside classes to manage the GPIO resources automatically and provide a cleaner interface. This is a central theme of using C++ in an embedded context.

Let's look at how a C++ developer might structure this.

ESP32 GPIO in C++ Part 1 - EmbeddedTutorials

A common practice in embedded C++ is to create wrapper classes around low-level C drivers. This tutorial demonstrates an effective way to create GpioInput and GpioOutput classes around the ESP-IDF GPIO driver.

Please read the section 'Implement the methods for the Input Class', focusing on the GpioInput::_init method. Observe how it uses the gpio_config_t struct we just learned about to perform the actual hardware configuration. You don't need to implement this yourself right now, but studying how the C API calls are wrapped inside a C++ class method is a key pattern for this course.

By creating classes like GpioOutput, we can later write code that is much more intuitive, like led.on() or led.off(). The _init method you just reviewed would be called by the class's constructor, ensuring the pin is configured correctly the moment the object is created—a core C++ principle known as RAII (Resource Acquisition Is Initialization).


Conclusion

In this lesson, you've learned the fundamental skill of configuring the ESP32's GPIO pins. This opens the door to countless projects that interact with the physical world.

Here are the key takeaways:

  • GPIO pins are the microcontroller's digital inputs and outputs.
  • Always consult a pinout diagram and be aware of pin-specific limitations (e.g., input-only, strapping pins).
  • The ESP-IDF provides a C API in driver/gpio.h for hardware control.
  • The recommended way to configure pins is by setting up a gpio_config_t struct and calling gpio_config().
  • This method allows you to set the pin number, direction (mode), internal pull-resistors, and interrupt type in a single, robust function call.
  • Encapsulating these C API calls within C++ classes is a powerful technique for writing safer and more maintainable embedded software.

Now that we know how to configure the pins, the next step is to use them. In our next lesson, we will do just that, learning how to write HIGH and LOW values to an output pin and how to read the state of an input pin.