Introduction to Digital Inputs on ESP32
Digital inputs are the bedrock of interactive ESP32 projects. Every button press, door sensor, limit switch, and motion detector relies on reading a binary HIGH or LOW voltage from a GPIO pin. ESP32 has up to 34 GPIO pins with hardware pull-up and pull-down resistors built in, making it one of the most flexible microcontrollers for digital input applications.
This guide covers everything from basic digitalRead() usage through hardware interrupts, input-only pins, voltage protection, and real-world project examples. After reading this, you will be able to wire and code any digital input scenario confidently.
How Digital Logic Levels Work on ESP32
ESP32 is a 3.3V device. Its GPIO pins interpret voltages in three regions:
| Voltage | Logic State | Result |
|---|---|---|
| 0 V – 0.66 V | LOW | digitalRead() returns 0 |
| 0.66 V – 2.64 V | Undefined | Unpredictable — avoid |
| 2.64 V – 3.3 V | HIGH | digitalRead() returns 1 |
You must drive the input firmly into the HIGH or LOW region. Leaving a pin floating (no connection, no pull resistor) places it in the undefined zone where noise dominates.
ESP32 GPIO Pin Capabilities at a Glance
| GPIO Range | Input? | Output? | Pull Resistors? | Notes |
|---|---|---|---|---|
| GPIO0–GPIO5 | Yes | Yes | Yes | GPIO0, GPIO2 affect boot; use carefully |
| GPIO6–GPIO11 | Avoid | Avoid | Yes | Tied to internal SPI flash — do not use |
| GPIO12–GPIO33 | Yes | Yes | Yes | General purpose, safest for projects |
| GPIO34–GPIO39 | Yes | No | No | Input-only; needs external pull resistors |
Recommended safe input pins: GPIO4, GPIO13, GPIO14, GPIO16, GPIO17, GPIO18, GPIO19, GPIO21, GPIO22, GPIO23, GPIO25, GPIO26, GPIO27, GPIO32, GPIO33, GPIO34, GPIO35.
Configuring Pins: INPUT, INPUT_PULLUP, INPUT_PULLDOWN
The pinMode() function in setup() sets the electrical mode of a GPIO:
void setup() {
Serial.begin(115200);
pinMode(4, INPUT); // Floating — needs external resistor
pinMode(16, INPUT_PULLUP); // Internal ~47kΩ pull-up → reads HIGH by default
pinMode(17, INPUT_PULLDOWN); // Internal ~47kΩ pull-down → reads LOW by default
}
void loop() {
Serial.printf("GPIO4=%d GPIO16=%d GPIO17=%dn",
digitalRead(4), digitalRead(16), digitalRead(17));
delay(300);
}
The most common choice for buttons is INPUT_PULLUP with the other end of the button wired to GND. When the button is pressed the pin is pulled LOW — this is called active-LOW logic.
The Floating Pin Problem (and How to Solve It)
A floating pin is a GPIO connected to nothing — no voltage source, no pull resistor. Electromagnetic fields from nearby wires, the MCU itself, and even your hand induce tiny currents that cause the pin to oscillate between HIGH and LOW randomly. The fix is simple:
- Software pull-up: use
INPUT_PULLUPinpinMode() - Software pull-down: use
INPUT_PULLDOWNinpinMode() - External pull-up: connect a 10 kΩ resistor between the pin and 3.3 V
- External pull-down: connect a 10 kΩ resistor between the pin and GND
External resistors are slightly preferred in noisy environments or when the pin travels off-board on a long wire, because the internal ~47 kΩ resistors are weaker and more susceptible to interference.
Reading a Button: Step-by-Step Project
Wire a momentary push button between GPIO16 and GND. No external resistor needed — we’ll use INPUT_PULLUP.
| Button Terminal | Connect To |
|---|---|
| Terminal A | ESP32 GPIO16 |
| Terminal B | ESP32 GND |
#define BUTTON_PIN 16
#define LED_PIN 2 // Built-in LED on most ESP32 dev boards
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP); // HIGH normally, LOW when pressed
pinMode(LED_PIN, OUTPUT);
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) { // Active-LOW: button pressed
digitalWrite(LED_PIN, HIGH);
Serial.println("PRESSED");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("released");
}
delay(50);
}
Reading Multiple Inputs Simultaneously
const int BUTTONS[] = {16, 17, 18, 19};
const int NUM_BTN = 4;
void setup() {
Serial.begin(115200);
for (int i = 0; i < NUM_BTN; i++)
pinMode(BUTTONS[i], INPUT_PULLUP);
}
void loop() {
for (int i = 0; i < NUM_BTN; i++) {
Serial.printf("B%d=%s ", i + 1, digitalRead(BUTTONS[i]) == LOW ? "ON " : "off");
}
Serial.println();
delay(200);
}
Hardware Interrupts for Fast Response
Polling digitalRead() in loop() may miss very brief signals. Hardware interrupts trigger immediately when the pin changes state, regardless of what the main loop is doing.
#define BTN_PIN 16
#define LED_PIN 2
volatile bool btnFlag = false; // shared between ISR and main
void IRAM_ATTR onButtonPress() { // IRAM_ATTR = runs from RAM, not flash
btnFlag = true; // keep ISR short — no delay/Serial here
}
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(BTN_PIN), onButtonPress, FALLING);
}
void loop() {
if (btnFlag) {
btnFlag = false;
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // toggle LED
Serial.println("Interrupt fired — button pressed!");
}
}
Key rules for ISR functions: always use IRAM_ATTR, keep the function to a few lines, use volatile on any variable the ISR writes, and never call Serial, delay(), or millis() inside the ISR.
Input-Only Pins: GPIO34–GPIO39
These four pins are special — they can only ever be inputs, and they have no internal pull resistors. They are ideal for clean analog signals (they double as high-quality ADC channels) and for digital inputs that arrive from clean, well-driven sources like sensors that actively output 3.3 V or 0 V.
If you must use them with a button or switch, add a 10 kΩ external pull-up (to 3.3 V) or pull-down (to GND) on the breadboard.
Protecting ESP32 from 5 V Signals
Many sensors, modules, and older Arduino peripherals run on 5 V logic. Connecting them directly to ESP32 GPIO pins will exceed the 3.6 V maximum and can permanently damage the chip. Two safe solutions:
| Method | Parts Needed | Best For |
|---|---|---|
| Voltage Divider | 10 kΩ + 20 kΩ resistors | Slow signals, sensors |
| Logic Level Converter | TXS0108E / BSS138 | Fast signals, I2C, SPI |
5V_signal ──── 10kΩ ──── GPIO_pin (3.3V max)
|
20kΩ
|
GND
Vout = 5 × (20k / (10k + 20k)) = 3.33 V ✓
PIR Motion Sensor Project
#define PIR_PIN 32
#define LED_PIN 2
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT); // PIR actively drives HIGH or LOW
pinMode(LED_PIN, OUTPUT);
delay(2000); // PIR initialisation warm-up
Serial.println("PIR ready — monitoring motion...");
}
void loop() {
if (digitalRead(PIR_PIN) == HIGH) {
Serial.println("Motion detected!");
digitalWrite(LED_PIN, HIGH);
delay(1000);
} else {
digitalWrite(LED_PIN, LOW);
}
delay(100);
}
Summary
Digital inputs on ESP32 revolve around three key concepts: logic levels (3.3 V system, avoid voltages above 3.6 V), pull resistors (always define a resting state — never float), and timing (polling for slow events, interrupts for fast ones). Master these and any button, sensor, or switch becomes straightforward.