ATMEGA328PB wake with button & interrupt

Hello! I am working on a project which is going well enough, but my current roadblock has me stymied. I am trying to make a blinky conference badge (LEDs) that can turn off (and back on, but it's processor sleep really) with a button (save battery power I figure). I am using an interrupt because that seems easiest in terms of the programming (except I can't make it work yet).

The badges do stop blinking (sleep) when the button is pressed, so I am feeling confident that the interrupt and the button as it is wired do indeed work. However, I can't get the LEDs to start blinking again despite having read (and tried) many potential solutions. So either the interrupt isn't working when the CPU is sleeping, or something else (like I've coded it wrong).

I'm using the Arduino IDE 2.3.5, with an ATMEGA328PB on a custom board. For the recent try (for the code I will post here), I am using PinChangeInterrupt (but I think it was sleeping with the regular library, but that isn't really relevant I don't think). My longer code with various patterns compiles and uploads and works great (I have a second button to change the mode, I have taken all that out of the minimal code example here and made sure to test this minimal code example, which does blink the one LED and turns it off but not back on, so sleep I assume).

Some examples I have found suggest just attaching the interrupt for the wake-up, but it makes my code so much easier to just have the interrupt all the time (I mean I could make that work, and had it that way for a while, but had the same problem where the LEDs would turn off as expected but then wouldn't come back on).

I am sure there are some relevant bits I have neglected to include. The below code does indeed blink the LED, then the on/off button when pressed makes that LED stop blinking, but then pushing the button again appears to have no effect. In the code example, I use an on/off boolean as was suggested in two posts I found. I've tried over-doing the wake up but that isn't working either. (Is this a bounce problem? Doesn't "Falling" get around that? Or, something about interrupts for the 328PB when sleeping?) (Oh right, I have the button wired to a PCINT pin because, well, the board layout I ended up with LEDs on the regular interrupt pins and only later learned about PCINT vs. INT but it should work, and I say that full well knowing "but it should work" is often the sign of the newbie making the obvious mistake, because clearly there is a mistake here somewhere and I am the one making it. What I mean is, that's why I'm using the PinChangeInterrupt.h library, to make sure I can access the PCINT pin that the button is connected to as an interrupt.)

Any guidance is appreciated. (I can probably upload the schematics just that doesn't appear as easy as including the code, but the button does appear to activate sleep, so I assume it's working but hey, assumptions. I have not fully cleaned up the comments in the code but you can ignore them.)

// Minimal example of awaken not working.
// Include Libraries
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/io.h>
#include <PinChangeInterrupt.h>

// Global Variables
const int led_yellow = PD2; // ATMega328PB pin references.

// Different online schematics show different pin desginations. Beware!
const int button_onoff = 8; //12;  PB0

volatile bool isSleeping = false;  // volatile maybe? since it is changed in the ISR

// before setup
void changeSleep() {
  if (!isSleeping) {    // if awake
    isSleeping = true;
    digitalWrite(led_yellow, LOW);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    sleep_mode(); // MCU sleeps here
    sleep_disable(); // Disable sleep after waking
    isSleeping = false;
  }
  else {
    sleep_disable(); // Disable sleep after waking
    isSleeping = false;
  }
}


void setup() {
  pinMode(led_yellow, OUTPUT);
  pinMode(button_onoff, INPUT_PULLUP); // the PULLUP is probably redundant but I tested it and it doesn't hurt.
  attachPCINT(digitalPinToPCINT(button_onoff), changeSleep, FALLING); // yes in setup
}


void loop() {
  blink_one();
} // main loop


// If the LEDs are on turn them off and vice-versa, for the blink pattern.
void reverse_led() {
  if (digitalRead(led_yellow) == HIGH) digitalWrite(led_yellow, LOW);
  else digitalWrite(led_yellow, HIGH);
} // reverse_all

void blink_one() {
  unsigned long start = millis();
  digitalWrite(led_yellow, HIGH);
  while (true) {
    if (millis() - start >= 20) {   // beta timing for board
      reverse_led();  //
      start = millis();
    }
  } // while
} // blink

I will read your code when I have stopped moving.

In the meantime, I hope you have read and understood this expert coverage of the matter:

Try writing the simplest fall asleep/wake up sketches possible.

Do you have a good current meter for verifying the soundness of the sleep? Designs are comprised by components or conditions that result in relatively high current during sleep.

I had to buy a better DVM when the low-power bug bit me.

a7

I think your program structure is incorrect.

Somewhere in the loop() you have to test a button and put the device to sleep if a button press has been detected. The program will stop at point that sleep_mode() is called and resume there when an interrupt is detected. You seem, however, to have that logic in changeSleep() which is the ISR called on wakeup.

If the device is in sleep mode and the button defined in setup() is pressed, the device will then wakeup.

Your routine changeSleep() should, as far as I see, actually be empty, although it must exist.

EMOJI:_good_advice (but I can't find the icon) :+1:

For PWR_DOWN mode, only INT0 and INT1 can wake the processor, not any pin change. See note 3

I don't think you are interpreting the table correctly. Note 3 refers to a restriction on the function of INT1 and INT 0 but has no impact on pin change interrupts.

EDIT
Here is more on the subject but specifically refers to an ATMEGA328P with lots of anecdotal evidence and tales of data sheet errors, if you follow the chain: https://stackoverflow.com/questions/65428299/atmega328p-wake-up-from-power-down-mode-using-a-edge-triggered-interrupt

good to know, thanks!

I would rework the sketch to stay within what I have made work.

Use a real interrupt (pin 2 or 3 I think) to wake up from sleep.

Detach the interrupt. Operate normally.

Use regular button handling to see if the button is pressed during normal operation. Use a button press to go back to sleep. Attach the wake-up interrupt handler as you drift off.

You could also mark time and go back to sleep after 60 seconds or whatever.

Your always attached toggle interrupt looks plausible, but is enough different to make me think it isn't operating correctly. To fix it, assuming it can be, would make more trouble than just following known good patterns.

a7

I didn't know about INT vs. PCINT pins until now, and the PCB wiring has the button connected to one of the PCINT pins, so I can't change that at the moment.

In my initial tries, I had the button use a regular push (not an interrupt) to go to sleep (the LEDs turned off, so I think that worked), and I had the code attach the interrupt just before sleep (the interrupt on the same button to wake it up), but I could not get that to work (so eventually I tried the above approach which is also not working).

changeSleep() which is the ISR called on wakeup

I thought changeSleep() would be called when the interrupt occurs (that is, when the button is pressed), regardless of whether or not the CPU is in a sleep mode? (So, it's an interrupt, not a regular button press, so I don't need to look for regular button presses on this button.)
I do feel this is part of the problem, I am trying to use sleep and also interrupts together, which should work but appears to be adding extra complexity.

There was a time not so long ago when making PCBs was somewhat harder, and it was rare that a first print of a new design would come back and be immediately functional.

Along with flaws in the execution, circuit errors discovered after printing had to be accounted for.

A razor knife was an essential tool.

a7

1 Like

That is awesome! It requires a zen-like approach, accept your mistakes (that you have embodied into the PCB) and do the best you can to fix them.

I don't have an EE background, but I do computational social science (so Python and R), mostly without event loops. I have gotten some help from an uncle who does PCBs (but we live in different US states, not nearby) and got the mode button and LEDs working on a breadboard with an Uno, but not the power button. I went ahead and had the PCB printed up and knew there would be a high chance of errors. A learning process! Learning is not bad.

@natpoor
Still need help with pinchange on interrupt?
It's not that difficult.

I was going to wait a day or two and see what replies rolled in, then try to incorporate the wisdom in my head and see what can be done. So I haven't actually tried new code just yet, but think I might try a loop() with a simple delay-blink in it, and maybe just use a timer to put it to sleep, and then have the button only (try to) wake it up via interrupt. So, try to make even simpler code than I have currently (as suggested).

Well here is something to look at

/*
This program shows how to put the an Uno to sleep and wake it 
using the Pin Change Interrupt that is available on any digital I/O pin
The program does not use any third party libraries
*/
#include <avr/sleep.h>
#include <avr/interrupt.h>


//-----------------------------------------------------------
// The ISR
ISR(PCINT0_vect) {
	PCICR &= ~_BV(PCIE0);  // Disable the PCINT[7:0] interrupts
}
//-----------------------------------------------------------

void setup(void) {
	Serial.begin(115200);
	// Use D8 as the interrupt input pin
	pinMode(8, INPUT_PULLUP);        // Set D8 as input with pullup
	pinMode(LED_BUILTIN, OUTPUT);    // Set the LED as output
	digitalWrite(LED_BUILTIN, LOW);  // OFF
	// Turn off the ADC to save some power if you don't use it
	PRR = 0x01;
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // Set sleep mode to full power down.
	Serial.println(F("Setup complete"));
}

void loop(void) {
	//##############################################################
	// Your stuff goes here, between the arrows
	//--->
	// This is just dummy stuff for testing
	// You can delete everything between the arrows
	Serial.println(F("Working"));
	static int count = 0;
	count++;
	//Blink the LED
	for (byte i = 0; i < 20; i++) {
		digitalWrite(LED_BUILTIN, HIGH);  // LED On
		delay(100);
		digitalWrite(LED_BUILTIN, LOW);  // LED Off
		delay(100);
	}
	Serial.print(F("Sleeping  "));
	Serial.println(count);
	Serial.flush();  // Ensures Println finishes before we go to sleep

	//<---
	// end of your stuff
	//##############################################################

	//==============================================================
	// Start of going to sleep code
	// This code should go at the very end of loop(void)
	PCIFR = 0x07;            // Clear all the change on interrupt flags
	bitSet(PCICR, PCIE0);    // Enanble change interrupts on PCINT[7:0] pins
	bitSet(PCMSK0, PCINT0);  // Enable for PCINT0 (D8)
	interrupts();            // Make sure interrupts are enabled
	sleep_bod_disable();     // Disble BOD; saves power
	sleep_mode();            // Enables sleep, puts cpu to sleep and disbles sleep when awoken

	// We are now ASLEEP, will stay here until interrupt occurs.
	//ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ

	// Now we are awake
	Serial.println(F("Awake"));
	//==============================================================
}

If people are curious, it's for a conference badge project (for June 2026), for the Games division pre-conference, so I am making it look like a game console controller (the LEDs are smaller than I would like but it's fine, and the buttons should be black and the coin battery holder was installed the wrong way which I didn't expect since they did it correctly the first two runs). I am a Mac person (NeXT!), with the Arduino IDE, KiCad for the schematics, and Tag Connect for the cable from an Uno as the programmer. I was a computer science minor in college (a long time ago), use Python and R now, and built a PC from parts once, but wow, assembling components to make hardware!!! So many moving parts!!! Difficult and awesome. (So, the Mode button works fine, but it's the Power button that I am dealing with here.)

I used @jim-p 's sleep magic (THX!) largely unchanged and modified the example to

  • provide a free running loop
  • sleep at task completion or on button pressed
  • wake up by button

The only sleep-relevant change was to delete the line where global interrupts are enabled, as they are always enabled. Anyone who turns them off should turn them back on.

Other efforts showed that switch bouncing is playing a roll here, and my undisciplined hacking at the issue failed to result in a good way to handle it. So in two places a very crude debouncing is coded.

/*
button or task complete -> go to sleep
button during sleep -> wake up
*/

# include <avr/sleep.h>
# include <avr/interrupt.h>

# define ledPin     2  // activity LED
# define buttonPin  8  // Use D8 as the interrupt input pin

ISR(PCINT0_vect) {
 PCICR &= ~_BV(PCIE0);  // Disable the PCINT[7:0] interrupts
}

void setup(void) {
 Serial.begin(115200);

 pinMode(buttonPin, INPUT_PULLUP);        // Set D8 as input with pullup
 pinMode(ledPin, OUTPUT);    // Set the LED as output

 digitalWrite(ledPin, LOW);  // OFF

 // Turn off the ADC to save some power if you don't use it
 PRR = 0x01;
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // Set sleep mode to full power down.

 Serial.println("Setup complete");
}

void loop(void) {
 bool sleepy = false;  // unless
 bool pressed;

 static int count = 0;
 static unsigned long timer;
 unsigned long now;

 now = millis();

 if (now - timer >= 77 && !sleepy) {
   timer = now;
   count++;
   digitalWrite(ledPin, digitalRead(ledPin) == HIGH ? LOW : HIGH);  // LED On

   if (count >= 50) {
     Serial.print("done for now - ");
     sleepy = true;
     count = 0;
   }

   pressed = digitalRead(buttonPin) == LOW;
   if (pressed) {
     Serial.print("button induces sleep - ");

     count = 0;  // for next wakey wake time

     do {delay(20);}
       while (digitalRead(buttonPin) == LOW);   // wait for the fat finger to get off the button

     sleepy = true;
   }
 }

 if (!sleepy) return;

// the rest is going to sleep, being asleep and waking up from sleep

 digitalWrite(ledPin, LOW);
 Serial.println("going to sleep");
 Serial.flush();

 PCIFR = 0x07;            // Clear all the change on interrupt flags
 bitSet(PCICR, PCIE0);    // Enanble change interrupts on PCINT[7:0] pins
 bitSet(PCMSK0, PCINT0);  // Enable for PCINT0 (D8)
 sleep_bod_disable();     // Disble BOD; saves power
 sleep_mode();            // Enables sleep, puts cpu to sleep and disbles sleep when awoken



 // Zzzz. We are now ASLEEP, will stay here until interrupt occurs.



 Serial.println("\nwaking up...");

 do {delay(20);}
   while (digitalRead(buttonPin) == LOW);   // wait for the fat finger to get off the button

 Serial.println("              now awake");
}

Note: I had no joy using the wokwi simulator; at this time I can't say it doesn't do sleep correctly for the UNO, but that would cover the facts.

a7

IT...... WOKE UP!!!!!! :heart_eyes:
(Ok now I have to figure out how, exactly, this code differs from mine, and why mine didn't work, and how to incorporate this into my code... which is a totally fun thing to do.)
One thing I will note, is that I assumed the ... well the setting of the pins and the low-level calls, I thought (having seen them in some various answers around the web but posted on many different years) they were probably encapsulated into the newer versions of the libraries so that I didn't need to worry about them, and I could just use the current interrupt library or one of the libraries that explicitly controlled the PCINT pins in the ATMEGA328PB, and just make simpler calls. (This was a bad conclusion to make, apparently.)
Amazing!!!!! Thank you!!!!!

(So excited, this PCB will work as planned eventually!!!)

I will also check out the changes you have made here to that code! @jim-p 's code does seem to work as far as I can tell, which is very exciting!

I wasn't sure the Arduino forum was quite the right place to ask my question, since maybe it wasn't an Arduino/C++ code problem (a problem made by me), maybe I made a hardware error in the schematic, but hey this is awesome! Thank you all!

Also, thank you both for the COMMENTS in the code, @jim-p and @alto777 (and clearly you both understand why).
When I was in college, we had a professor who graded us not just on the code but on the comments as well. Being young (~1990), and on a VAX/VMS and only on BITNET, we didn't really get it (probably using Pascal), but I picked up the habit. Many years later, I was re-using some R code from the year before, and was really helped by my comments. That was when the lightbulb went on about good commenting practice.

Glad I could help.
That code was for someone else on the forum that was also haveing problems with sleep.
Have a nice day!