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.

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
| 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
- Establish a defined idle level.
- Observe raw press and release transitions.
- Decide what counts as an event.
- Implement a non-blocking state machine.
- Test quick taps, long holds, repeated presses, and startup state.
- 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.
Comments
Post a Comment