Help with Debouncing a Pin Interrupt Change

Hello,

I am building a watch and would like to put the Micro controller to sleep, and wake it up with the touch of one of the buttons. However, I want to debounce this button, which I believe does not work in the normal way as millis() doesnt increase inside the Interrupt Service Routine looping.

Does any one have any example code or libraries that work for this? I have already tried to research but only found a hardware debounce for this problem.

thanks in adavnce!!

jmramosfran:
I have already tried to research but only found a hardware debounce for this problem.

I'm surprised you couldn't find anything. A quick Google search brought up plenty of answers for me.

This is probably one of the more simple approaches. Although I would move the call to the function into the loop and just set a flag in the ISR.

//Software debouncing in Interrupt, by Delphiño K.M.

long debouncing_time = 15; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;

void setup() {
 attachInterrupt(0, debounceInterrupt, RISING);
}

void loop() {
}

void debounceInterrupt() {
 if((long)(micros() - last_micros) >= debouncing_time * 1000) {
   Interrupt();
   last_micros = micros();
 }
}

void Interrupt() {
 //Do Something
}

When the interrupt wakes up the controller, you can read and debounce the button as usual, inside loop(). As soon as the debounce interval is over, put the controller into sleep mode again.

If you are suffering from bounce I wonder if CHANGE is the right option. Perhaps using RISING would be better and then use the ISR to change that to FALLING for the next event etc

What is the minimum time (in microsecs) between legitimate events?

If you are just using the interrupt to wake up then you just need to respond to the first one and use it to turn off more interrupts on that pin until you are ready for more.

...R

saximus: Thank you for your help. I tried searching but I am not sure how it all works so might have overlooked some of the options.

DrDiettrich: Does that means that putting the debouncer in the loop is the same as in the ISR?

Robin2: THat makes sense! Thanks for yoru help!

It's better in loop(), because there it will not block further interrupts.

DrDiettrich:
It's better in loop(), because there it will not block further interrupts.

Would that be the case if I disabled the interrupts?

I have this preliminary code. It works, sends it to sleep and all. But the problem is that when I press the button (it does the right thing and displays the activity) and keep it pressed for more than a second it wakes up the arduino. I have no ideas why though. Any tips?

//Library for sleeping
#include <avr/sleep.h>
#include <EnableInterrupt.h>

//==========================START OF IGNORE============================
//Library for Display - Can skip it if you want!

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2


#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif


String activity1 = "Work";

//____________________________END OF IGNORE______________________________

//Variable setup fot Debounce

int debounceCounter111 = 0;

int buttonState = HIGH;

long lastDebounceTime = 0; 
long debouncedelay = 50;



// Interrupt Service Routine
void wake ()
{

  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
 // If interrupts come faster than 200ms, assume it's a bounce and ignore
 if (interrupt_time - last_interrupt_time > 200) 
 {

  sleep_disable();
  disableInterrupt(8);
 }
 last_interrupt_time = interrupt_time;
}


void sleep()
{
  // disable ADC
ADCSRA = 0;

//set_sleep_mode (SLEEP_MODE_STANDBY);
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();

// Do not interrupt before we go to sleep, or the
// ISR will detach interrupts and we won't wake.
noInterrupts ();

// will be called when pin D2 goes low
////attachInterrupt (0, wake, FALLING);//
pinMode(8, INPUT_PULLUP);
enableInterrupt(8, wake, FALLING);

// turn off brown-out enable in software
// BODS must be set to one and BODSE must be set to zero within four clock cycles
MCUCR = bit (BODS) | bit (BODSE);
// The BODS bit is automatically cleared after three clock cycles
MCUCR = bit (BODS);

// We are guaranteed that the sleep_cpu call will be done
// as the processor executes the next instruction after
// interrupts are turned on.
interrupts ();  // one cycle
sleep_cpu ();   // one cycle
}


//===================================================================
//Display text on screen fucntion - You can skip.

void displayText(String activityName)
{
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(10,0);
  display.clearDisplay();
  display.println(activityName);
  display.display();
  delay(200);
  display.clearDisplay();
  display.display();
}

//=========================================================================


//Button Varibale int

int buttonTwo = 8;

///////++++++++++++++++++++SETUP++++++++++++++++++++++///

