Issue : Interrupt button registering both interrupt and increment

Hi,
I am working a werable device with 3 buttons ( Menu / + / - ) each Menus / submenus trigger an action and then the UNO sleeps until an interrupt on pushbutton is triggered and the action linked to the menu/submenu is triggered again & sleep etc. etc.
The issue I have is that while the interrupt works, my code switch menu/submenu as well. (registering both interrupt and action assigned to the button).
I tried adding parameters in my WakeUp function to control if the interrupt is pressed = don't register button action but the arduino turns back to sleep and get stuck.

Working code so far.

Thanks friends

#include <Arduino.h>
#include <avr/sleep.h>//this AVR library contains the methods that controls the sleep modes
#define interruptPin 3 //Pin we are going to use to wake up the Arduino - PIN 2 and 3 on UNO

const byte MINUS = 5;
const byte PLUS = 6;
const byte MENU = 4;
const int led1 = LED_BUILTIN;


byte menu = 0;
byte nav1 = 0;
byte nav2 = 0;
unsigned long prevMillis = 0;
unsigned long currentMillis = 0;


void setup()
{
  // put your setup code here, to run once:
  pinMode(MINUS, INPUT_PULLUP);
  pinMode(PLUS, INPUT_PULLUP);
  pinMode(MENU, INPUT_PULLUP);
  pinMode(led1, OUTPUT);
  pinMode(interruptPin,INPUT_PULLUP);//Set pin d3 to input using the buildin pullup resistor

  Serial.begin(9600);
}

void loop()
{
  checkTimer();
  nav1 = buttonCheck(nav1, 1); 
  nav1 = constrain(nav1, 0, 3);
  switch (menu)
  {
  case 0: //home screen
    Serial.println(" MENU 0");
    break;
  case 1: 
    Serial.println(" MENU 1 ");
     if (menu == 1)
     {
submenu1();
    }
  
    break;
  case 2:
    Serial.println(" MENU 2 ");
    break;
  case 3:
    Serial.println(" MENU 3 ");
    break;
  default:
    break;
  }
}

float buttonCheck(float variable, float scale)
{
static byte prevplusButton;
static byte prevminusButton;
static byte prevmenuButton;
  byte plusButton = digitalRead(PLUS);
  byte minusButton = digitalRead(MINUS);
  byte menuButton = digitalRead(MENU);

if (plusButton != prevplusButton)
{
    if (plusButton == LOW)
    {
    Serial.println("PLUS PUSHED");
      prevMillis = currentMillis;
      variable = variable + scale;
    }
prevplusButton = plusButton;

}

if (minusButton != prevminusButton)
{
  if (minusButton == LOW)
  {
    Serial.println("MINUS PUSHED");
          prevMillis = currentMillis;
    variable = variable - scale;
    if (variable < 0)
    {
      variable = 0;
    }
    }
      prevminusButton = minusButton;
  }

  if (menuButton != prevmenuButton)
  {
    if (menuButton == LOW)
    {
      Serial.println("MENU PUSHED");
            prevMillis = currentMillis;
      menu = menu + scale;
        if (menu > 3)
    {
      menu = 0;
    }
    
    }
    prevmenuButton = menuButton;
  }
  return variable;
}

void checkTimer()
{
  currentMillis = millis();
  if (currentMillis - prevMillis >= 10000L)
  {
Going_To_Sleep();
  }
}


void subemenu1()
{
  nav2 = buttonCheck(nav2, 1); 
  nav2 = constrain(nav2, 0, 6);
  switch (nav2)
  {
  case 0: //home screen
    Serial.println(" 0 ");
    break;
  case 1:
    Serial.println(" 1 ");
    break;
  case 2:
    Serial.println(" 2 ");
    break;
  case 3:
    Serial.println(" 3 ");
    break;
      case 4:
    Serial.println(" 4 ");

    break;
      case 5:
    Serial.println(" 5 ");
    break;
      case 6:
    Serial.println(" 6 ");
    break;
  default:
    break;
  }
}



void Going_To_Sleep(){
    sleep_enable();//Enabling sleep mode
    attachInterrupt(1, WakeUp, LOW);//attaching a interrupt to pin d2
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);//Setting the sleep mode, in our case full sleep
    digitalWrite(led1,LOW);//turning LED off
    delay(1000); //wait a second to allow the led to be turned off before going to sleep
    sleep_cpu();//activating sleep mode
    Serial.println("just woke up!");//next line of code executed after the interrupt 
    digitalWrite(led1,HIGH);//turning LED on
}

