Button combo retrigger/debounce

So I'm turning to the forum here as my last hope.
I have been stuck on trying to debounce buttons for the better part of 3 days now and I simply can't figure out what is going wrong.

I would massively appreciate any insight that you might have into how to fix this issue.
Or if you have any thoughtful advice on how to debug that would be great as well, cause I can't even figure out where the fault lies.

The problem is that I get very sporadic double presses on my buttons, probably like once every 50 presses or so. There seems to be no way to replicate the problem. It seems to just be totally random.
I have rewritten the code about 3 times and simply can't figure out where my logic is going wrong.

Ok so heres the setup,
I have 6 buttons using 10k pull down resistors, the buttons are fed into a 4021 shift register. The remaining 2 inputs are tied to ground (via 10k resistors). I am polling the 4021 in the main loop of my program. It returns all the proper values. I have also connected the buttons I am using to an oscilloscope and looked at their "bounciness". There is almost no bounce. Occasionally a very little glitch but usually in the sub 1ms range. Based on all this I am pretty confident that there is no hardware issue with how the buttons are connected or being read.

Ok, on to the software - I need to be able to read single button presses and different combinations of buttons pressed at the same time. (I realize this isn't ideal, but I can't change it at this point due to the hardware being finalized).

The basic code logic is this:

poll the shift register

when the shift register returns a non zero value this means
a button is/was pressed and we store the initial value of that button and start a timer
(using a shift register so I get a binary value 1,2,4,8,16, etc…representing what button was pressed)

Wait until a certain amount of time has elapsed (to allow for additional buttons to be pressed)
When time has elapsed we reread the value of the shift register and "or" it with the previously stored button value.
(I am saving and "or" ing the initial button press so that the user does not need to hold a single button press down until the time elapses.)

return the value representing which buttons were pressed from the function.

Set a flag stating that we can't read any more button presses until the shift register value returns to 0
Set a timer saying we can't clear the flag until some time has elapsed (so we don't get bounces on button release.

and heres the code for the function.
Oh and one last note- the function does not return anything, it just changes the value of the "buttonsPressed" variable.

Sorry for the wall of text. :o

void readButtons(byte shiftRegButtonsVal)
{

  //button variables
static boolean readOk;                      //have we met the criteria necessary before we can read the buttons agin?
static byte lastButton;                     //holds the previous button value to compare against and see when a change has occured
static unsigned long readTime;              //the time that we will actually read the button values
static unsigned long upTime;                //how long do the buttons need to be "unpressed" before we can read again
static unsigned int actionDelay = 90;       //delay between first detection of a press and when we read the button values 
static byte valueAtFirstPress;              //Which button was initially pressed?




// 1
// is the button either not pressed or just shift?
  if ((shiftRegButtonsVal == 0) && (millis() > upTime)) //the buttons have been released and we have waited 5ms before we can start reading again
  {
    readOk = true;       //its ok to read again if the shift reg is returning 0 and enough time has elapsed 
  }

 
 // 2
 //this section sets the timer to start counting down the delay between firstpress and when we read the button state
 //this will reset the timer any time a new buttonpress is registered...
  if((shiftRegButtonsVal != prevButtons) && (shiftRegButtonsVal != 0) && (readOk == true))     //if we have a button value change and the change is not to nothing or shift 
  {
    readTime = millis() + actionDelay;   // set the startTime- start time is the time when we will actually read what buttons are pressed   
    valueAtFirstPress = shiftRegButtonsVal;            //this will hold the value representing what button is pressed when we start the timer     
  } 


  // 3
  //wait then check if multiple buttons are pressed
  //if starttime is not 0 then we have pushed a button / and we wait for millis to get larger than our delay setting, and if the button pressed is not shift then we read the value of combined buttons
  if((readTime != 0) && (millis() > readTime) && (readOk == true))   // is it the time to check for other (second/third) buttons yet? Only pressed buttons have a nonzero startime.
  { 
    buttonsPressed = shiftRegButtonsVal | valueAtFirstPress;       //wait the alloted time then check what buttons are pushed and output the or'd value                                                    
    readTime = 0;
    valueAtFirstPress = 0;
    readOk = false; // we cant read the buttons again until we release the buttons back to 0 or shift 
  } 


  if ((shiftRegButtonsVal == 0) && (prevButtons != 0)) //if we just released the button, set the timer.
  {
    upTime = millis()+5;
  }

  prevButtons = shiftRegButtonsVal;  //update buttons so we know if there is a change

}

I have a debounce library at my github page . There are also quite a few other debounce libraries and sample code in the playground

Playground - main index
Playground - buttons libraries

  lastButton = switchVar1;  //update buttons so we know if there is a change

I know no such thing. Those names appear to have just been typed at random. I am positive that meaningful names will reveal the issue.

Thanks for the responses,
PaulMurray, I will check out those libraries, though I think the problem is more likely related to the code logic than the actual button debouncing.

PaulS - I'm not sure what you mean. The switchVar1 variable holds the current value returned by the shift register, and lastButton holds the value of this variable from the last time through the function so we can determine when/ if it has changed. I tried to give the variables meaningful names, but maybe their less meaningful to other people.

Or if I misinterpreted your comment, let me know.

I tried to give the variables meaningful names, but maybe their less meaningful to other people.

For one thing, switchVar1 does not contain information about 1 switch.
For another, changing from switch to button in the name is confusing.
For another, currXxxx and prevXxxx are the names I use when I want to convey that one contains the current data and one contains the previous data.

So, yeah, your names are confusing.

PaulS:
For one thing, switchVar1 does not contain information about 1 switch.
For another, changing from switch to button in the name is confusing.
For another, currXxxx and prevXxxx are the names I use when I want to convey that one contains the current data and one contains the previous data.

So, yeah, your names are confusing.

Ok your right, I suppose I have been working on this project for so long that I've stopped noticing things like that. I will amend the original code to hopefully make it more clear to others.

pre55ure:
The basic code logic is this:

poll the shift register

when the shift register returns a non zero value this means
a button is/was pressed and we store the initial value of that button and start a timer
(using a shift register so I get a binary value 1,2,4,8,16, etc…representing what button was pressed)

Wait until a certain amount of time has elapsed (to allow for additional buttons to be pressed)
When time has elapsed we reread the value of the shift register and "or" it with the previously stored button value.

I haven't tried buttons on a shift register, how many times per milli can you read and process data from the shift register?

My debounce code works directly from AVR pins, loop() checks only 1 pin per pass through loop(). However when loop() runs at over 20KHz (the demo runs over 50KHz) that still checks every button pin 5+ times per milli.

What my debounce code looks for is pin state stable for 2 to many millis depending on the buttons. I test with a jumper that I make work as a dirty switch, I might go 5 millis.

Stable state means the pin has stayed ON or OFF, meaning check as often as possible to catch changes in pin state, not ignoring the pin but again this requires that loop() runs fast.

With the register on SPI you get all the pins at once as a byte that you can process quickly using bitwise logic, for example ( this_read & last_read ) will tell you which buttons have not changed as bits in the result. If you don't know bits and bitwise logic commands then I can give you a link to get you started.