Debouncing many switches used for MCU input via a shift register

I spent a fair amount of time a few weeks back looking at hardware debouncing for multiple quadrature encoder's inputs to a low power MCU. I didn't want to spend clock cycles working out if a mechanical encoder output was bouncing. I prototyped an R/C -> Schmitt trigger inverter circuit (after working out the resistor components for the time delay) and it worked flawlessly. Throughout the process I learnt more about 74xxx CMOS IC's and electronics so it was a win for me. I was also able to use the same circuit (different component values for more debounce) on some very noisy push button switches.

My project is a model train layout control board (I have mentioned this project in other threads) and will have upwards of 45 momentary push buttons (SPST) to debounce. I know now that there will be significant hardware / PCB design (for me anyway) to implement debounce using the above circuit (given the 74HC14 has only 6 Schmitt triggers for IC) so I am now looking at ways to debounce that number of switches in software - I will be using a Teensy 3.6 that runs a 180MHz ARM processor so I won't (shouldn't) be constrained by clock cycles.

I have used debounce code in previous Arduino projects to debounce a couple of switches and understand how it all works ie. waiting for successive input signals over a period of time before deciding in code if a button is pressed. What I am looking at now is using multiple 74HC165 PISO shift registers to read in 8 buttons at a time. I have read many pages on the net about debounce with shift registers and have read some sample code that I don't quite understand but I think I get the gist of.

Considering just one 8-bit shift PISO register here, if I were to set up a timer based interrupt and read the shift register ever 10ms. If I wanted a 20ms debounce test I would read the 8-bits every 10ms, store them, and then when I had 2 successive reads where the individual bit (in a particular placeholder from the two reads) was a 1 I would then know the button has gone high, likewise two successive 0's would indicate the button is low meaning a 0 and a 1 for a particular bit shows that input as bouncing.

I have written no code yet as I am at the stage of getting an understanding of how to process 45 buttons in an efficient manner. I am not asking anyone to code this for me - I'm asking for some direction before I have a go at it myself.

I'm thinking I could read in two bytes from the shift register over a 20ms period - 2 x interrupt cycles(or whatever the buttons actually needed) and store the byte in a structure/s were I could perform the necessary analysis.

ie. switch1_input = B10010001 //buttons 8,5,1 pressed
switch2_input = B10000000 //buttons 8, pressed

So from the above - button 8 (MSB) is a genuine press and buttons 5 and 1 are bouncing (or changing state).

Can I process these two bytes at once (using bitwise maths) and essentially perform the bounce analysis of 8 buttons at once without iterating through each of the 8 bits - this is what I meant above when I said I was after an efficient manner of debouncing.

I would have another array that held button "state" and when the buttons were found to be pressed just update that array - thats what the rest of my code would look at to read button state.

I don't know enough about bitwise maths to know if I can do this or not. Another thing I noticed was that if I added the bits vertically, a '2' would indicate the HIGH state over two cycles, a "0" a LOW state and "1" would mean its bouncing (or changing state) but to use this method would mean that I would have to go through each byte and do integer maths on each - this sounds inefficient compared to working on 8bits at once.

45 inputs would mean 6 x 74HC165 IC's and it would be nice to perform just 6 bitwise operations each 10ms to perform analysis on the current and previous byte from that register.

Or am I just way off ?

After a few more hours searching I came across this fork of some code that does exactly what I was after:

This is a useful resource on the topic as well:
http://www.ganssle.com/debouncing-pt2.htm

I wrote a simple test sketch below.

From the library ButtonCurrent() will return a byte showing 8 debounced button states.

I set 8 pins as inputs with internal pullups (just so I didn’t have to set up a board with 8 pull down resistors) and then just used a jumper wire to short each pin to ground, multiple at a time and wiping the ground wire down 8 pins and the code worked perfectly. I tested also with one of my noisy switches and the state change was detected flawlessly.

I think I will use this code to read each 8-bit value from 6 x 74HC165 PISO shift registers unless anyone can tell me otherwise.

I’ll grab some shift registers tomorrow to test this via SPI read in of the shift register and also with more than one byte of data, I’ll just have to use n x Debouncer objects.

In the final code I will have to change the object to be declared volatile so I can run an ISR from a timer based interrupt of a suitable frequency.

#include <ButtonDebounce.h>

long timer1 = millis();
long timer2 = millis();

byte tempByte;

byte lastButtonState;
byte currentButtonState;

//instantiate the debouncer will all pins using internal pullups
Debouncer port1(0b11111111);
//with external pulldowns it would be
//Debouncer port1();
//Debouncer port2(); etc

void setup() {
  Serial.begin(9600);
  pinMode(0,INPUT_PULLUP);
  pinMode(1,INPUT_PULLUP);
  pinMode(2,INPUT_PULLUP);
  pinMode(3,INPUT_PULLUP);
  pinMode(4,INPUT_PULLUP);
  pinMode(5,INPUT_PULLUP);
  pinMode(7,INPUT_PULLUP);
  pinMode(8,INPUT_PULLUP);
}

void prntBits(byte b)
{
  for(int i = 7; i >= 0; i--)
    Serial.print(bitRead(b,i));
  Serial.println();  
}

void loop() {

  //read button states every 4ms
  //debounce library is chekcing for 8 concurrent button states
  //therfore allowing for 24ms bounce
  if (millis() - timer1 > 4) {
    //generate a byte from 8 digital pin reads to simulate shift register read of one byte
    tempByte  = digitalRead (0);
    tempByte  |= digitalRead (1) << 1;
    tempByte  |= digitalRead (2) << 2;
    tempByte  |= digitalRead (3) << 3;
    tempByte  |= digitalRead (4) << 4;
    tempByte  |= digitalRead (5) << 5;
    tempByte  |= digitalRead (7) << 6;
    tempByte  |= digitalRead (8) << 7;
    
    port1.ButtonProcess(tempByte);
    timer1 = millis();
  }

  lastButtonState = currentButtonState;
  //read all 8 bits
  currentButtonState = port1.ButtonCurrent(0b11111111);

  if (lastButtonState ^ currentButtonState ) {
    //state for the byte has changed - print it
    prntBits(currentButtonState);
  }
}

No more assistance required for de-bouncing. I will post a new thread for code assistance relating to the inputs and outputs.

If you are using the Teensy, it has a 32bit MCU. So you can do your bitwise maths in 32 bit chunks.

Also consider multiplexing the buttons, it may make it a bit easier.

Smajdalf:
Also consider multiplexing the buttons, it may make it a bit easier.

I want to be able to press multiple buttons at once, I believe multiplexing is unable to handle this.

If you place diodes in series with each button, they can be read independently by the matrix.

For 50 or so switches I am just going to concentrate on the shift registers and the debounce code that I have working, it seems simpler (at least to me). The 74HC165's are easy to read and I can daisy chain enough (with the 74HC125 buffer IC) to run off the SPI bus of the Teensy.

Thank you for the excellent post and self follow up. Asking a question about switch de-bouncing often turns to a contentious post, primarily centering around the hardware vs software solution, and usually little in the way of helpful solutions for either. The two links were very helpful. Thank you.

I downloaded your code from GitHub and used your example above. Works like a charm on my Arduino Mega 2560 r3. I played with the interval and even at one 1ms (vs the default 4ms) it works like a charm touching two wires together. One interesting observation is when one sets the interval to say 120 (about 1 second for the eight pins checked) is that it takes a second (obviously), before it issues the status when pulling down an pin (touching the pin wire to gnd, but is essentially instantaneous when you disconnect.

Thank you again.