void WakeUp()
{
    Serial.println("Interrupt fired!");
        sleep_disable();
    detachInterrupt(1);
}```

Are all pushbuttons wired to trigger interrupt pins?

What about the following approach?

Made a "isSleeped" flag in your software and enable it before going to sleep mode.
And change the button read function to skip the code of the button as long as the that flag is in effect.
After waking up with an interrupt, "when the interrupt pin returns to HIGH (a.k.a. the button is released)" software clears the that flag.
This causes the button function to regain its functionality and the project recognizes the button the next time the button is pressed.

Also, I think that if you use PCINT, can wake up from sleep with any your key (+/-/MENU) without preparing a dedicated pin for interrupts.

Yeah this is what I tried but cannot figure out why it's not working - I cannot get my head arround the fact that the main loop seems to be executed in parallel of the "Just woke up!" line in the Sleep function. does it has anything to do with the debouncing ? I am gonna check your other recommendation thanks:

void checkTimer()
{
  currentMillis = millis();
  if (currentMillis - prevMillis >= 10000L)
  {
    isSleep = 1;
Going_To_Sleep();
  }
}
if (menuButton != prevmenuButton && isSleep == 0)
  {
    if (menuButton == LOW)
    {
      Serial.println("MENU PUSHED");
            prevMillis = currentMillis;
      menu = menu + scale;
        if (menu > 3)
    {
      menu = 0;
    }
    
    }
    prevmenuButton = menuButton;
  }
void Going_To_Sleep(){
    sleep_enable();//Enabling sleep mode
    attachInterrupt(1, WakeUp, LOW);//attaching a interrupt to pin d2
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);//Setting the sleep mode, in our case full sleep
    digitalWrite(led1,LOW);//turning LED off
    delay(1000); //wait a second to allow the led to be turned off before going to sleep
    sleep_cpu();//activating sleep mode
    Serial.println("just woke up!");//next line of code executed after the interrupt 
    digitalWrite(led1,HIGH);//turning LED on
    isSleep = 0;
}

As shown below, in AVR, one instruction is always executed between interrupts.
This is the reason why the continuously occurring low level interrupt and main loop seem to be running at the same time.

https://www.microchip.com/content/dam/mchp/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf#page=24

When the AVR exits from an interrupt,it will always return to the main program
and execute one more instruction before any pending interrupt is served.

https://www.microchip.com/content/dam/mchp/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf#page=79

When the external interrupt is enabled and is configured as level triggered,
the interrupt will trigger as long as the pin is held low.


Also, I writing a code using PCINT, so please wait for a while.

I haven't tested it, does this work?

#include <avr/sleep.h>

const byte MINUS = 5;
const byte PLUS = 6;
const byte MENU = 4;
const int led1 = LED_BUILTIN;

byte menu = 0;
byte nav1 = 0;
byte nav2 = 0;
unsigned long prevMillis = 0;
unsigned long currentMillis = 0;

boolean isSleeped = false;

void setup()
{
  pinMode(MINUS, INPUT_PULLUP);
  pinMode(PLUS, INPUT_PULLUP);
  pinMode(MENU, INPUT_PULLUP);
  pinMode(led1, OUTPUT);

  /* Choose wake-up pin you needed */
//PCMSK2 |= 1 << MINUS; // MINUS key wakeup Disable, Please uncomment to Enable
//PCMSK2 |= 1 << PLUS;  //  PLUS key wakeup Disable, Please uncomment to Enable
  PCMSK2 |= 1 << MENU;  //  MENU key wakeup Enable

  Serial.begin(9600);
}

void loop()
{
  checkTimer();
  nav1 = buttonCheck(nav1, 1);
  nav1 = constrain(nav1, 0, 3);
  switch (menu)
  {
    case 0:
      Serial.println(" MENU 0");
      break;
    case 1:
      Serial.println(" MENU 1 ");
      if (menu == 1)
      {
        submenu1();
      }
      break;
    case 2:
      Serial.println(" MENU 2 ");
      break;
    case 3:
      Serial.println(" MENU 3 ");
      break;
    default:
      break;
  }
}

float buttonCheck(float variable, float scale)
{
  static byte prevplusButton;
  static byte prevminusButton;
  static byte prevmenuButton;
  byte plusButton = digitalRead(PLUS);
  byte minusButton = digitalRead(MINUS);
  byte menuButton = digitalRead(MENU);

  if (isSleeped) {
    /* Clear software sleep flag when all button were released */
    isSleeped = (boolean)!(plusButton && minusButton && menuButton);
  } else {
    /* Normally operation code */
    if (plusButton != prevplusButton)
    {
      if (plusButton == LOW)
      {
        Serial.println("PLUS PUSHED");
        prevMillis = currentMillis;
        variable = variable + scale;
      }
      prevplusButton = plusButton;
    }
    if (minusButton != prevminusButton)
    {
      if (minusButton == LOW)
      {
        Serial.println("MINUS PUSHED");
        prevMillis = currentMillis;
        variable = variable - scale;
        if (variable < 0)
        {
          variable = 0;
        }
      }
      prevminusButton = minusButton;
    }
    if (menuButton != prevmenuButton)
    {
      if (menuButton == LOW)
      {
        Serial.println("MENU PUSHED");
        prevMillis = currentMillis;
        menu = menu + scale;
        if (menu > 3)
        {
          menu = 0;
        }
      }
      prevmenuButton = menuButton;
    }
  }
  return variable;
}

void checkTimer()
{
  currentMillis = millis();
  if (currentMillis - prevMillis >= 10000L)
  {
    Going_To_Sleep();
  }
}

void submenu1()
{
  nav2 = buttonCheck(nav2, 1);
  nav2 = constrain(nav2, 0, 6);
  switch (nav2)
  {
    case 0:
      Serial.println(" 0 ");
      break;
    case 1:
      Serial.println(" 1 ");
      break;
    case 2:
      Serial.println(" 2 ");
      break;
    case 3:
      Serial.println(" 3 ");
      break;
    case 4:
      Serial.println(" 4 ");
      break;
    case 5:
      Serial.println(" 5 ");
      break;
    case 6:
      Serial.println(" 6 ");
      break;
    default:
      break;
  }
}

void Going_To_Sleep() {
  digitalWrite(led1, LOW);
  delay(1000);
  isSleeped = true; // Set software sleep flag
  PCIFR = 1 << PCINT2; //
  PCICR = 1 << PCINT2; // Enable PCINT2 (PORTD)
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();
  /* Zzz... */
  digitalWrite(led1, HIGH);
  Serial.println("just woke up!");
}

/* Wake up */
ISR(PCINT2_vect) // PCINT with PORTD
{
  sleep_disable();
  PCICR = 0; // Disable PCINT
}

EDIT:
Interrupts are picked up directly from the button pins, so you don't need to connect anything other than the three buttons.
There is a line to set which button can wake up from sleep.
For the time being, I enabled only the menu key.
If you uncomment, you should be able to wake it up with that button as well.

Hey Chris,

Thanks for your help really - I am just starting with interrupt and it looks more complicated than what I thought. I tried the code, the main program runs smoothly but when the UNO is asleep, the interrupt don't work - it is stuck asleep...

I have tried keeping the 3 buttons wired to the interrupt pin 3 (1) keeping the isSleeped flag : True before going to sleep.
When I fire the interrupt, the arduino starts and seems to keep the menu position (no increment) but it goes back to sleep and never wakes up.

The serial displays :
MENU 2
MENU 2
MENU 2
Interrupt fired!
just woke up!
MENU 2
Interrupt fired!

  if (menuButton != prevmenuButton && isSleeped == false)
  {
    if (menuButton == LOW)
    {
      Serial.println("MENU PUSHED");
            prevMillis = currentMillis;
      menu = menu + scale;
        if (menu > 3)
    {
      menu = 0;
    }
    
    }
    prevmenuButton = menuButton;
  }

  else {
    isSleeped = false;
  }

Hmm, Won't the code wake up from sleep?
AFAIK, PCINT can be usable for wake-up from power down sleep.
I'm away from my desk right now, so I promise to send another reply later!

Hey :grinning: I got it :exclamation:

It's not that he not awake from sleep.
When he tick on the 10-second with non-operate, it goes to sleep, but the timer value is not initialized!
In other words, he sleeping again right after wake up.

I added one line at checkTimer() function in above code.
please try it.

#include <avr/sleep.h>

const byte MINUS = 5;
const byte PLUS = 6;
const byte MENU = 4;
const int led1 = LED_BUILTIN;

byte menu = 0;
byte nav1 = 0;
byte nav2 = 0;
unsigned long prevMillis = 0;
unsigned long currentMillis = 0;

boolean isSleeped = false;

void setup()
{
  pinMode(MINUS, INPUT_PULLUP);
  pinMode(PLUS, INPUT_PULLUP);
  pinMode(MENU, INPUT_PULLUP);
  pinMode(led1, OUTPUT);

  /* Choose wake-up pin you needed */
//PCMSK2 |= 1 << MINUS; // MINUS key wakeup Disable, Please uncomment to Enable
//PCMSK2 |= 1 << PLUS;  //  PLUS key wakeup Disable, Please uncomment to Enable
  PCMSK2 |= 1 << MENU;  //  MENU key wakeup Enable

  Serial.begin(9600);
}

void loop()
{
  checkTimer();
  nav1 = buttonCheck(nav1, 1);
  nav1 = constrain(nav1, 0, 3);
  switch (menu)
  {
    case 0:
      Serial.println(" MENU 0");
      break;
    case 1:
      Serial.println(" MENU 1 ");
      if (menu == 1)
      {
        submenu1();
      }
      break;
    case 2:
      Serial.println(" MENU 2 ");
      break;
    case 3:
      Serial.println(" MENU 3 ");
      break;
    default:
      break;
  }
}

float buttonCheck(float variable, float scale)
{
  static byte prevplusButton;
  static byte prevminusButton;
  static byte prevmenuButton;
  byte plusButton = digitalRead(PLUS);
  byte minusButton = digitalRead(MINUS);
  byte menuButton = digitalRead(MENU);

  if (isSleeped) {
    /* Clear software sleep flag when all button were released */
    isSleeped = (boolean)!(plusButton && minusButton && menuButton);
  } else {
    /* Normally operation code */
    if (plusButton != prevplusButton)
    {
      if (plusButton == LOW)
      {
        Serial.println("PLUS PUSHED");
        prevMillis = currentMillis;
        variable = variable + scale;
      }
      prevplusButton = plusButton;
    }
    if (minusButton != prevminusButton)
    {
      if (minusButton == LOW)
      {
        Serial.println("MINUS PUSHED");
        prevMillis = currentMillis;
        variable = variable - scale;
        if (variable < 0)
        {
          variable = 0;
        }
      }
      prevminusButton = minusButton;
    }
    if (menuButton != prevmenuButton)
    {
      if (menuButton == LOW)
      {
        Serial.println("MENU PUSHED");
        prevMillis = currentMillis;
        menu = menu + scale;
        if (menu > 3)
        {
          menu = 0;
        }
      }
      prevmenuButton = menuButton;
    }
  }
  return variable;
}

void checkTimer()
{
  currentMillis = millis();
  if (currentMillis - prevMillis >= 10000L)
  {
    prevMillis = currentMillis; // Prevent sleeping loop @@@@@@ Must need ! I Added !!!
    Going_To_Sleep();
  }
}

void submenu1()
{
  nav2 = buttonCheck(nav2, 1);
  nav2 = constrain(nav2, 0, 6);
  switch (nav2)
  {
    case 0:
      Serial.println(" 0 ");
      break;
    case 1:
      Serial.println(" 1 ");
      break;
    case 2:
      Serial.println(" 2 ");
      break;
    case 3:
      Serial.println(" 3 ");
      break;
    case 4:
      Serial.println(" 4 ");
      break;
    case 5:
      Serial.println(" 5 ");
      break;
    case 6:
      Serial.println(" 6 ");
      break;
    default:
      break;
  }
}

void Going_To_Sleep() {
  Serial.println("I'm sleepy...");
  digitalWrite(led1, LOW);
  delay(1000);
  isSleeped = true; // Set software sleep flag
  PCIFR = 1 << PCINT2; //
  PCICR = 1 << PCINT2; // Enable PCINT2 (PORTD)
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();
  /* Zzz... */
  digitalWrite(led1, HIGH);
  Serial.println("just woke up!");
}

/* Wake up */
ISR(PCINT2_vect) // PCINT with PORTD
{
  sleep_disable();
  PCICR = 0; // Disable PCINT
}

Ahhh for f**** sake ! Thanks a lot man !!! It works super well now thank you so much for your help. One last thingy, can you please explain :

isSleeped = (boolean)!(plusButton && minusButton && menuButton);

If the Sleep routine is initiated (true) then "invert" the value of plusButton & Minus & Menu. Meaning when the pin turns HIGH = False ?

Thanks again !!

I'm glad you solved it!

I'm sorry for the strange code writing.

First, keep in mind that HIGH is represented by 1 and LOW is represented by 0.

By joining each pin with &&, the state in parentheses becomes 1 only when all the pins become 1.
In other words, if one pin is 0 at least, it will be 0 because AND does not hold.
Next, the logic is reversed because the condition in parentheses is set to NOT with !.

That is, only when all pins are HIGH, False is returned and the sleep flag is cleared.

All clear ! Thanks mate

Did the PCINT version which uses only three pins just for the buttons well it works? :wink:
Please check the post of solution if you like, It will be useful for those who searched later. :white_check_mark: