I am beyond rusty at Class Magic but remember some parts and I have a C based task and status method for debouncing to turn into a library.
But it's not simple or easy because I want a base class with derived classes for the many different ways buttons get wired up.
I think that:
The base class needs virtual functions .update() and .read() where read may have multiple args... I once knew how to do that but now there's a hole. The base class lets all instances use a base pointer and those functions.
From the base class derive classes for button, button array, button matrix, etc. Single button .read() has no arg but array needs read( index ) and matrix needs row and col. Same for .update().
Other added features have been suggested with code to catch taps and press durations... IMO those should be in derived classes of their own since they add RAM needed per pin.
The no-class demo, but see below:
// button2026 by GoForSmoke @ Arduino.cc Forum
// Free for use, Oct 23/2026 by GFS. Compiled on Arduino IDE 1.8.19.
// rev 0.001, added direct port pin read. Beginner digitalRead code left as comments.
/* 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.
Each button has a history byte that holds the last 8 reads with 256 possible
states but only 6 of them being significant. Pin mode is INPUT_PULLUP.
detect stable static button
0 is button held down.
0xFF is button left up.
detect transition after pre-change stable period
0x80 is button change from up to down and 7 stable down reads.
0x7F is button change from down to up and 7 stable up reads.
detect transition then post-change stable period
0xFE is button change from up to down after 7 stable up reads.
1 is button change from down to up after 7 stable down reads.
everything else is to be ignored as bounce. Most apps might use 0x80.
Note that 4 bit history works with good buttons...
if (( history & 15 ) == 8 ) { down to up after 3 stable reads };
*/
extern volatile unsigned long timer0_millis; // might be faster than millis()
// button vars
byte buttonPin = 7;
byte buttonHistory;
byte lastMillis, nowMillis;
// 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() // this is the history keeper, 1 read every 1024 micros.
{
nowMillis = ( timer0_millis & 255 );
if ( lastMillis != nowMillis )
{
buttonHistory <<= 1; // moves current reads up and bit 7 is lost, bit 0 = 0
// beginner friendly half-fast pin read for Uno R3
// buttonHistory += digitalRead( buttonPin ); // read history streams through buttonHistory
// alternate faster non-beginner bit logic used pin read for Uno R3
// PIND & 0x80; // PIND is pins 0 to 7, pin 7 is bit 7
buttonHistory += PIND & 0x80 ? 1 : 0; // read history streams through buttonHistory
lastMillis = nowMillis;
}
}
void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
if ( waitBlink > 0 ) // this is the on/off switch
{
if (( timer0_millis & 0xFFFF ) - 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." ));
Serial.println( F( "--- events reports followed by micros() to show pre from post detect.\n\n" ));
pinMode( buttonPin, INPUT_PULLUP );
pinMode( ledPin, OUTPUT );
buttonHistory = 255;
};
void loop()
{
buttonTask();
/*
detect stable static button
0 is button held down.
0xFF is button left up.
detect transition after pre-change stable period
0x80 is button change from up to down and 7 stable down reads.
0x7F is button change from down to up and 7 stable up reads.
detect transition then post-change stable period
0xFE is button change from up to down after 7 stable up reads.
1 is button change from down to up after 7 stable down reads.
everything else is to be ignored as bounce. Most apps might use 0x80.
*/
switch ( buttonHistory )
{
case 0x80 : // pin is HIGH in bit 7 then LOW for 7 reads, up to down detected
buttonHistory = 0; // change detected, make it into no detect now
Serial.print( F( "post stable press detected " ));
Serial.print( millis());
if ( waitBlink == 0 ) // toggle action tied to button press
{
waitBlink = 500; // makes the blinking start
startBlink = millis(); // gets the low 16 bits
Serial.println( F( " blink" ));
}
else
{
waitBlink = 0; // makes the blinking stop
Serial.println( F( " stop" ));
}
break;
case 0x7F : // pin is LOW in bit 7 then HIGH for 7 reads, down to up detected
buttonHistory = 0xFF; // change detected, make it into no detect now
Serial.print( F( "post stable release detected " ));
Serial.println( millis());
Serial.println();
break;
case 0xFE : // pin is HIGH for 7 reads then LOW, up to down detected
buttonHistory = 0xFC; // change detected, make it into no detect now
Serial.print( F( "pre stable press detected " ));
Serial.println( millis());
break;
case 1 : // pin is LOW for 7 reads then HIGH, down to up detected
buttonHistory = 0x3; // change detected, make it into no detect now
Serial.print( F( "pre stable release detected " ));
Serial.println( millis());
break;
}
OnOffBlinker();
}
This is the single pin debounce update task.
// button2026 by GoForSmoke @ Arduino.cc Forum
// Free for use, Oct 23/2026 by GFS. Compiled on Arduino IDE 1.8.19.
extern volatile unsigned long timer0_millis; // might be faster than millis()
void buttonTask() // this is the history keeper, 1 read every 1024 micros.
{
nowMillis = ( timer0_millis & 255 );
if ( lastMillis != nowMillis )
{
buttonHistory <<= 1; // moves current reads up and bit 7 is lost, bit 0 = 0
// beginner friendly half-fast pin read for Uno R3
buttonHistory += digitalRead( buttonPin ); // read history streams through buttonHistory
// alternate faster non-beginner bit logic used pin read for Uno R3
// PIND & 0x80; // PIND is pins 0 to 7, pin 7 is bit 7
// buttonHistory += PIND & 0x80 ? 1 : 0; // read history streams through buttonHistory
lastMillis = nowMillis;
}
}
as for .read() and maybe .isPress(), etc ---- I only have ideas. As above, when a transition -is- detected the history needs to get moved forward (assuming no state change) 1 step just to keep another read from triggering before the next history update. Arduino process may check that pin >50 times between updates.
Am I anywhere near on track or just must be on crack?