1. Introduction: What Does "Output" Mean on the ESP32?
Every GPIO (General Purpose Input/Output) pin on the ESP32 can be configured in software to behave either as an input (reading a signal) or an output (generating a signal). When a pin is configured as an output, the microcontroller can actively drive that pin to one of two voltage levels, which in turn can switch an LED on or off, or trigger any small electronic component.
You can use the ESP32 GPIO pins in output mode just to control anything ranging from a small LED all the way up to high-power devices through relays, SSR, thyristors, etc. In this guide we begin with the simplest case — blinking a single LED — before scaling up to controlling several LEDs simultaneously.
2. Pin Numbering: GPIO Number vs. Board Silkscreen Number
One of the most common points of confusion for beginners is that an ESP32 development board often has two different numbering systems printed near the pins:
- The GPIO number — the actual internal pin number used by the chip (e.g. GPIO13, GPIO27).
- Sometimes a silkscreen label that tries to mimic Arduino-style naming.
For the ESP32, the safest and most reliable approach is to always program using the native GPIO number. The pin numbers are those indicated in the grey "GPIO" insert on the board's pinout, and even if there is a link between the number of GPIO pins of the ESP32 and those usually used on the Arduino, to avoid confusion, it is better not to use them and to use the native pins number of ESP32 exclusively — you should therefore avoid using labels like A0, MOSI, SCK, SDA.
pinMode(13, OUTPUT), the number 13 refers to GPIO13 on the chip — not necessarily the position of the pin on the board edge.
Not every GPIO can be freely used, though. For the ESP32 DEVKIT V1 board, GPIO6 to GPIO11 are connected to the integrated SPI flash and are not recommended for other uses. All GPIOs can be used as outputs except GPIOs 6 to 11 (connected to the integrated SPI flash) and GPIOs 34, 35, 36 and 39 (input‑only GPIOs).
3. Understanding Voltage Logic Levels (HIGH / LOW)
Digital electronics work with only two meaningful voltage states. High level (HIGH) represents the logical "1" or "true", and Low level (LOW) represents logical "0" or "false".
On the ESP32 specifically: on the ESP32 board, HIGH typically means that the pin output is close to 3.3V, and Low level typically means the pin outputs about 0 volts, which is connected to ground (GND).
| State | Written in Code | Approx. Voltage | Effect on LED |
|---|---|---|---|
| HIGH | digitalWrite(pin, HIGH) | ≈ 3.3 V | LED on |
| LOW | digitalWrite(pin, LOW) | ≈ 0 V | LED off |
4. The Basic LED Circuit: Resistor + LED on a GPIO Pin
To light an LED safely from a GPIO pin, a current-limiting resistor is always placed in series with the LED. Below is a simplified schematic of the circuit connected to GPIO13, matching the typical layout found on many ESP32 dev boards.
How Current Flows
When the GPIO outputs a high level (3.3V), current flows out from the pin, through the current-limiting resistor, through the LED, and back to the GND pin of the ESP32, forming a complete circuit loop.
Why the Resistor Is Necessary
The resistor is a current-limiting resistor that protects the LED by preventing excessive current from burning it out, and protects the ESP32 by preventing the GPIO pin from outputting excessive current. If a specific value isn't available, a resistor in the 220Ω–1kΩ range can be used instead.
5. Code Example: Blinking a Single LED
The Arduino-style workflow for any digital output pin always follows the same two-step pattern: first declare the pin as an output inside setup(), then toggle it HIGH or LOW inside loop().
You need to define the GPIO pin to operate in output mode in the setup() function using the pinMode() Arduino function, then you can drive the pin HIGH or LOW to change the digital state of that pin.
Sketch: LED on GPIO13
const int ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
Upload steps in the Arduino IDE, performed one action at a time:
Select the correct ESP32 board from Tools > Board Select the correct COM port from Tools > Port Save the sketch (File > Save) Click the Upload button Hold the BOOT button if prompted during flashing Release BOOT once uploading begins Wait for "Done uploading" in the console
This matches the standard flashing procedure: choose the board and COM port, hold down the BOOT button, click upload and keep the button pressed, then release it once the IDE starts sending the code and wait for flashing to complete.
6. Scaling Up: Controlling Multiple LEDs
Once a single LED works, the same principle extends naturally to several LEDs at once — each one simply needs its own GPIO pin, its own current-limiting resistor, and a shared connection back to GND.
A commonly used and readily accessible set of GPIOs for this kind of experiment on ESP32 dev boards is GPIO12, GPIO27, GPIO33 and GPIO15 — all confirmed usable as digital outputs. Similar four-LED demonstrations appear frequently in ESP32 tutorials, for example wiring four LEDs connected to pins D4, D0, D2, and D15 of ESP32.
7. Code Example: Blinking Multiple LEDs Together
To manage several pins cleanly, it is good practice to store the pin numbers in named variables (or an array) rather than repeating raw numbers throughout the code. This mirrors the standard multi-pin configuration pattern used in ESP32 tutorials, where several GPIOs are declared as outputs together:
void setup() { pinMode(0, OUTPUT); pinMode(4, OUTPUT); pinMode(2, OUTPUT); pinMode(15, OUTPUT); } is a typical way multiple pins are initialized before use.
Sketch: Four LEDs Blinking Together
const int led1 = 12;
const int led2 = 27;
const int led3 = 33;
const int led4 = 15;
void setup() {
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);
pinMode(led4, OUTPUT);
}
void loop() {
digitalWrite(led1, HIGH);
digitalWrite(led2, HIGH);
digitalWrite(led3, HIGH);
digitalWrite(led4, HIGH);
delay(500);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);
digitalWrite(led4, LOW);
delay(500);
}
Every pin follows the exact same two rules used earlier for the single LED: it must be declared as OUTPUT in setup(), and it is switched using digitalWrite() in loop(). digitalWrite() sets an output pin to either HIGH (3.3V) or LOW (0V), exactly as it does for a single LED — simply repeated once per pin.
Variation: Different Blink Speed
Changing only the delay() value changes the blink rate for all four LEDs simultaneously, since they share the same timing in this simple synchronous loop:
delay(500); // 500 ms ON delay(500); // 500 ms OFF
for loop once you're comfortable with the basics.
8. Using a Prototyping / Breadboard Setup
For repeated experiments, many learners build a small reusable prototyping board that hosts an ESP32 alongside common peripherals — switches, a buzzer, an LCD, a real-time clock module, and a motor driver — all pre-wired to accessible header pins. Mounting the ESP32 on a breadboard makes it easy to pull individual GPIO wires out to LEDs, resistors, and other components without redoing the core wiring each time.
9. Summary
- Program using the native GPIO number, not board silkscreen labels, to avoid confusion.
- ESP32 logic HIGH ≈ 3.3V; logic LOW ≈ 0V — and the chip is not 5V tolerant.
- Every LED needs a series current-limiting resistor (typically 220Ω–1kΩ) to protect both the LED and the GPIO pin.
- Resistor order (before/after the LED) does not matter — only that both are in the same series loop.
- The pattern
pinMode()insetup()+digitalWrite()inloop()scales directly from one LED to many. - Never exceed the 40 mA absolute maximum current per GPIO pin.
With these fundamentals in place, the same digital output technique can be extended in future lessons to drive buzzers, relays, motor drivers, and other components — all following the identical HIGH/LOW output logic covered here.