← all lessons
Microcontrollers · #8 of 48

GPIO Inputs

Floating, Pull-ups, Debouncing

Why it matters

A real button press should be one event. Without debouncing you get phantom presses, double-clicks, and flaky menus — even with perfect code everywhere else.

The idea

Goal

Turn a noisy mechanical switch into a clean digital signal you can trust.

What’s actually happening

A switch is two pieces of metal touching. When they first touch, they bounce for a few milliseconds: HIGH/LOW/HIGH/LOW… then finally settle. Your CPU is fast enough to see all those transitions.

flowchart LR
    Raw[RawGPIO] --> Debounce[WaitForStability]
    Debounce --> Clean[CleanState]

Floating Inputs

An unconnected GPIO input is floating — it picks up noise and randomly flips between HIGH and LOW.

ESP‑WROOM‑32 wiring note

Use a button-to-GND wiring and enable an internal pull-up:

Avoid using a strapping pin (e.g. GPIO0/2/12/15) for a button unless you know why. Also note: GPIO34–39 are input-only and typically require an external pull-up/down.

Mini-lab

Try a debounce window of 20–50ms for common tactile switches. If you still see false triggers, increase it. If the button feels “laggy”, decrease it.

Demo

The top timeline is the raw GPIO signal (with bounce). The bottom is the debounced output.

Use:

Key takeaways

Going deeper

Hardware debounce (RC + Schmitt trigger) can reduce CPU work and improve EMI robustness. For event-driven code, interrupts still need debouncing — either by masking interrupts for a window, or by using a timer task to confirm stability.

Math details

Definitions:
  sample_rate = N samples / second
  debounce_window = W seconds

Rule of thumb:
  W should be several times the worst-case bounce duration.

Simple debounce state machine:
  if raw != pending:
      pending = raw
      stable_time = 0
  else:
      stable_time += dt
      if stable_time >= W:
          debounced = pending

Implementation

LLM Prompt: GPIO Debounce

Write Rust code for ESP32 GPIO debouncing using esp-hal.
Input: GPIO pin configured as input with pull-up.
Implement state machine: detect change → wait for stability → accept.
Include configurable debounce window (default 50ms). Handle both
polling and interrupt-driven modes.

Lab Exercise

  1. Connect button: GPIO → Button → GND
  2. Enable internal pull-up in code
  3. Implement debounce state machine
  4. Test with different debounce windows (20ms, 50ms, 100ms)
  5. Observe: too short = false triggers, too long = laggy feel

full glossary →