Interrupts and AVR Wake-Up Issue

Hi there, I have a push-button for my Arduino Uno that's normally pulled high (internal pull-up) and when I push the button I need the it go to sleep, and wake up when I push it again. Pretty simple, and I have already done that with this sample code:

#include <avr/sleep.h>
#include <avr/power.h>
#include <PinChangeInterrupt.h>

#define LED 13
#define BUTTON A1 // Main power button (toggle device on/off)

volatile bool buttonPressed = false;
volatile bool MCU_turnedOn = false;
unsigned long previous = 0;

void setup() {
  Serial.begin(115200);
  
  pinMode(LED, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);

  attachPCINT(digitalPinToPCINT(BUTTON), toggle, FALLING);
}

void loop() {
  if (MCU_turnedOn) { // Blinks LED 3 times when MCU wakes up
    for (int i=0; i<3; i++) {
      digitalWrite(LED, HIGH);
      delay(50);
      digitalWrite(LED, LOW);
      delay(50);
    }
    MCU_turnedOn = false;
  }
  
  digitalWrite(LED, HIGH); // Normally LED is on

  if (buttonPressed) {
    MCU_powerDown();
  }
  
  delay(50);
}

void MCU_powerDown() {
  digitalWrite(LED, LOW); // Turn off LED before sleeping

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0; // Turn off ADC
  power_all_disable ();  // Power off ADC, Timer 0 and 1, serial interface
  noInterrupts();
  sleep_enable();
  attachPCINT(digitalPinToPCINT(BUTTON), wakeUp, FALLING);
  interrupts();
  sleep_cpu();

  // Code resumes here after wakeup
  sleep_disable();
  power_all_enable(); // Power everything back on
}

void wakeUp() {
  buttonPressed = false;
  MCU_turnedOn = true;
  
//  PCIFR = (1<<PCIF1); // Clear pending flag for PCINT1_vect
  attachPCINT(digitalPinToPCINT(BUTTON), toggle, FALLING); // Re-attach toggle ISR instead of in loop()
}

void toggle() {
  delay(10); // Debouncing
  if (!digitalRead(BUTTON)) { // If state is still low, then button was pressed for sure
    buttonPressed = true;
  }
}

This test code runs just fine, and I intentionally made it mimic my actual code structure where I check for the flag "buttonPressed" and go directly to sleep when it's true.

Now for the weird part: In my actual code (with a lot more intensive while loops, basically sending AT commands and getting a reply), when I press the button it does indeed go to the sleep function, but instead of actually sleeping it somehow wakes up directly after, thus making it seem like it did nothing.

This seems to indicate that the wakeUp() ISR is being triggered by something like the release of the button, but why would it do this in my actual code vs the test code?

I've been pulling my hair out trying to figure out this issue for the past couple days and can't seem to get this resolved. Again, it's indeed reaching the MCU_powerDown() function, but it's waking up directly after. My actual code involves a while loop to receive a response from an AT command that is sent via Software Serial, but within that while loop it checks for "buttonPressed" and if true, it calls MCU_powerDown(). So the real issue here (at least I think) is preventing the wakeUp() function from falsely triggering.

Any ideas? Thanks!


EDIT: if you're really interested in seeing my code, I'm using this library and in the Adafruit_FONA.cpp at line 1762 I am inserting this code:

if (buttonPressed) {
  buttonPressed = false; // Reset flag so it won't trigger multiple times
  MCU_powerDown();
}

then in my loop I have this:

if (!fona.begin(*fonaSerial)) { // This runs the function at line 41 of Adafruit_FONA.cpp
    Serial.println(F("Couldn't find FONA"));
  }

  delay(100);

This basically runs setup over and over. Of course I wouldn't do this in my actual code but I am using this to test the concept. The function fona.begin() in line 41 of the .cpp will be interrupted whenever I press the button, and it jumps directly to my MCU_powerDown() function. However, it seems to wake up immediately after trying to sleep...

Buttons bounce - how do you deal with that ? What does delay() do within an interrupt?

Yes, I understand that putting a delay inside an ISR isn't good. However, I'm doing something where I need to not only turn off the MCU but also a GSM unit by pulsing a pin to GND for at least half a second. How do I do something like that without putting a delay within the ISR?

