Bounce-free switches and push buttons you may already have!

Any switch with a "break before make" action doesn't need timer based debounce. Just simple logic will yeild bounce-free results every time. This is true for SPDT toggle switches, SPDT slide switches, SPDT push buttons and Form-C relay contacts. In other words, bypassing the contact bounce is inherent by design as long as we check the state of both contacts.

With center pin (common) connected to GND and contacts A and B connected to input pins with pullup resistors enabled, we get this signal pattern on the pins as they change state:

Bounce free code:

  // check input status
  bool A = digitalRead(pinA);
  bool B = digitalRead(pinB);

  if (!A && B) {
    // A is low, B is high
  }

  if (!B && A) {
    // B is low, A is high
  }

Indeed that finesses the problem, however that approach requires 2 pins for each switch, and push
buttons and tactile switches are almost always SPST. One simple way to debounce lots of buttons/switches at once goes like this:

unsigned long last_switch_scan = 0 ;

void loop ()
{
  if (millis() - next_switch_scan >= 100)
  {
    next_switch_scan += 100 ;  // for next time
    scan_switches() ;
  }
  ..
  ..
}

where scan_switches just reads each switch into a variable. Because it only happens every 1/10th of a second any bouncing is sampled once at most. The downside is that its pot-luck if any brief presses register.

The rest of the code just reads the variables, only scan_switches() performs digitalReads.

Indeed that finesses the problem, however that approach requires 2 pins for each switch, and push
buttons and tactile switches are almost always SPST.

True, but still ...

I hadn't used the serial plotter in quite a while, so I thought I'd give it some bouncy SPDT data and check the results:

const byte dataA[128] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1,
                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                         0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
                        };

const byte dataB[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                         0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1
                        };
byte A = 1;
byte B = 0;
byte stateA = 1;

void setup() {
  Serial.begin (115200);
  while (!Serial);
  Serial.println ("A, B, stateA");
}

void loop() {

  for (int i = 0; i < 4; i++) {
    for (int i = 0; i < 128; i++) {

      // check input status
      A = dataA[i];
      B = dataB[i];

      if (!A && B) {
        stateA = 1;
      }

      if (!B && A) {
        stateA = 0;
      }

      // shift, vertical x2, print
      Serial.print((A + 6) * 2);
      Serial.print("\t");
      Serial.print((B + 4) * 2);
      Serial.print("\t");
      Serial.println((stateA + 2) * 2);
    }
  }
  while (1);
}

I just see little point in worrying about hardware approaches to bounce.

While the old "R-S" flip-flop approach described here will almost always manage the problem perfectly, it is as mentioned, rarely practical.

There are some situations which would be intractable where the bounce rate approaches the actual "signal" being detected. But in most cases, the "ironclad" algorithm of requiring the microprocessor input to be stable for the whole of a specified interval of - generally - a few milliseconds is more reliable than hardware approaches - simply because it is impractical to implement all the hardware that would be needed to perform that same algorithm.

It does of course require continuous polling but most situations requiring de-bounce such as accurate counting - as opposed to simple "interruption" of a process which does not require de-bouncing because it was known that the button/ switch would never be actuated unless that interruption was required - will necessarily be coded to focus on polling the button without significant delays between polls.

In short, there will be relatively few cases where processor power will be so critically devoted to other tasks that it is not practical to implement the de-bounce software - because that would tend also to preclude servicing the button/ switch at all. :roll_eyes:

Sigh ... you're missing the point (Paul__B). For SPDT switches, you're probably already using 2-pins and if not, then probably have some spare pins available that are doing nothing.

This isn't a hardware based approach, just straight up software logic. Not a timer based approach either. Time and polling speed is of no issue ... the loop can be spinning at 500kHz or one iteration every 10 seconds. The logic result will always manage the problem perfectly. Impossible for failure, impossible to loose a stable period during switching ... its inherent to the design (break-before-make). Its also the quickest solution possible ... the very first contact hit to GND, if caught, establishes a clean change of state. No need to catch the first hit either, the signal will be 0V at some point if the contacts have already transferred. Also, quite simple to implement.

But, if you don't have a spare pin, its back to the normal software debounce approach, which I admit works quite well if implemented correctly.

MarkT:
Indeed that finesses the problem, however that approach requires 2 pins for each switch, and push
buttons and tactile switches are almost always SPST. One simple way to debounce lots of buttons/switches at once goes like this:

unsigned long last_switch_scan = 0 ;

void loop ()
{
  if (millis() - next_switch_scan >= 100)
  {
    next_switch_scan += 100 ;  // for next time
    scan_switches() ;
  }
  ..
  ..
}



where scan_switches just reads each switch into a variable. Because it only happens every 1/10th of a second any bouncing is sampled once at most. The downside is that its pot-luck if any brief presses register.

The rest of the code just reads the variables, only scan_switches() performs digitalReads.

+1

Ha, another "pot luck" fan. Not bad, but when it comes to Belgian Waffles, I prefer maple syrup...

  if (!A && B) {
    // A = 0
  }

  if (!B && A) {
    // A = 1
  }

mmmm Canadian Maple Syrup !