Yet More Buttons! -- Single, Multiple, Matrix Add-a-Sketches

Added preface: These sketches are to go into an Intro Tutorial set of sketches that can be combined into each other and still work if done reasonably. The set includes an example of replacing delays with a timer and switch-case statement, both of which are in the Arduino Tutorial Examples so I figure are fair to present to beginners.
Yes, I’m still working on comments and user info.

In the name of beginner friendly and faster, shorter code and button insanity in general, here is Yet Another Button Code.

The Multi-Button Code is Reply #8.
The Button Matrix code is after that.

This one only checks the pin twice a milli. The multi-button version will seek to check each pin as often.

The button has a status byte that is no more than pin history. Every bit from low to high is a later read and the value tells all that the sketch needs to know. 127 is 0b01111111 says that the button was down and has been up when checked 7 times in a row since, call it release detected. 128 is press detected, 0 is down, 255 is up and everything else is garbage.
I use 16-bit micros timing for this, a 500us timer can be 60ms late and still hit (not roll over and mistime) is enough margin for me. 2 byte timers run faster than UL timers and use half the RAM, a consideration when many buttons is a longer goal.

Assuming the button is not crap, there will be a spiky bounce transition patch between the pin being at one stable state and the other state also stable. The bounce time may be indeterminate but I don’t see 2 ms gaps between spikes without the contacts physically wobbling, as in I wobble contacts just to see this kind of thing.

Trouble is that while it’s easy for me to wobble, it’s hard not to. Some day I need to wire a button but the idea of using what anyone can pick up and try too has been pretty powerful to me so I’ve stuck with it. I wrote this thing and when I’m careful grounding the jumper/button it works like a charm but that’s hardly a test is it?

This example also toggles the status led on every button press.

This compiles and runs but I’d like it tested by kind souls and maybe even PaulS. :o

// add-a-sketch_button 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.
  --- Press and hold the Button to toggle led13 blinking.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton changing from up to down, button just released.
  128 is the button changing from down to up, button just pressed.
  everything else is to be ignored as bounce.
*/

// int r,s,t;
// byte a,b,c;

// button vars
byte buttonPin = 7;
byte buttonState;
byte buttonReadFlag; // this tells the sketch that buttonState just changed.
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 500; // micros

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds

void buttonTask()   // yup, this is my new button debounce compromise method.
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonReadFlag = 1;
    buttonState <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonState += digitalRead( buttonPin ); // read history streams through buttonState
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }
  /*        buttonState bits read as values:
    0 is button held down, 8 reads of 0 in a row
    255 is button left up, 8 reads of 1 in a row
    127 is buton changing from up to down, 7 1's and 1 0 = just released
    128 is button changing from down to up, 7 0's and 1 1 = just pressed.
    everything else is to be ignored as bounce.
    Understand that 7 same bits in a row counts as pin state stable.
  */
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}

