← Back to Course
Digital Input: Buttons and Switches
🔘 Digital Input: Reading Buttons and Switches with ESP32
🔘 What You'll Learn:
- 🔌 Wire buttons to ESP32 using internal pull-up (no external resistor)
- 💻 Read button presses: HIGH (3.3V) = not pressed, LOW (0V) = pressed
- 🛡️ Implement debouncing to prevent false triggers
- 💧 Use buttons for manual irrigation override
Digital input lets your ESP32 read button presses, switch states, and other on/off signals. This is how you'll add manual control to your IoT projects - like overriding automated irrigation or starting a pump manually.
🔧 How Digital Input Works
- HIGH (1): Pin reads 3.3V (button not pressed, switch OFF)
- LOW (0): Pin reads 0V (button pressed, switch ON)
- Use
pinMode(pin, INPUT_PULLUP)to enable internal pull-up resistor - Use
digitalRead(pin)to read the current state
🔌 Button Wiring
Button Wiring (using internal pull-up):
ESP32 Button
════ ══════
GPIO0 ─────────────────── Button Pin 1
GND ─────────────────── Button Pin 2
⚠️ No external resistor needed - using INPUT_PULLUP!
Button NOT pressed → GPIO reads HIGH (1)
Button pressed → GPIO reads LOW (0)
Recommended GPIOs for buttons: GPIO0, GPIO4, GPIO5, GPIO12, GPIO13, GPIO14, GPIO15
💡 Safe GPIO Pins for Buttons:
- GPIO0: Safe, but affects boot if held LOW during power-on
- GPIO4, GPIO5, GPIO12, GPIO13, GPIO14, GPIO15, GPIO16, GPIO17, GPIO18, GPIO19, GPIO21, GPIO22, GPIO23: Completely safe, no boot issues
- Avoid: GPIO6-11 (used for PSRAM/internal flash)
📖 Basic Button Reading Code
#define BUTTON_PIN 0
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("🔘 Button Reader Ready");
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
Serial.println("🔘 Button PRESSED!");
// Add your action here (e.g., water pump override)
delay(300); // Simple debounce
}
delay(50);
}
🛡️ Button Debouncing (Important!)
Mechanical buttons "bounce" for 5-20ms after press, creating multiple false readings. Here's proper debouncing code:
#define BUTTON_PIN 0
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;
int pressCount = 0;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("Debounced Button Reader Ready");
}
void loop() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (reading == LOW) {
pressCount++;
Serial.printf("✅ Valid button press #%d\n", pressCount);
}
}
lastButtonState = reading;
}
🌱 Farm Application: Manual Irrigation Override
#define OVERRIDE_BUTTON 0
#define PUMP_RELAY 5
#define WATER_DURATION 10 // seconds
unsigned long lastPress = 0;
const unsigned long DEBOUNCE = 500;
void setup() {
pinMode(OVERRIDE_BUTTON, INPUT_PULLUP);
pinMode(PUMP_RELAY, OUTPUT);
digitalWrite(PUMP_RELAY, HIGH); // Pump OFF
Serial.begin(115200);
Serial.println("💧 Manual pump override ready");
}
void loop() {
// Check button press with debounce
if (digitalRead(OVERRIDE_BUTTON) == LOW) {
if (millis() - lastPress > DEBOUNCE) {
Serial.println("🔘 Manual override triggered!");
digitalWrite(PUMP_RELAY, LOW); // Pump ON
delay(WATER_DURATION * 1000); // Water for X seconds
digitalWrite(PUMP_RELAY, HIGH); // Pump OFF
Serial.println("✅ Watering complete");
lastPress = millis();
}
}
// Your automated irrigation code here...
}
📖 Farm Application:
A farmer installed a manual override button next to their automated irrigation controller. When they spotted a dry area, pressing the button added 10 seconds of watering without changing the automated schedule.
⚠️ Common Mistakes:
- ❌ Forgetting INPUT_PULLUP: Floating pin gives random readings!
- ❌ No debounce: One press registers as 10-100 presses
- ❌ Using delay() in debounce: Blocks other code. Use millis() instead.
- ❌ GPIO0 held LOW at boot: ESP32 enters flashing mode and won't run code
🎯 Key Takeaways:
- ✅ Use
INPUT_PULLUPfor internal resistor (no external parts) - ✅ Button pressed = LOW (0V), released = HIGH (3.3V)
- ✅ Always debounce (50-100ms delay) to prevent false triggers
- ✅ Use
millis()for non-blocking debounce, notdelay() - ✅ Manual override lets operators intervene when needed
💡 Key Takeaways:
- Apply these concepts directly to your farm or project.
- Take notes on important details for the quiz.
- Use the button below to track your progress.
×