void setup ()
{

pinMode(buttonTwo, INPUT_PULLUP);

//DISPLAY SETUP - You can skip

      // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  
}

//+++++++++++++++++++++++++++++LOOP+++++++++++++++++++++++++++//

void loop()
{

  buttonpress();


}

void buttonpress(){


  buttonState = digitalRead (buttonTwo);

    if ((millis() - lastDebounceTime) > debouncedelay) {

      if (buttonState == LOW) {
        ++debounceCounter111;

        if (debounceCounter111 > 3){
          debounceCounter111 = 1;
          lastDebounceTime = millis();
          sleep();
        }

        if (debounceCounter111 == 1){

          displayText(activity1);
          lastDebounceTime = millis();
          sleep();
        }

        if (debounceCounter111 == 2){
          displayText(activity1);
          lastDebounceTime = millis();
          sleep();
        }

        if (debounceCounter111 == 3){
          displayText(activity1);
          lastDebounceTime = millis();
          sleep();
        }
      }
    }
  Serial.println(buttonState);
}

Please try to describe what you do, and why you do it, then the resulting behaviour should become clear. Without such a description it's hard to find out, how e.g. wake() is ever activated. Or how some other interrupt may cause a wakeup of the controller. Don't expect that anybody will wade through all your code, in order to find out where something might go wrong.

For a first test disable all code in sleep(), and remove all code from the button ISR. You can set a global flag from the button ISR, so that you can find out later which interrupt has caused a wakeup.

Then verify that your code does what you want, without going to sleep in between. I suspect that your problem resides in the conditional blocks containing the sleep calls, which also update lastDebounceTime.

I'd try to replace the many sleep() calls by one, after all other button handling, so that you can be sure what the controller does immediately after wakeup. In a later version of your sketch more things may have to be done, like waiting for Serial output finished, before you put the controller asleep.

BTW the Serial output most probably is also interrupt driven, so that there can exist many reasons for a controller wakeup, besides the button ISR.

DrDiettrich:
Please try to describe what you do, and why you do it, then the resulting behaviour should become clear. Without such a description it's hard to find out, how e.g. wake() is ever activated. Or how some other interrupt may cause a wakeup of the controller. Don't expect that anybody will wade through all your code, in order to find out where something might go wrong.

For a first test disable all code in sleep(), and remove all code from the button ISR. You can set a global flag from the button ISR, so that you can find out later which interrupt has caused a wakeup.

Then verify that your code does what you want, without going to sleep in between. I suspect that your problem resides in the conditional blocks containing the sleep calls, which also update lastDebounceTime.

I'd try to replace the many sleep() calls by one, after all other button handling, so that you can be sure what the controller does immediately after wakeup. In a later version of your sketch more things may have to be done, like waiting for Serial output finished, before you put the controller asleep.

BTW the Serial output most probably is also interrupt driven, so that there can exist many reasons for a controller wakeup, besides the button ISR.

Thank you so much for your help with this DrDiettrich! Sorry for not including what is going on in my code. Here is the explanation:

The code is designed to be a test for a larger project, an arduino wrist watch. Right now, the code does the following. It starts the arduino and sends it to sleep, and when I push a button it wakes it up, displays a text on an OLED display that is attached, and then goes to sleep again.
Further inner working of the code are:

  • The Adafruit OLED library is incuded, as well as the sleep library and the Pin Change interrupt library.
  • There is debouncing done in the Interrupt button and also on the button that triggers the display.
  • the wake() function dictates what is done when the Interrupt is triggered.
  • the sleep() function is called just after the display is used.
  • There is an if statement that cycles through three different texts that can be displayed (this has all been reduced to one text to make it easer.

Upon further testing I can see that the Arduino is woken up if I released the button after it has slept. I have semi solved the problem by using a while (digitalRead(button) == 0), so that it only executes sleep when it has been depressed. However, I would still like to find the root cause of the problem and why it is triggering it when it is set to a falling edge interrupt.

You failed to explain many things, which are or are not done in code. E.g. why pinMode() is called in sleep(), or sleep_disable() in wake(), and only if the interval is greater than 200ms.

If some interrupt occurs, the controller wakes up, the button is checked and, if pressed, the counter will increment.

As already mentioned, make your code work first, without sleeping.