Give Your Button a Default State
The Story
Mission 03 showed the problem: an input pin with no clear connection can drift between HIGH and LOW. That was not a software bug. It was an unfinished circuit.
Mission 04 fixes that circuit. You will add a weak electrical default so the ESP32 always knows what the button means when it is not being pressed.
Explain Like I'm 12
Imagine a door that should normally stay closed. A pull-down resistor is like a gentle spring that keeps it closed until someone pushes it open. A pull-up resistor is the opposite: the spring keeps it open until someone pulls it closed.
The spring is weak on purpose. A real button press easily wins, but when nobody touches the button, the input still has a clear answer.
Academy Path
Current mission: Mission 04 Β· Foundation
Expected outcome: You will build stable button inputs using both an external pull-down resistor and the ESP32 internal pull-up resistor.
Mini challenge: Invert the button logic, explain why INPUT_PULLUP reads LOW when pressed, and predict what happens if the pull-up resistor is removed.
Required Knowledge
Skills You Unlock
- Explain why floating pins are unreliable
- Wire a button with an external pull-down resistor
- Use the ESP32 internal pull-up resistor with INPUT_PULLUP
- Predict why a pull-up button reads LOW when pressed
- Choose between INPUT and INPUT_PULLUP for beginner button circuits
Learning Objectives
- Explain how pull-up and pull-down resistors stop floating inputs.
- Wire a button using an external pull-down resistor.
- Wire a button using the ESP32 internal pull-up resistor.
- Compare INPUT and INPUT_PULLUP in real button circuits.
- Explain why pressed can mean LOW in pull-up wiring.
What You'll Build
- A stable external pull-down button circuit where released reads LOW and pressed reads HIGH.
- A stable internal pull-up button circuit where released reads HIGH and pressed reads LOW.
- A Serial Monitor test that proves both circuits are reliable.
- A mental model for choosing INPUT or INPUT_PULLUP in future ESP32 projects.
Things You'll Need
- ESP32 DevKit boardThe GPIO input platform
- Push buttonMomentary tactile switch
- 10 kOhm resistorExternal pull-down resistor for the first circuit
- BreadboardKeeps the button and resistor stable
- Jumper wiresFor GPIO, 3.3 V, and GND connections
- USB data cableUpload code and read Serial Monitor
Component Spotlight
This mission focuses on ESP32 GPIO input behavior and the resistor that defines a safe default state.
The Default State Problem
A button is not always connected. When it is open, it leaves one side of the circuit disconnected unless you add a default path.
A pull resistor gives the input a weak default. Pull-up means the default is HIGH. Pull-down means the default is LOW. Pressing the button creates a stronger path to the opposite state.
The resistor is important because it limits current. It is strong enough to guide the input, but weak enough that a button press can safely override it.
Engineering Explanation
An ESP32 digital input is high impedance. That means it listens without taking much current. This is useful, but it also means the pin cannot decide its own state.
A pull-up or pull-down resistor creates a predictable voltage on the pin when the button is open. With a pull-down, the resistor connects the input to GND, so released reads LOW. Pressing the button connects the input to 3.3 V, so pressed reads HIGH.
With a pull-up, the resistor connects the input to 3.3 V, so released reads HIGH. Pressing the button connects the input to GND, so pressed reads LOW. This inverted logic surprises beginners, but it is completely normal.
Real-Life Analogy
Think of a pull resistor like a weak magnet holding a pointer to a default answer. If nobody touches the pointer, the magnet keeps it in one place. When you press the button, your finger is stronger than the magnet and moves the pointer to the other answer.
The weak magnet matters. Without it, the pointer is loose and can wobble from tiny vibrations. That wobble is the floating-pin randomness you saw in Mission 03.
Floating Pin Recap
In Mission 03, GPIO27 changed because the loose wire had no path to 3.3 V or GND. It picked up tiny noise from your hand, USB cable, nearby wiring, and the room.
A floating pin is not broken. It is simply unfinished. The fix is to give the input a default state before waiting for the button or sensor to change it.
Internal Pull-Up Resistor
The ESP32 includes small internal pull resistors that can be enabled in software. INPUT_PULLUP turns on an internal resistor from the GPIO pin to 3.3 V.
That is why a button can be wired with one side to the GPIO pin and the other side to GND. No external resistor is needed. Released reads HIGH. Pressed reads LOW.
This is why many ESP32 examples use INPUT_PULLUP: it is stable, simple, and uses fewer parts.
External Pull-Up and Pull-Down Resistors
An external resistor is a physical resistor you place on the breadboard. External pull-down wiring is excellent for learning because the logic feels natural: released is LOW, pressed is HIGH.
External resistors are also useful when you need a specific resistor value, when a sensor output requires a pull resistor, or when you want the circuit behavior to be obvious even before reading the code.
INPUT vs INPUT_PULLUP
| Mode | Default State | Typical Wiring | When to Use |
|---|---|---|---|
| INPUT with external pull-down | LOW when released | GPIO to button, button to 3.3 V, 10 kOhm from GPIO to GND | Best for learning natural button logic or when an external circuit already defines the pin. |
| INPUT_PULLUP | HIGH when released | GPIO to button, button to GND, internal resistor enabled in code | Best for simple ESP32 buttons with fewer parts. |
| INPUT with no pull resistor | None | GPIO connected to an open button or loose wire | Avoid for buttons. It can float and read randomly. |
Breadboard Wiring
Follow these steps in order. Unplug USB before you change any wires.
-
1
External pull-down test: place the push button across the breadboard center gap.
-
2
Connect one side of the button to ESP32 3.3 V.
-
3
Connect the other side of the button to ESP32 GPIO27.
-
4
Place a 10 kOhm resistor from the GPIO27 side of the button to GND.
-
5
Upload the INPUT example and open Serial Monitor at 115200 baud.
-
6
Internal pull-up test: remove the external resistor and 3.3 V button wire.
-
7
Connect one side of the button to GPIO27 and the other side to GND.
-
8
Upload the INPUT_PULLUP example and compare the released and pressed readings.
GPIO Table
| Signal | ESP32 Pin | Mode | Notes |
|---|---|---|---|
| Button input | GPIO27 | INPUT or INPUT_PULLUP | Reads the button state in both examples. |
| External pull-down default | GND through 10 kOhm | Pull-down | Keeps GPIO27 LOW until the button connects it to 3.3 V. |
| Button HIGH source | 3.3 V | Power | Used only in the external pull-down version. |
| Internal pull-up default | GPIO27 internal resistor | INPUT_PULLUP | Keeps GPIO27 HIGH until the button connects it to GND. |
| Serial debug | USB | Serial | Shows released and pressed states at 115200 baud. |
Code
Copy this into Arduino IDE, then click Upload.
// Mission 04 - Example A: external pull-down resistor
// Wiring: GPIO27 -> button -> 3.3V, and GPIO27 -> 10k resistor -> GND
const int BUTTON_PIN = 27;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT);
Serial.println("Example A: INPUT with external pull-down");
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading == HIGH) {
Serial.println("Button is PRESSED");
} else {
Serial.println("Button is RELEASED");
}
delay(250);
}
/*
Mission 04 - Example B: internal pull-up resistor
Rewire first: GPIO27 -> button -> GND. No external resistor needed.
Then replace setup() and loop() with the code below.
const int BUTTON_PIN = 27;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("Example B: INPUT_PULLUP internal pull-up");
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading == LOW) {
Serial.println("Button is PRESSED");
} else {
Serial.println("Button is RELEASED");
}
delay(250);
}
*/
This file includes two examples. Upload Example A for the external pull-down circuit. Then rewire the button and upload Example B for INPUT_PULLUP. Do not use both setups on the breadboard at the same time.
Line-by-Line Code Explanation
- const int BUTTON_PIN = 27; gives the button input a clear name.
- pinMode(BUTTON_PIN, INPUT); is used only when the external pull-down resistor already gives the pin a default LOW state.
- In Example A, digitalRead() returns HIGH when the button connects GPIO27 to 3.3 V.
- pinMode(BUTTON_PIN, INPUT_PULLUP); turns on the ESP32 internal pull-up resistor.
- In Example B, the released button reads HIGH because the internal pull-up holds the pin near 3.3 V.
- In Example B, the pressed button reads LOW because the button connects GPIO27 directly to GND.
- The delay(250); line slows the output so the Serial Monitor is readable. It is not button debouncing yet.
Expected Serial Monitor Output
Example A, external pull-down:
Button is RELEASED Button is RELEASED Button is PRESSED
Example B, INPUT_PULLUP:
Button is RELEASED Button is RELEASED Button is PRESSED
The printed words are the same because the code handles the different logic. The raw GPIO value is different: Example A uses HIGH for pressed, while Example B uses LOW for pressed.
Experiment: Leave the Input Floating
Try removing the 10 kOhm pull-down resistor from Example A while leaving the button open. The input may become random again, just like Mission 03. Put the resistor back and the reading should become stable.
Then try the INPUT_PULLUP circuit. Notice that there is no external resistor, but the reading stays stable because the ESP32 enabled one internally.
This is the main lesson: stable button inputs need a default state. That default can come from an external resistor or from an internal pull resistor enabled in software.
Mini Quiz
Quick check β no grades, just confidence!
Q1. With INPUT_PULLUP, what does the pin read when the button is released?
Q2. With INPUT_PULLUP, what does the pin read when the button is pressed to GND?
Q3. What problem does a pull resistor solve?
Challenge Yourself
No wrong answers β experiment and have fun!
- Modify the code so the variable buttonPressed is true when the button is pressed, even in the INPUT_PULLUP example.
- Write one sentence explaining why INPUT_PULLUP reads LOW when the button is pressed.
- Predict what happens if the pull-up or pull-down resistor is removed while the button is released.
- Compare which wiring style feels easier to understand and which style uses fewer parts.
Common Problems
Most ESP32 problems are wiring, power, library, or timing issues. Check these first.
The button always reads pressed
Likely cause: The GPIO may be permanently connected to 3.3 V or GND because the button legs are in the wrong breadboard rows.
Fix: Rotate the button 90 degrees or move it across the breadboard center gap, then test again.
The reading is still random
Likely cause: The pull-down resistor is missing, in the wrong row, or not connected to GND.
Fix: Trace GPIO27 to the resistor and make sure the other resistor leg reaches a real GND pin.
INPUT_PULLUP prints pressed when I release the button
Likely cause: The code may be treating HIGH as pressed even though pull-up logic is inverted.
Fix: For INPUT_PULLUP, use digitalRead(pin) == LOW to detect a pressed button.
The ESP32 resets when I press the button
Likely cause: The button may be shorting 3.3 V directly to GND.
Fix: Unplug USB and rebuild the circuit slowly. The resistor should define the GPIO, not sit as a direct short bypass.
Serial Monitor shows old text from Example A
Likely cause: Example B is inside a comment until you copy it into the active sketch area.
Fix: Replace the active setup() and loop() with Example B, then upload again.
The button works but prints many lines per press
Likely cause: The loop prints every 250 ms while the button is held.
Fix: That is expected in this mission. Mission 05 will teach detecting one clean press and debouncing.
FAQs
What is a pull-up resistor?
A pull-up resistor connects an input pin weakly to 3.3 V so the pin reads HIGH when nothing else is pressing it LOW.
What is a pull-down resistor?
A pull-down resistor connects an input pin weakly to GND so the pin reads LOW when nothing else is pressing it HIGH.
Why do buttons need pull-up or pull-down resistors?
A button is open most of the time. Without a default path to 3.3 V or GND, the input can float and produce random readings.
Why does INPUT_PULLUP read LOW when the button is pressed?
With INPUT_PULLUP the pin normally rests HIGH through the internal resistor. Pressing the button connects the pin to GND, so the pressed state becomes LOW.
Is LOW a problem in INPUT_PULLUP button logic?
No. LOW simply means pressed in that wiring style. Your code can name it clearly, for example bool buttonPressed = digitalRead(pin) == LOW.
When should I use INPUT?
Use INPUT when an external circuit already drives the pin HIGH or LOW, such as a sensor output, or when you add your own external pull-up or pull-down resistor.
When should I use INPUT_PULLUP?
Use INPUT_PULLUP for simple buttons or switches where one side of the button connects to GND and you want the ESP32 to provide the default HIGH state.
Does ESP32 have INPUT_PULLDOWN?
Many ESP32 GPIOs support internal pull-down too, but beginner button examples usually use INPUT_PULLUP because it is simple, common, and works well with a button to GND.
What resistor value should I use for an external pull-up or pull-down?
10 kOhm is a common beginner-friendly value. It is strong enough to define the input but weak enough that only a tiny current flows when the button is pressed.
Can I connect a button directly without a resistor?
Only if you use an internal pull-up or pull-down mode. A plain INPUT button without a resistor will usually float when the button is open.
Can a pull resistor damage the ESP32?
A normal pull resistor such as 10 kOhm connected between a GPIO input and 3.3 V or GND is safe. The dangerous mistake is shorting 3.3 V directly to GND.
Can I use 5 V with an ESP32 button input?
No. ESP32 GPIO pins are 3.3 V logic and are not 5 V tolerant. Use 3.3 V for button circuits connected to GPIO.
Why do many ESP32 tutorials use INPUT_PULLUP?
It reduces parts, avoids an external resistor, and gives a stable default state with only a button wired between the GPIO and GND.
Which is better, pull-up or pull-down?
Neither is universally better. Pull-up is very common for buttons because internal pull-ups are available and wiring to GND is convenient. Pull-down is easier for beginners to read because pressed often equals HIGH.
What happens if I remove the pull-up resistor?
The input loses its default HIGH state. When the button is open, the pin can float and random HIGH/LOW readings can return.
Why not use a very small resistor like 100 ohms?
A small resistor wastes more current when the button is pressed. Pull resistors should be weak defaults, not strong loads.
Do pull-up resistors debounce a button?
No. Pull resistors stop floating. Debouncing handles the tiny rapid on/off transitions caused by the mechanical contacts inside a real button.
Can one resistor serve multiple buttons?
Usually each independent button input should have its own pull-up or pull-down path so every GPIO has a clear default state.
Does a PIR sensor need INPUT_PULLUP?
Usually no. Most PIR modules actively drive their output HIGH or LOW, so the ESP32 can read them as INPUT. Check the module documentation if the output is open-drain.
What should I learn after pull-up and pull-down resistors?
Learn button debouncing. Once the input has a stable default state, debouncing helps your code ignore the brief mechanical chatter of a real button press.
Mission Complete!
Your button now has a reliable default state.
You now understand the real fix for floating pins. A pull-down resistor makes an input rest LOW until a button drives it HIGH. A pull-up resistor makes an input rest HIGH until a button drives it LOW.
You also learned why INPUT_PULLUP is everywhere in ESP32 examples: it gives you a stable button input with fewer parts. The only mental switch is remembering that pressed usually means LOW in pull-up wiring.
The next problem is different. Even with perfect pull-up wiring, a mechanical button can chatter for a few milliseconds. That is why Mission 05 is debouncing buttons.
- Choose INPUT with an external pull-down resistor
- Use INPUT_PULLUP for a simple button to GND
- Explain inverted pull-up button logic
- Predict floating behavior when the pull resistor is removed
- Prepare for button debouncing
