How do you debounce a mechanical switch in firmware? Describe at least two approaches.
Mechanical switches bounce for 1-20 ms after actuation, producing multiple rapid transitions as the metal contacts physically settle. Without debouncing, a single button press can register as dozens of events — a menu advances 15 steps, a counter increments 20 times, or a state machine enters an undefined state.
Timer-based debounce (recommended): On the first detected edge (via polling or interrupt), record the event and start a one-shot software timer for 20-50 ms. During this debounce window, ignore all subsequent edges on that pin. When the timer expires, read the pin's current state — this stable reading is the true state. If using interrupts, disable the EXTI for that pin during the debounce period to prevent ISR re-entry, and re-enable it when the timer fires. This approach is non-blocking, robust, and works well in both bare-metal and RTOS environments.
// Timer-based debounce in EXTI ISR + software timer callbackvoid EXTI0_IRQHandler(void) {EXTI->PR = EXTI_PR_PR0; // Clear pendingNVIC_DisableIRQ(EXTI0_IRQn); // Ignore bouncesstart_debounce_timer(20); // 20 ms one-shot}void debounce_timer_callback(void) {bool stable = (GPIOA->IDR & 1); // Read settled stateprocess_button(stable);NVIC_EnableIRQ(EXTI0_IRQn); // Re-arm interrupt}
Counter-based (integrating) debounce: In a periodic task running every 5-10 ms, read the pin state. Increment a counter when the reading matches the proposed new state; reset it when the reading differs. Accept the new state only after N consecutive identical readings (e.g., 3-4 samples = 15-40 ms of stability). This approach fits naturally into RTOS tick handlers or super-loop architectures and requires no additional timers. The trade-off is slightly higher latency and the requirement for a periodic polling context.
Source: GPIO Q&A