void setup()
{
  Serial.begin( 115200 );
  for ( byte i = 0; i < 66; i++ )  Serial.println();
  Serial.println( F( "\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( buttonPin, INPUT_PULLUP );
  pinMode( ledPin, OUTPUT );

  buttonState = 255;
  waitBlink = 500;
};


void loop()
{
  buttonTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  if ( buttonReadFlag > 0 )  // we only check state once after it has changed
  {                          // state does not change as quickly as void loop() runs.
    buttonReadFlag = 0;
    switch ( buttonState ) // except that buttonState does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        Serial.print( F( "press detected     " ));
        Serial.println( millis());
        if ( waitBlink == 0 ) // toggle action tied to button press
        {
          waitBlink = 500; // makes the
          startBlink = millis(); // gets the low 16 bits
        }
        else
        {
          waitBlink = 0;
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        Serial.print( F( "release detected   " ));
        Serial.println( millis());
        break;
    }
  }

  OnOffBlinker();
}

Interesting take on the button problem.

The main issue I can find is that it pretty much relies on being called frequently. So no delay() or even a long Serial.print() statement is allowed (just 60 characters at the default 9600 bps and you're hitting your 60 ms mark already).
The solution to this would be a timer interrupt, so the buttons get read pretty much regardless of what else happens. But that again may cause clashes with other libraries that also want to use timers.

wvmarle:
Interesting take on the button problem.

The main issue I can find is that it pretty much relies on being called frequently. So no delay() or even a long Serial.print() statement is allowed (just 60 characters at the default 9600 bps and you're hitting your 60 ms mark already).
The solution to this would be a timer interrupt, so the buttons get read pretty much regardless of what else happens. But that again may cause clashes with other libraries that also want to use timers.

Actually my previous debouncers all worked through close checking, the examples typically ran loop() at 67KHz when I counted. So going from checking 67 times per millisecond to twice... that's just a little bit of backing off on pin-attention.

Perspective: 500us is 8000 cycles and I'm doing a very short read history update once that often where I could do 33 times.
Where I get the debounce is in 7 reads that repeat with the 8th=oldest being the opposite state.
Read every 500us, debounce time is 3500us of reads all the same. That's acceptable, average total response should be 5 or 6ms.

It's not a problem to have 10 buttons and read them 1 by 1 at a 50us (800 cycles) per read rate. I have code that does way more with 250000 baud (25000 cps=40us/640 cycles apart) text even while printing trace/watch info.

The whole deal with cycles is that AVR's are RISC's that with optimized code run close to 1 instruction per cycle.

Your reply came very fast, did you run the sketch even with just a jumper for button/switch?

wvmarle:
Interesting take on the button problem.

The main issue I can find is that it pretty much relies on being called frequently. So no delay() or even a long Serial.print() statement is allowed (just 60 characters at the default 9600 bps and you're hitting your 60 ms mark already).
The solution to this would be a timer interrupt, so the buttons get read pretty much regardless of what else happens. But that again may cause clashes with other libraries that also want to use timers.

About print time and Serial baud. I do set the rate at 115200, do I need to comment that it's just to empty the output buffer quicker? I have before.

It's not great coding to overfill the serial buffer.

OTOH what if you do and hang things up to where the button read timer thinks that it hasn't been 500us since last read? The button would be unresponsive if the delay caused by overloading the serial output buffer fell in the 0 to 499 part or the 500 to 65535 part.

If the programmer wants to print a book to screen and have responsive buttons, it's up to the programmer to not block, to control the speed of data passed on to Serial.

65ms is 1040000 cycles. It's long enough to see.

No, didn’t try, would have to rig up something first. Mostly interested in the debounce technique you use - keeping the last 8 reads in a byte, and the update routines for it.

I just had to think immediately on the many “my buttons don’t work!” threads where the code is filled with long delay() statements and the like…

GoForSmoke:
OTOH what if you do and hang things up to where the button read timer thinks that it hasn’t been 500us since last read? The button would be unresponsive if the delay caused by overloading the serial output buffer fell in the 0 to 499 part or the 500 to 65535 part.

The chance of this happening is quite small (<1% of the time it’s in that range) so likely the next try it’ll be fine.

wvmarle:
I just had to think immediately on the many "my buttons don't work!" threads where the code is filled with long delay() statements and the like...

And buttons work with code that has long delays .... with interrupt and still the sketch has to wait but I dunno how else.

The lesson about using millis for timing is the start for learning automation. It unlocks Do Many Things At Once. The corollary is Do Them In Quick Steps that leads to state machines and more responsive code.

You can code for events in a certain order and time. That invites debugging with delays just to impose order.

You can code for events to happen whenever they do and the sketch to react appropriately even if to report an error.

I came across a problem with the main code checking button status more often than the button is read but only a problem with status 127 and 128. I fixed it but I'm not incredibly happy with the fix. I should change the status value instead, 128 to 0 and 127 to 255 as that would be cleaner and save a byte.

Changed but Not Compiled Or Tested. It’s cleaner, less code and memory used.

// add-a-sketch_button 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// Update untested May 4/2018
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.
  --- Press and hold the Button to toggle led13 blinking.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton changing from up to down, button just released.
  128 is the button changing from down to up, button just pressed.
  everything else is to be ignored as bounce.
*/

// int r,s,t;
// byte a,b,c;

// button vars
byte buttonPin = 7;
byte buttonHistory;
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 500; // micros

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds

void buttonTask()   // yup, this is my new button debounce compromise method.
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory += digitalRead( buttonPin ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }
  /*        buttonHistory bits read as values:
    0 is button held down, 8 reads of 0 in a row
    255 is button left up, 8 reads of 1 in a row
    127 is buton changing from up to down, 7 1's and 1 0 = just released
    128 is button changing from down to up, 7 0's and 1 1 = just pressed.
    everything else is to be ignored as bounce.
    Understand that 7 same bits in a row counts as pin state stable.
  */
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}

void setup()
{
  Serial.begin( 115200 );
  for ( byte i = 0; i < 66; i++ )  Serial.println();
  Serial.println( F( "\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( buttonPin, INPUT_PULLUP );
  pinMode( ledPin, OUTPUT );

  buttonHistory = 255;
  waitBlink = 500;
};


void loop()
{
  buttonTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

    switch ( buttonHistory ) // except that buttonState does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        buttonHistory = 0;
        Serial.print( F( "press detected     " ));
        Serial.println( millis());
        if ( waitBlink == 0 ) // toggle action tied to button press
        {
          waitBlink = 500; // makes the
          startBlink = millis(); // gets the low 16 bits
        }
        else
        {
          waitBlink = 0;
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        buttonHistory = 255;
        Serial.print( F( "release detected   " ));
        Serial.println( millis());
        break;
    }

  OnOffBlinker();
}

wvmarle:
No, didn't try, would have to rig up something first.

What I'm doing including the wobbles is sticking 1 end of a jumper in pin 7 and touching the other to the grounded USB port box.

That is my button that I always have in reach along with at least 1 Uno. Add a red led and you have a light detector.

2 buttons seem to work okay. Already a doc oops, stick a button/jumper in pin 6 as well as 7.

// add-a-sketch_buttons 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 6/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton transitioning from up to down, button just released.
  128 is the button transititioning from down to up, button just pressed.
  everything else is to be ignored as bounce.

  For multiple buttons on 1:1 pins the between-reads time is reduced and each
  pin is read in turn. This demo makes pin 0 be ON-RESET/OFF and pin 1 adjusts
  blink time.
*/

// int r,s,t;
// byte a,b,c;

// multi_buttons vars
const byte buttons = 2;
byte buttonIndex, lastIndex; // only process 1 button every waitButtonTime micros
byte buttonPin[ buttons ] = { 6, 7 };
byte buttonHistory[ buttons ];
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 250; // micros, 20 to 500 / more to fewer buttons.
// type word as micros can time across 65.535 millis before rollover, can be a few late

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds


void multiButtonsTask()
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory[ buttonIndex ] <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory[ buttonIndex ] += digitalRead( buttonPin[ buttonIndex ] ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }

  // ++buttonIndex pre-increments buttonIndex before comparing to buttons
  if ( ++buttonIndex >= buttons )   buttonIndex = 0;
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "This code shows use of struct, union, bit fields and 16 bit timers." ));
  Serial.println( F( "It can work with many buttons, uses structs to hold button data." ));
  Serial.println( F( "Button data structs can be arrayed even for matrix arrangements." ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( ledPin, OUTPUT );

  for ( byte i = 0; i < buttons; i++ )
  {
    pinMode( buttonPin[ i ], INPUT_PULLUP );
    buttonHistory[ i ] = 255;
  }
};


void loop()
{
  multiButtonsTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  if ( buttonIndex != lastIndex )
  {
    lastIndex = buttonIndex;

    switch ( buttonHistory[ buttonIndex ] ) // buttonHistory does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        buttonHistory[ buttonIndex ] = 0; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  press detected     " ));
        Serial.println( millis());
        if ( buttonIndex == 0 )
        {
          if ( waitBlink == 0 ) // toggle action tied to button press
          {
            waitBlink = 500; // makes the blinking start
            startBlink = millis(); // gets the low 16 bits
          }
          else // press button 2 changes the blink rate
          {
            waitBlink = 0; // makes the blinking stop
          }
        }
        else
        {
          if (( waitBlink -= 100 ) < 100 ) // setting a var in an expression is okay C
          {
            waitBlink = 1000;
          }
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        buttonHistory[ buttonIndex ] = 255; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  release detected   " ));
        Serial.println( millis());
        break;
    }
  }

  OnOffBlinker();
}

Slightly tested — the button matrix example. I stuck jumpers in 6 and 7 then put them in and out of 4 and 5.

I learned a bit about timing with this one. Answer was to run asynch between inputs and outputs.

// add-a-sketch_matrix_buttons 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 9/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  This example requires a diode per button in the row&column matrix.
  Each row and column pin has its own wire. Where they cross a button and diode
  connect in series allowing current to flow from the row wire to th column wire
  only. The diode is to allow multiple buttons pressed at once detected correctly.

  OTOH for this test, jumper a row pin (2 or 3) to a column pin (4 or 5) while
  watching serial monitor.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton transitioning from up to down, button just released.
  128 is the button transititioning from down to up, button just pressed.
  everything else is to be ignored as bounce.

  For multiple buttons the between-reads time is reduced and each pin is read in
  turn.
*/

// int r,s,t;
// byte a,b,c;

// matrix_buttons vars --- 2D matrix
const byte rows = 2; // test with 4 buttons or jumper
const byte cols = 2;
byte rowIdx, colIdx = 255;
byte buttonRowPin[ rows ] = { 6, 7 };
byte buttonColPin[ cols ] = { 4, 5 };
byte buttonHistory[ rows ][ cols ];
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 50; // micros, 50 us
// type word as micros can time across 65.535 millis before rollover, can be a few late


void matrixButtonsTask()
{
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    // iterate the row and column indexes
    if ( ++colIdx >= cols ) // ++rowIdx pre-increments rowIdx before comparing to rows
    {
      colIdx = 0;
      if ( ++rowIdx >= rows ) // ++rowIdx pre-increments rowIdx before comparing to rows
      {
        rowIdx = 0;
      }
    }

    pinMode( buttonRowPin[ rowIdx ], INPUT_PULLUP ); // supplies weak 5V
    pinMode( buttonColPin[ colIdx ], OUTPUT ); // is LOW, grounds pressed buttons

    buttonHistory[ rowIdx ][ colIdx ] <<= 1; // shift history bits up 1 for the new read
    buttonHistory[ rowIdx ][ colIdx ] += digitalRead( buttonRowPin[ rowIdx ] ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin

//    digitalWrite( buttonRowPin[ rowIdx ], LOW ); // turn the weak 5V off
//    pinMode( buttonRowPin[ rowIdx ], OUTPUT );
//    delayMicroseconds( 10 ); // drain the wire
    pinMode( buttonRowPin[ rowIdx ], INPUT );
    pinMode( buttonColPin[ colIdx ], INPUT ); // was OUTPUT LOW
  }
}


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Matrix Example, free by GoForSmoke\n" ));

  for ( byte i = 0; i < rows; i++ )
  {
    pinMode( buttonRowPin[ i ], INPUT ); // mode INPUT pin is electrically neutral
    for ( byte j = 0; j < cols; j++ )
    {
      if ( i == 0 ) // only set the column pins once
      {
        pinMode( buttonColPin[ j ], INPUT ); // mode INPUT pin is electrically neutral
      }

      buttonHistory[ i ][ j ] = 255; // all buttons start UP
    }
  }
};


void loop()
{
  matrixButtonsTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */
  static byte row, col;

  switch ( buttonHistory[ row ][ col ] )
  {
    case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
      buttonHistory[ row ][ col ] = 0; // change detected, make it into no change now
      Serial.print( F( "button " ));
      Serial.print( rowIdx );
      Serial.print( F( ", " ));
      Serial.print( colIdx );
      Serial.print( F( "  press detected     " ));
      Serial.println( millis());
      break;

    case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
      buttonHistory[ row ][ col ] = 255; // change detected, make it into no change now
      Serial.print( F( "button " ));
      Serial.print( rowIdx );
      Serial.print( F( ", " ));
      Serial.print( colIdx );
      Serial.print( F( "  release detected   " ));
      Serial.println( millis());
      break;
  }

  if ( ++col >= cols )
  {
    col = 0;;

    if ( ++row >= rows )
    {
      row = 0;;
    }
  }
}