ES: ESP32 GPIO

GPIO (General Purpose Input/Output) is the most fundamental peripheral on the ESP32. This guide covers configuring pins as digital input or output, reading pin states, and writing pin values using the Arduino framework.

GPIO pins are the primary interface between the ESP32 and the physical world. Through GPIO, the ESP32 reads buttons, switches, and digital sensors, and controls LEDs, relays, motors, and other actuators.

Understanding how to configure and use GPIO correctly is the foundation for all embedded work on the ESP32.


What is GPIO?

GPIO (General Purpose Input/Output) pins are digital pins that can be individually configured as:

  • Output — the firmware drives the pin HIGH (3.3V) or LOW (0V)
  • Input — the firmware reads whether the pin is currently HIGH or LOW

The ESP32 has up to 34 accessible GPIO pins, most of which support both input and output. A small number (GPIO34–GPIO39) are input-only.

md
ESP32 GPIO Pin (Output Mode)
-----------------------------
ESP32                      External Circuit
 GPIO ---> 3.3V / 0V ---> LED + Resistor ---> GND


ESP32 GPIO Pin (Input Mode)
----------------------------
Button ---> GPIO (reads HIGH or LOW)
         ^
         |
     Pull-up resistor (to 3.3V) keeps pin HIGH when button is open


GPIO Configuration Using the Arduino Framework

The Arduino framework provides three simple functions for GPIO:

FunctionDescription
pinMode(pin, mode)Configure a pin as INPUT, OUTPUT, or INPUT_PULLUP
digitalWrite(pin, value)Set an output pin HIGH or LOW
digitalRead(pin)Read the current state of an input pin

Step 1: Configure GPIO Mode

Use pinMode() in the setup() function to configure each pin's direction before using it.

cpp
#define GPIO_OUTPUT_PIN  2   // GPIO2 as output (connected to onboard LED)
#define GPIO_INPUT_PIN   4   // GPIO4 as input (connected to a button)

void setup() {
    pinMode(GPIO_OUTPUT_PIN, OUTPUT);  // Configure GPIO2 as output
    pinMode(GPIO_INPUT_PIN,  INPUT);   // Configure GPIO4 as input
}

Input Modes

ModeBehavior
INPUTReads pin state, no internal pull resistor
INPUT_PULLUPEnables internal pull-up resistor (pin reads HIGH when floating)
INPUT_PULLDOWNEnables internal pull-down resistor (pin reads LOW when floating)

For buttons connected between a GPIO and GND, use INPUT_PULLUP to avoid a floating input state:

cpp
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Button pressed:   reads LOW  (connected to GND)
// Button released:  reads HIGH (pulled up to 3.3V)


Step 2: Set GPIO Output

Use digitalWrite() to drive an output pin:

cpp
digitalWrite(GPIO_OUTPUT_PIN, HIGH);  // Set GPIO to 3.3V
digitalWrite(GPIO_OUTPUT_PIN, LOW);   // Set GPIO to 0V (GND)


Step 3: Read GPIO Input

Use digitalRead() to check the current state of an input pin:

cpp
int level = digitalRead(GPIO_INPUT_PIN);  // Returns HIGH (1) or LOW (0)


Complete GPIO Example: Button-Controlled LED

This example reads a button on GPIO4 and controls an LED on GPIO2:

cpp
#include <Arduino.h>

#define LED_PIN    2    // Onboard LED (also connected to boot strapping)
#define BUTTON_PIN 4    // Button connected between GPIO4 and GND

void setup() {
    Serial.begin(115200);
    pinMode(LED_PIN,    OUTPUT);      // LED as output
    pinMode(BUTTON_PIN, INPUT_PULLUP); // Button with internal pull-up
}

void loop() {
    int button_state = digitalRead(BUTTON_PIN);

    if (button_state == LOW) {
        // Button is pressed (active LOW with pull-up)
        digitalWrite(LED_PIN, HIGH);   // Turn LED on
        Serial.println("Button pressed - LED ON");
    } else {
        // Button is released
        digitalWrite(LED_PIN, LOW);    // Turn LED off
        Serial.println("Button released - LED OFF");
    }

    delay(50);  // Small debounce delay
}


The classic blink example using digitalWrite():

cpp
#include <Arduino.h>

#define LED_PIN 2

void setup() {
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    digitalWrite(LED_PIN, HIGH);  // LED on
    delay(500);
    digitalWrite(LED_PIN, LOW);   // LED off
    delay(500);
}

For faster toggling without manually tracking state, keep a variable:

cpp
bool led_state = false;

void loop() {
    led_state = !led_state;
    digitalWrite(LED_PIN, led_state ? HIGH : LOW);
    delay(500);
}


GPIO Summary Table

OperationCodeDescription
Set as outputpinMode(pin, OUTPUT)Pin drives HIGH or LOW
Set as inputpinMode(pin, INPUT)Pin reads external signal
Input with pull-uppinMode(pin, INPUT_PULLUP)Floating pin reads HIGH
Input with pull-downpinMode(pin, INPUT_PULLDOWN)Floating pin reads LOW
Drive HIGHdigitalWrite(pin, HIGH)Pin outputs 3.3V
Drive LOWdigitalWrite(pin, LOW)Pin outputs 0V
Read statedigitalRead(pin)Returns HIGH or LOW

GPIO Limitations on ESP32

LimitationDetail
3.3V logicESP32 GPIO is not 5V tolerant — never connect directly to 5V signals
Max current per pin~12 mA source, ~28 mA sink — use a transistor for high-current loads
GPIO34–GPIO39Input only — cannot be configured as output
GPIO6–GPIO11Reserved for internal SPI flash — do not use
GPIO0, 2, 12, 15Boot-strapping pins — use with care

Final Thoughts

GPIO is the most fundamental building block of ESP32 firmware. Every more complex peripheral — UART, I2C, SPI, ADC — ultimately controls physical voltage levels on GPIO pins.

Mastering pinMode(), digitalWrite(), and digitalRead() provides the foundation for:

  • Driving LEDs and relays
  • Reading buttons and limit switches
  • Interfacing with digital sensors
  • Building hardware state machines

GPIO is where firmware meets the physical world.