OC
OceanRemote
Low-code IoT platform
← Back to Course

Digital Input: Buttons and Switches

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_PULLUP for 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, not delay()
  • ✅ 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.