Here's a nice (working) multi pushbutton debouncer that runs on the M0 using timer TC4 (which I chose randomly). I don't know how to find out what timers and timer interrupts are used with existing libraries so this may interfere with some. The timer can be changed (see the forum reference in the code). This example uses two buttons and three output pins to demo how it works. This was originally written for the Uno, now ported to the M0.
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// This does debouncing using Jack Ganssle's shifting technique
// called 'DebounceSwitch2()' from "The Art of Designing Embedded Systems"
// It depends on having a periodic timer interrupt to shift bits and test result.
// This example uses M0 timer4 which may screw up other things where it's used by other programs
// It may be changed to use another timer if that is the case.
//
// pg 1/25/17
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#define NUM_PBS 2 //number of switches
#define INPUTPIN_A 5 //PB_A PIN
#define INPUTPIN_B 6 //PB_B PIN
//choose 3 IO pins for separate LEDs to show the three states
#define LED1PIN 13 //on-board RED LED
#define LED2PIN 10
#define LED3PIN 11
//function prototypes
void debounceSwitches2();
void turnOffAllLEDs();
byte input_pins[NUM_PBS] = {INPUTPIN_A,INPUTPIN_B};
uint16_t debounceState[NUM_PBS] = {0}; //history of switch closures
bool pb_detected[NUM_PBS] = {false}; //array containing present states of all PBs
uint8_t machineState = 0; //state machine for testing first PB
volatile bool ovfFlag = false;
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("Uno running!");
pinMode(INPUTPIN_A, INPUT_PULLUP); // PB_A input
pinMode(INPUTPIN_B, INPUT_PULLUP); // PB_B input
pinMode(LED1PIN, OUTPUT);
pinMode(LED2PIN, OUTPUT);
pinMode(LED3PIN, OUTPUT);
//==================== Set up the generic clock (GCLK4) used to clock timers
// Code modified from this original by MartinL: https://forum.arduino.cc/index.php?topic=332275.15
// Set overflow to get about 10mS ovf interrupts (this may be overkill, 5mS may be fine depending on the switch)
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(192) | // Divide the 48MHz clock source by divisor 192: f=250kHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Feed GCLK4 to TC4 and TC5
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK4 to TC4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_CTRLA |= TC_CTRLA_MODE_COUNT8; // Set the counter to 8-bit mode
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_COUNT8_PER = 0x9B; // Set the PER (period) register to get ~10mS interrupts
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect TC4 to Nested Vector Interrupt Controller (NVIC)
REG_TC4_INTFLAG |= TC_INTFLAG_MC1 | TC_INTFLAG_MC0 | TC_INTFLAG_OVF; // Clear the interrupt flags
REG_TC4_INTENSET = TC_INTENSET_OVF; // Enable TC4 ovf interrupt
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, f=15.625kHz (legal values 1,2,4,8,16,64,256,1024)
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
//==============================================================================================================
}
void loop() {
//state 0 all LEDs off
//state 1 LED1 ON
//state 2 LED2 ON
//state 3 LED3 ON
//then back to state 0
if(ovfFlag){
ovfFlag = false; //reset the flag
debounceSwitches2();
//test first PB and increment machine state if closed
if(pb_detected[0]){
machineState = ++machineState % 4;
Serial.print("State: "); //prints for debug only
Serial.println(machineState);
} //end if(pb_detected)
//test second PB and decrement machine state if closed
if(pb_detected[1]){
machineState = --machineState % 4;
Serial.print("State: "); //prints for debug only
Serial.println(machineState);
} //end if(pb_detected)
} //end if(ovfFlag)
switch(machineState)
{
case 0:
turnOffAllLEDs();
break;
case 1:
turnOffAllLEDs();
digitalWrite(LED1PIN,HIGH);
break;
case 2:
turnOffAllLEDs();
digitalWrite(LED2PIN,HIGH);
break;
case 3:
turnOffAllLEDs();
digitalWrite(LED3PIN,HIGH);
break;
default:
turnOffAllLEDs();
break;
} //end of switch
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
// Check for overflow (OVF) interrupt
if (TC4->COUNT8.INTFLAG.bit.OVF && TC4->COUNT8.INTENSET.bit.OVF)
{
ovfFlag = true; //don't do anything time consuming here, just set flag and get out
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag
}
} //end TC4_Handler
/***********************************************************
*
* clever edge detection code from JG
*
************************************************************/
void debounceSwitches2()
{
//this runs 10 times (run from timer interrupt ie 10 x 10mS)
//shift state left by one, no wrap then OR with button input state then OR with 0xF800
//the 0xF800 and 0xFC00 will cause 10 cycles of delay (can be 14 or less)
//The result is just a single true (falling edge detected) and no response to rising edge
// do for all pushbuttons
for (int i = 0; i < NUM_PBS; i++)
{
debounceState[i] = (debounceState[i] << 1) | digitalRead(input_pins[i]) | 0xF800;
if(debounceState[i] == 0xFC00) pb_detected[i] = true;
else pb_detected[i] = false;
}
} //end debounceSwitch
//this does what it says
void turnOffAllLEDs()
{
digitalWrite(LED1PIN,LOW);
digitalWrite(LED2PIN,LOW);
digitalWrite(LED3PIN,LOW);
}
//===========================================================================
//Debounce algorithm explained
//Each line is one sample taken at timer interrups
//Closed button = 0
//Mask 0xF800
//Detect 0xFC00
// Nib3 / Nib2 / Nib1 / Nib0 / Button / Notes
// 1111 / 1111 / 1111 / 1111 / 1 /Button has been open for long time
// 1111 / 1111 / 1111 / 1110 / 0 /Button just closed
// 1111 / 1111 / 1111 / 1101 / 1 /Button bounce
// 1111 / 1111 / 1111 / 1010 / 0 /Button closed
// 1111 / 1111 / 1111 / 0101 / 1 /Button bounce
// 1111 / 1111 / 1110 / 1010 / 0 /Button closed
// 1111 / 1111 / 1101 / 0101 / 1 /Button bounce
// 1111 / 1111 / 1010 / 1010 / 0 /Button closed
// 1111 / 1111 / 0101 / 0100 / 0 /Button closed
// 1111 / 1110 / 1010 / 1000 / 0 /Button closed
// 1111 / 1101 / 0101 / 0000 / 0 /Button closed
// 1111 / 1010 / 1010 / 0000 / 0 /Button closed
// 1111 / 1101 / 0100 / 0000 / 0 /Button closed
// 1111 / 1010 / 1000 / 0000 / 0 /Button closed
// 1111 / 1101 / 0000 / 0000 / 0 /Button closed
// 1111 / 1010 / 0000 / 0000 / 0 /Button closed
// 1111 / 1100 / 0000 / 0000 / 0 /Button closed Closing edge detected 10 cycles after last bounce (== 0xFC00)