MyRoboPath
electronics15 min readUpdated 2026-03-14Intermediate

Switch Debouncing Explained: RC Hardware Filters & Software Algorithms

Solve phantom multiple button clicks: understand mechanical contact bounce physics, build analog RC low-pass debounce hardware filters, and write non-blocking millis() software debouncers.

MyRoboPath Engineering Lab
Peer-Reviewed Open-Source Hardware & Firmware Guide

Key Engineering Takeaways

  • When mechanical switch contacts collide, elastic metallic spring leaves bounce rapidly for 2ms–10ms before settling.
  • To a high-speed microcontroller running at 16MHz–240MHz, a single finger press appears as 10 to 50 distinct rapid button presses.
  • Hardware debouncing uses an RC low-pass filter (10kΩ + 100nF) to smooth voltage spikes, paired with a Schmitt Trigger for sharp logic edges.
  • Software debouncing ignores state changes occurring within a 20ms–50ms refractory time window.
  • Never put delay() inside an Interrupt Service Routine (ISR) for debouncing; compare timestamps using millis() or micros().
Prerequisites
  • Pull-Up Resistors and Basic RC Time Constants
Required Hardware / Tools
  • Pushbuttons
  • 10kΩ Resistors
  • 100nF Ceramic Capacitors
  • 74HC14 Schmitt Trigger IC
  • Microcontroller / Oscilloscope

The Physics of Mechanical Contact Bounce

When you press a physical pushbutton, toggle switch, or limit switch, the internal metal contacts do not make instantaneous, clean electrical contact. Microscopically, the metal contact leaves act like stiff diving boards: upon collision, they **physically bounce against each other** repeatedly for **2ms - 10ms** before settling into solid contact. Because microcontrollers execute instructions in nanoseconds, a single physical finger press will trigger a digital counter to increment by 15 or 30 counts instead of 1!
Switch contact bounce oscilloscope waveform
Figure 5.1: Oscilloscope capture showing 8ms of chaotic contact bounce pulses upon button closure.Visual Guide

Hardware Debouncing: RC Low-Pass Filter & Schmitt Trigger

An **RC Low-Pass Filter** debouncer uses a capacitor to absorb high-frequency bounce spikes: - When the button is pressed, capacitor C_1 (100nF) discharges slowly through resistor R_2 (1 kΩ), absorbing rapid millisecond bounces. - The smoothed analog ramp is fed into a **Schmitt Trigger Inverter (74HC14)**, whose built-in hysteresis voltage threshold snaps the analog ramp into a single, clean digital square edge.
Hardware RC switch debouncer schematic
Figure 5.2: Hardware debouncing circuit combining RC filter with 74HC14 Schmitt Trigger inverter.Visual Guide

Software Debouncing: Non-Blocking Millis() Timer Algorithm

In 90% of robotics firmware, debouncing is implemented in software without adding extra hardware components:
non_blocking_debounce.cpp
cpp
const int BUTTON_PIN = 4;
int buttonState;             // Current stable reading
int lastButtonState = HIGH;  // Previous raw reading
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50 milliseconds debounce window

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  Serial.begin(115200);
}

void loop() {
  int reading = digitalRead(BUTTON_PIN);

  // If the switch changed due to noise or press, reset debounce timer
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  // If reading has persisted longer than debounce delay, accept it as real
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == LOW) {
        Serial.println("Legitimate Click Registered!");
      }
    }
  }

  lastButtonState = reading;
}

Frequently Asked Questions

Why should I never use delay(50) for debouncing?

delay(50) completely halts CPU execution for 50 milliseconds. During this time, your robot cannot calculate motor PID loops, decode sensor streams, or respond to emergency stop commands. Always use non-blocking millis() timer comparisons.

Tags:#Debouncing#Switch Bounce#RC Filter#Firmware#Oscilloscope#Embedded C++