ESP32 (9) – Basic I/O

by luca

With this tutorial we’re going to start using the I/O pins of the esp32 chip. The chip features 40 General Purpose I/O pads (GPIO), that are 40 “contacts” you can use in different ways, both for input and output functions.

The setup of those pads is quite complex: the esp32 chip allows indeed to “map” the internal peripherals (UART, SPI…) to different pads, via matrices that can be configured in your firmware. In the technical reference manual you can find a drawing that explains how it works:


In this first post about I/O I’m not going to explain in details the different registers involved… it’s however important that you understand by now that an initial configuration is always required to use a pin, even for basic I/O functionalities. The driver you have to include in your code to use all the functions described below is:

#include "driver/gpio.h"

Basic I/O

As previously said, before using a pin you need to make a proper configuration of the internal registers. After the chip reset, most pins are automatically configured as basic I/O pins; this is not true for all the pins. You can configure a pin as a general purpose I/O pin with the function:

void gpio_pad_select_gpio(uint8_t gpio_num);

The GPIO pins can work in 3 different “directions”:

  • input
  • output
  • open drain
Open Drain
A pin configured in open drain mode can only sink current (if set to a logic level 0) but cannot provide current to an external load; if indeed the pin is set to logic level 1, it is disconnected from the external circuitry.

The corresponding constants are defined in the gpio.h file:


There are some exceptions, in particular pins > 33 can only work as input pins.

You can configure the direction of a pin with the function:

esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode);

gpio_num is the number of the pin to be configured, while mode is one of the three constants shown above. For example if you need to use pin 13 as output, the code is:

gpio_set_direction(GPIO_NUM_13, GPIO_MODE_DEF_OUTPUT);

You can read the pin status, if configured as input, with the function:

int gpio_get_level(gpio_num_t gpio_num);

while if the pin is configured as output or open drain, you can change its status with:

esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);

Pull-up and pull-down

Every pin also includes pull-up and pull-down resistors, you can enable them with the function:

esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull);

the possible constants are:


Alternatively, you can use the dedicated functions to enable/disable a resistor on a pin:

esp_err_t gpio_pullup_en(gpio_num_t gpio_num);
esp_err_t gpio_pullup_dis(gpio_num_t gpio_num);
esp_err_t gpio_pulldown_en(gpio_num_t gpio_num);
esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num);

Multiple configuration

So far, you learned how to configure a single pin. You can also configure some pins at the same time, applying to them the same configuration template.

First you have to declare a gpio_config_t struct:

gpio_config_t pin_config;

The parameters that can be configured are:


The pins you want to configure must be specified in the pin_bit_mask variable as a “bit mask”. You can create it concatenating the different GPIO_SEL_x constants with the OR (|) operator. Let’s see a couple of examples (I’ll blog about interrupts in a future post, for now let’s keep them disabled):


  • pin 2, 3 and 7 configured as output, pull-up and down resistors disabled:


pin_config.pin_bit_mask = GPIO_SEL_2 | GPIO_SEL_3 | GPIO_SEL_7;
pin_config.mode = GPIO_MODE_OUTPUT;
pin_config.pull_up_en = GPIO_PULLUP_DISABLE;
pin_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
pin_config.intr_type = GPIO_PIN_INTR_DISABLE;


  • pin 6 and 8 configured as input, with pull-up resistor:


pin_config.pin_bit_mask = GPIO_SEL_6 | GPIO_SEL_8;
pin_config.mode = GPIO_MODE_INPUT;
pin_config.pull_up_en = GPIO_PULLUP_ENABLE;
pin_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
pin_config.intr_type = GPIO_PIN_INTR_DISABLE;

The configuration template is finally applied with the function gpio_config(&pin_config).


Because this tutorial is about I/O basic concepts, I didn’t prepare dedicated examples… with the framework are indeed shipped some programs that show what I explained above:

Related Posts


Karl Britton Sunday April 16th, 2017 - 03:41 PM

Hi Luca

I have read most of your web-site with interest. I wish I had found it 4 weeks ago while trying to set up Eclipse!
Excellent tutorials!

Would it be possible for you to explain how to send a data packet from a PC to the ESP32 on the same wireless home network (not accessing the external web)?

My goal is to send a simple graphic (much like your LED matrix project) to the ESP32 and use the I/O (SPI) to control some LEDs.

Would it require the ESP32 to have a fixed IP address or could the network be scanned to get the automatically assigned IP address?

Many thanks in advance.

luca Wednesday April 19th, 2017 - 09:19 AM

Hi Karl, thanks for your comment! I’m working on a tutorial for using ESP32 as a server and… something also to solve the problem to “find” the ESP32 on the network 😉

Reply – ESP32 (23) – I2C basic Monday October 9th, 2017 - 08:57 AM

[…] controller I2C sono collegati internamente alla matrice IO_MUX quindi, come vi ho spiegato in un precedente articolo, è possibile assegnare loro via software i diversi pin del chip (con alcune […]

Andrea Friday June 12th, 2020 - 06:42 PM

Grazie per l’interessante articolo se volessi configurare come open drain utilizzando ide di Arduino come potrei farlo?

luca Monday June 15th, 2020 - 10:41 AM

Salve Andrea, i miei tutorial non sono per arduino, mi spiace

arun maisale Wednesday December 5th, 2018 - 05:15 AM

In the Blink example how do I

Can run ‘make menuconfig’ to choose the GPIO to blink,

I cant see Any Settings in MenuConfig where I can set GPIO

luca Friday December 7th, 2018 - 03:51 PM

in the “Example Configuration” menu… see the readme as this is an official example by Espressif


Leave a Reply to – ESP32 (23) – I2C basic Cancel Reply

5 × two =