Bench note
Debounce switches in firmware, not hope
The problem arrives at 10 kHz
A tactile switch closes. Your microcontroller sees fourteen transitions in three milliseconds. Your state machine increments fourteen times. You stare at the serial log wondering if the universe hates you specifically.
Mechanical contacts bounce. Always. The datasheet might promise 5 ms of chatter, but you'll see 20 ms on a cold morning or when someone presses the button like they're angry at it. You can't fix this in hardware without adding parts you'll regret debugging later. Fix it in firmware where you can log what actually happened.
State, timestamp, and a threshold
Store three things: the last stable reading, the timestamp when you first saw a new value, and a debounce period — typically 20-50 ms depending on how much you trust your switches.
Every time your polling loop or interrupt fires, compare the current reading to the last stable one. If they match, reset your timestamp and move on. If they differ, check whether enough time has passed since you first noticed the change. Only then do you commit the new value as stable and act on it.
This isn't elegant. It's correct. You're distinguishing between a real button press and electrical noise pretending to be user intent.
One timer, many switches
You don't need a hardware timer per switch. Poll all your inputs in a single periodic task — 1 kHz is plenty, 200 Hz works if you're not building a musical instrument. Each switch carries its own timestamp, but they all share the same clock reference.
If you're using interrupts because someone on a forum said they're "more responsive," reconsider. Interrupts fire on every bounce. You'll spend more cycles disabling and re-enabling them than you save. A tight polling loop with a proper state machine handles this without the ceremony.
Edge detection after debounce
Once you have a stable reading, you can detect edges cleanly. Compare the new stable value to the previous stable value. Rising edge: it was low, now it's high. Falling edge: it was high, now it's low. No bounces, no phantom events, no incrementing your counter into the integer overflow your future self will debug at midnight.
Log these edges. Timestamp them. If your project involves any kind of closed-loop control, you'll need this data when the behavior surprises you later.
Hysteresis for analog thresholds
If you're reading a switch through an ADC — maybe it's part of a resistor ladder, maybe you're doing something weird with capacitive sensing — add hysteresis to your threshold. Decide on two levels: one for transitioning to "pressed," one for transitioning to "released." The gap between them absorbs noise that would otherwise flutter your state.
This is the same principle as debouncing, but in the voltage domain. You're building a deadband where small variations don't matter. Pick values wide enough that your ADC reference noise won't trigger false transitions.
When to ignore the datasheet bounce spec
Switch datasheets list typical bounce times under controlled conditions: 25°C, specific actuation force, new contacts. Your enclosure is colder. Your user presses harder. The switch has been clicked six thousand times. Multiply the datasheet number by two, then add 10 ms, then test it on the actual board.
If you're feeling precise, write a test fixture that logs bounce duration across a hundred presses. You'll find a distribution, not a single number. Pick a debounce threshold that covers 99% of it, document why, and move on.
No debounce is also a choice
Sometimes you want raw switch data — building a latency measurement tool, characterizing contact behavior, proving to yourself that bounces exist. Fine. Log everything, timestamp it, don't filter. But don't pretend this is production firmware. It's a diagnostic mode.
For everything else, debounce in software. Your state machine will thank you. Your logs will make sense. And when someone asks why the button works correctly, you'll have an answer that isn't "I got lucky."