Debounce in hardware for example and set a flag and handle at Next spin of the loop() ? (Ior f you put to low and go to sleep and don't have bounces how likely is it that the pin will go back high?)

Hmmmm I actually found out that it wasn't my code per se, but it was SoftwareSerial. Somehow when I take out the mySerialName.begin(4800), the test code works perfectly, but when I put it back in, when I hit the button it will randomly trigger multiple times after the first click.

What is going on? I made sure that SoftwareSerial and the PinChangeInterrupt libraries are using different interrupt ports.

Are you sending at commands in the interrupt?

No, I'm not doing anything questionable in the ISR. I found out that actually I have to use mySoftwareSerial.end() before I sleep the MCU otherwise it seems to wake back up after going to sleep. This seemed to fix the issue.

Good catch then!

Thanks! Although it now sleeps fine, it seems to take a while to boot up (like at least 10s before it will blink an LED at the top of my loop(), indicating that it's on). Seems like I need to clear the serial buffer before it sleeps because when I wake it up it seems to spit out a lot of serial print that would have been printed if it were to remain on, even though I end software serial before sleeping. The GSM module does take several seconds to sleep but I would expect all the serial stuff to be cleared after ending it, right?

Hmmm I think it's actually trying to run other stuff at the time I tell it to go to sleep, so when it powers on it tries to finish the loop it's in and therefore it only goes to the top of loop() after it's done with whatever loop it was stuck in. Any ideas on how to break away from any and every loop after pressing the button, then and only then call the sleep function?

androidfanboy:
Hmmm I think it's actually trying to run other stuff at the time I tell it to go to sleep, so when it powers on it tries to finish the loop it's in and therefore it only goes to the top of loop() after it's done with whatever loop it was stuck in. Any ideas on how to break away from any and every loop after pressing the button, then and only then call the sleep function?

there is only one thread, there is only one loop(). you call sleep and it does that.

you could always put the sleep function at the end of loop(). FYI, you can wait (read "block") for serial to get sent using flush().

Well so actually I have one main function from the Adafruit library called "getCheckReply" which basically sends an AT command waits for a response with a specified timeout, and my loop() code uses this function pretty much exclusively. Therefore, inside the while() loop that is in "getCheckReply" I check for the "buttonPressed" flag and if true, I call MCU_powerDown(). For this reason I can't just check for "buttonPressed" at the end of loop() because it will be much too slow. Rather, I just interrupt it in the middle of the main function I am using, and it works. However, it seems to wake up and try to complete that loop that it's in, which is super annoying.

If you go to this library that I am using, I am inserting this code at line 1762:

if (buttonPressed) {
      buttonPressed = false;
      Serial.println(F("BUTTON PRESSED"));
    MCU_powerDown();
    }

Once buttonPressed = true it will power down the MCU, and that part works fine. The issue is when it wakes up and tries to finish the "getline()" function it's in. I'm just not sure how to exit out of all the loops while still running MCU_powerDown() because this function is in a library. It would be much too slow to check for this in my main "void loop()"

androidfanboy:
Well so actually I have one main function from the Adafruit library called "getCheckReply" which basically sends an AT command waits for a response with a specified timeout, and my loop() code uses this function pretty much exclusively. Therefore, inside the while() loop that is in "getCheckReply" I check for the "buttonPressed" flag and if true, I call MCU_powerDown(). For this reason I can't just check for "buttonPressed" at the end of loop() because it will be much too slow. Rather, I just interrupt it in the middle of the main function I am using, and it works. However, it seems to wake up and try to complete that loop that it's in, which is super annoying.

knocking yourself out of that function so indelicately is your prerogative.

since your getCheckReply() function is blocking, you could just use an interrupt with a flag that waits until the response from the AT request is complete. Then, when control returns to loop() it checks for the flag and sleeps accordingly.

Edit: this is one of those cases (your code has some un-blockable bits) where using an interrupt is better than polling.

So it's impossible to get the wakeUp() ISR to start at the top of the main loop()?

androidfanboy:
So it's impossible to get the wakeUp() ISR to start at the top of the main loop()?

why would you want to?

your Serial needs housekeeping , you are leaving behind a mess...

wouldn't you rather finish up the task at hand?

When I go to sleep I actually turn off serial and do housekeeping, then sleep the MCU. I would like to just quit everything I'm doing without even finishing it up, because it would be a waste of time to finish it anyway.

I also tried putting this code directly after the code I inserted at line 1762:

if (MCU_turnedOn) break;

and setting "MCU_turnedOn" to true right when the MCU turns on. Theoretically this would mean that right when the MCU boots up it will start at that line of code, break out, and run the top of loop() again, but this doesn't work. I also tried just putting "break;" right after my original code (directly after MCU_powerDown() so that when it wakes up it breaks out of the while loop and runs main loop() but that didn't work either.

No the function returns to the caller

Yes, and the caller is the function "readline()" at around line 1762 where I injected the code. So shouldn't it resume there? From my observations it's finishing the readline() function before returning to the top of loop().