The puzzle
A counter on the screen should go up by one per press. Sometimes it jumps by three. The switch is fine: every mechanical contact bounces for a few milliseconds as the metal settles (unit 1, lesson 5), and a fast microcontroller sees every bounce as a separate press. How do you turn that burst into exactly one event, and what does it cost?
STEP 1
Sample, then decide
The robust approach is to stop reacting to edges and instead look at the input at a steady rate, for example every millisecond, then decide what the switch is doing from its recent history. Two families of decision rule exist:
- Defer. Report a change only after the input has stayed at the new level for the debounce time. Bounces reset the wait. Robust against bounce and against short noise spikes, but every event is delayed by at least the debounce time.
- Eager. Report the first change immediately, then ignore the input for the debounce time. No delay, but a single noise spike is reported as a press.
↑ This step uses the figure at the top of the page.
For eager debouncing the debounce time must outlast the whole bounce; for defer it must outlast the longest quiet gap inside the bounce, which is shorter, but a margin is wise either way; 5 ms is a common default (QMK’s keyboards and Linux’s gpio-keys driver both use it), while Arduino’s teaching example uses a generous 50 ms. Measure your switch: in Ganssle’s tests of 18 switches the bounce averaged about 1.6 ms (two outliers excluded), the worst of the rest reached 6.2 ms, and one outlier bounced for 157 ms on opening.
STEP 2
A defer debounce in C
The version with timestamps, called every millisecond or simply every pass of the main loop:
typedef struct { bool stable, last_raw; uint32_t changed_ms; } button_t;
bool button_update(button_t *b, bool raw, uint32_t now_ms) { /* true on a new stable state */
if (raw != b->last_raw) { b->last_raw = raw; b->changed_ms = now_ms; }
if (raw != b->stable && (uint32_t)(now_ms - b->changed_ms) >= DEBOUNCE_MS) {
b->stable = raw;
return true;
}
return false;
}
Timestamps make the debounce time independent of how often the function is called (the call interval must still be well below the debounce time, or the decision rests on only a sample or two), and the unsigned subtraction survives counter wrap as long as the millisecond counter wraps at 2³² (unit 6, lesson 6). Initialise stable and last_raw from a first reading of the pin, or an idle-high button reports a spurious change at start-up. Long press and double click are the same idea one level up: timestamps of stable-state changes.
STEP 3
Interrupts on every edge are a trap
Configuring an edge interrupt and counting in the handler looks efficient, but a bouncing switch can fire dozens of interrupts in a few milliseconds, each one taking CPU time and pushing other interrupts later, and the handler still has to debounce. Better patterns: use the interrupt only to wake the chip or to start the sampling, then disable it until the debounced state has settled; or simply sample from a periodic timer tick.
STEP 4
Debouncing in hardware
An RC low-pass filter in front of a Schmitt-trigger input can remove bounce before the firmware sees it. While a contact bounce is open for a moment, the capacitor charges only partway; if it never reaches the upper threshold, the input never changes. The Schmitt trigger matters: a slowly moving voltage on a plain input would itself cause several transitions.
A resistor and capacitor slow the input so that brief bounces cannot swing it across the threshold of a Schmitt-trigger input. While the button is held the capacitor is discharged; when a bounce opens the contact for a moment the capacitor starts charging through R. The input only changes if it reaches the upper threshold VT+ (1.8 V here, illustrative) before the contact closes again.
The filter also delays the genuine release edge by about the same time (the closing edge discharges the capacitor through the contact almost at once), and that discharge must not exceed the contact’s rating; a small series resistor helps and also slows the closing edge.
STEP 5
Worked example: latency of a defer debounce
A switch bounces for 3 ms; the loop samples every 1 ms with a 5 ms defer debounce. The last bounce ends 3 ms after the first contact, and the state is accepted 5 ms after that:
rounded to the sampling grid. For a user, 8 ms is imperceptible. For a switch that detects the end of a mechanical travel, it may matter, which is where eager debouncing, or an eager press with a deferred release (QMK’s asym_eager_defer_pk), is used.
MYTHS AND FACTS
Common misconceptions
A good switch doesn’t bounce
Every mechanical contact bounces; good ones bounce for less time.
Debouncing is just a delay after the edge
Reporting the first edge and then ignoring the input (the eager method) does not filter noise, and neither does waiting a fixed time and reading once; a defer debounce requires the level to be stable.
An interrupt per edge is the efficient way to read a button
It multiplies interrupts by the number of bounces and still needs debouncing.
A capacitor across the switch is enough
Without a Schmitt-trigger input the slow edge can cause multiple transitions, and discharging a bare capacitor through the contacts can damage them.
Check yourself
Answer in your head, then open the card.
A defer debounce of 10 ms samples every 2 ms. A press bounces for 4 ms. When is the press reported?
About 4 + 10 = 14 ms after first contact, rounded up to the next 2 ms sample.
An industrial input has occasional 1 ms noise spikes but no switch. Defer or eager?
Defer: an eager algorithm would report each spike as an event, while a defer debounce longer than the spikes ignores them.
With R = 10 kΩ and C = 100 nF, how long does the capacitor take to charge from 0 V to 1.8 V at 3.3 V?
τ = 1 ms; t = τ · ln(3.3 / (3.3 − 1.8)) = 1 ms × ln 2.2 ≈ 0.79 ms, taking the figure's illustrative 1.8 V threshold (use your chip's specified Schmitt threshold). Bounce openings shorter than that are filtered.
Why does the C function compare (uint32_t)(now_ms − changed_ms) rather than now_ms > changed_ms + DEBOUNCE_MS?
The subtraction of unsigned values gives the right elapsed time even when the millisecond counter wraps; the addition can overflow and make the comparison wrong around the wrap.
Sources (4)
- QMK Firmware documentation, docs/feature_debounce_type.md — defer: “When DEBOUNCE milliseconds of no changes has occurred … changes are pushed … noise-resistant”; eager: “any key change is reported immediately. All further inputs for DEBOUNCE ms are ignored … not noise-resistant”; timestamp-based is usually superior to cycle-based; default debounce time 5 ms
- Arduino, arduino-examples, examples/02.Digital/Debounce/Debounce.ino — a timestamp defer debounce: lastDebounceTime is reset on every change of the raw reading, and the state is accepted when millis() − lastDebounceTime > debounceDelay (50 ms)
- Linux kernel, Documentation/devicetree/bindings/input/gpio-keys.yaml — debounce-interval: “Debouncing interval time in milliseconds. If not specified defaults to 5.”
- Jack Ganssle, “A Guide to Debouncing”, Part 1 (August 2004, rev. 2 April 2007) — measurements on 18 switches: excluding two outliers the average bounce was 1557 µs with a maximum of 6200 µs; one switch bounced 157 ms on opening (as cited in unit 1, lesson 5)