Why One Button Press Becomes Many: Debouncing, Polling, and Interrupts

Series navigation: Episode 1: The Roadmap · Episode 12

The problem: One physical press increments a counter several times.
The goal: Turn a noisy mechanical transition into one intentional software event.
The result: A beginner-friendly choice between polling, software debounce, hardware filtering, and interrupts.

A pushbutton transition being inspected on an oscilloscope

AI-generated illustration showing the idea of contact bounce; the waveform is explanatory, not a claimed measurement.

I pressed once, but the program counted four times

The button wiring was correct. The input had a pull-up. Yet one press produced several events.

Mechanical contacts do not always change cleanly from open to closed. They can touch, separate, and touch again for a short interval. A fast microcontroller sees those transitions individually. This is contact bounce.

It is neither random software behavior nor the same problem as a floating pin.

Three different layers

Raw button edges, a debounce window, and one accepted event

Layer Question
Electrical state Does the input have a pull-up or pull-down?
Physical transition Does the switch bounce or pick up noise?
Software event What qualifies as one press, release, hold, or repeat?

Define the user action first. A machine stop input, keyboard key, rotary encoder, and menu button do not necessarily need the same strategy.

A non-blocking software debounce

Avoid solving debounce with a long delay(). It freezes unrelated work and hides the state transition. Instead, accept a new state only after it remains stable for a chosen interval.

const int buttonPin = 2;
const unsigned long debounceMs = 20;

bool stableState = HIGH;
bool lastRaw = HIGH;
unsigned long changedAt = 0;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  bool raw = digitalRead(buttonPin);

  if (raw != lastRaw) {
    lastRaw = raw;
    changedAt = millis();
  }

  if (raw != stableState && millis() - changedAt >= debounceMs) {
    stableState = raw;
    if (stableState == LOW) {
      // Exactly one accepted press event.
    }
  }
}

The interval above is an example, not a universal switch specification. Observe the actual switch and choose a response time appropriate to the product.

Polling or interrupt?

Approach Good fit Common mistake
Polling Human buttons and simple state machines Blocking the loop with long delays
Interrupt plus deferred handling Wake-up or time-critical edge capture Performing debounce, printing, or long work inside the ISR
Timer sampling Many keys or consistent periodic processing Assuming the sampling interval alone defines an event
Hardware filter Harsh noise, special timing, or safety design Adding an RC without checking thresholds and edge-rate requirements

An interrupt does not remove bounce. It may simply call the interrupt service routine several times faster. Keep the ISR short: record a flag or timestamp, then make the state decision in normal code.

When an RC filter helps

A resistor and capacitor can slow a transition, and a Schmitt-trigger input can turn the slow voltage into a clean logic edge. But values affect latency, current, thresholds, and rise time. Do not attach a large capacitor directly across contacts or assume every MCU input safely accepts a slow edge.

For safety-related controls, use the architecture and diagnostics required by the relevant standard rather than treating a tutorial debounce as a safety function.

Debugging order

  1. Establish a defined idle level.
  2. Observe raw press and release transitions.
  3. Decide what counts as an event.
  4. Implement a non-blocking state machine.
  5. Test quick taps, long holds, repeated presses, and startup state.
  6. Add interrupt or hardware filtering only when the requirement justifies it.

Completion checklist

  • [ ] Floating input and contact bounce are treated as different problems.
  • [ ] Press and release polarity is explicit.
  • [ ] Debounce does not block unrelated work.
  • [ ] The interval is verified for the actual switch and product response.
  • [ ] Interrupt handlers remain short.
  • [ ] A long hold cannot accidentally become repeated presses unless intended.

Next experiment

With GPIO, voltage compatibility, and physical inputs understood, I am ready to document a complete breadboard circuit. Next week, I will move from jumper wires into KiCad.


References

Comments

Popular posts from this blog

Phrase Hero 1.1.0: Review Only the Phrasal Verbs You Missed

Simple Side Note: Never Lose a Note Again

Phrase Hero 1.2.0: Five Worlds and 100 English Expressions