ATtiny84 Wake Up From Sleep (Interrupts)

Hi All,

This is my first post on the forum, so bear with me. I have read through many many posts on this forum about ATtiny and interrupts but could not find a solution.

Right now I have an ATtiny84 that flashes an LED during normal operation (loop) and a switch on the RESET pin (physical pin 4) toggles the power state in/out of sleep mode. This works great, but I want to also be able to wake up the ATtiny84 from a change of state on one of the pins (let’s say PCINT1, or physical pin 12). The issue I think I am having is that the ISR doesn’t run when the ATtiny is sleeping, so it doesn’t do anything. I have also read Nick Gammon’s code here: Gammon Forum : Electronics : Microprocessors : Interrupts

but I don’t want the ATtiny to sleep after blinking the LED once; I want the code in loop() to run constantly until the ATtiny is turned off, then wake it up via a change of state on the interrupt pin. Also, if the ATtiny is already on, the interrupt should have the ATtiny remain on (and not toggle it off).

This is the code I am using right now:

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

#define LED 10  // Physical pin 2 on ATtiny84
#define SWITCH 1  // Physical pin 12

int Power __attribute__ ((section (".noinit")));

void PowerDown() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0; // Turn off ADC
  sleep_enable();
  sleep_cpu();
}
  
void setup() {
  Power = !Power;
  if (!Power) PowerDown();

  pinMode(SWITCH, INPUT_PULLUP);  // Connect switch to GND to run ISR
  pinMode(LED, OUTPUT);
  
  attachPCINT(digitalPinToPCINT(SWITCH), wakeUp, CHANGE);  // Pin change interrupt
}

void loop() {
  digitalWrite (LED, HIGH); // Flash LED during normal operation
  delay(250);
  digitalWrite (LED, LOW);
  delay(250);
}

void wakeUp() {
  // I want to wake up the ATtiny84 when SWITCH is momentarily connected to GND
}

Any help would be much appreciated!

I would recommend not using the PinChangeInterrupt library and just doing it by hand. I get a bad feeling when I look over that library.

Doing it by hand is two lines of code, setting the PCMSKn register, and enabling PCINTs in the global interrupt register, and an empty ISR.

Why is Power in .noinit? I don't really know what that does on AVRs. Maybe it shouldn't be? Your code depends on power being false on startup, otherwise it will sleep before it's set up the interrupt.

Why are you setting up the interrupt so late? Set it up before the first time you consider sleeping.

Why are you checking a variable in setup like that? Setup only runs once...

Thanks for the reply! The “.noinit” part is so that when the ATtiny restarts via the reset pin it doesn’t initialize the state of “Power” to true or false. This is needed because if I initialize Power to true or false, whenever I hit the reset button it will never power down.

As far as the pinChangeInterrupt library, I tested the hard-coded version before and it works exactly the same as if I used the library.

I changed my code and move the PinChangeInterrupt line before I set the sleep in setup(), but now whenever I hit the reset button the LED goes from normally flashing to solidly on until I press it again, after which it goes back to flashing.

This is my updated code with the line re-ordered:

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

#define LED 10  // Physical pin 2 on ATtiny84
#define SWITCH 1  // Physical pin 12

int Power __attribute__ ((section (".noinit")));  // Don't initialize "Power" variable on reset

void PowerDown() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0; // Turn off ADC
  sleep_enable();
  sleep_cpu();
}
  
void setup() {
  pinMode(SWITCH, INPUT_PULLUP);  // Connect switch to GND to run ISR
  pinMode(LED, OUTPUT);
  
  attachPCINT(digitalPinToPCINT(SWITCH), wakeUp, CHANGE);  // Pin change interrupt
  
  Power = !Power;
  if (!Power) PowerDown();
}

void loop() {
  digitalWrite (LED, HIGH); // Flash LED during normal operation
  delay(250);
  digitalWrite (LED, LOW);
  delay(250);
}

void wakeUp() {
  // I want to wake up the ATtiny84 when SWITCH is momentarily connected to GND
}

Assuming that my code works (and it does) for toggling the ATtiny on/off via the reset button, how would I go about implementing a wake-up via an interrupt?

Oh my god. I am horrified that that worked (your method using reset and noinit).

You talk about using the reset button here - why? You should be grounding the interrupt pin now that you're using PCINTs..

Also, setup() only runs once. If you're waking on an interrupt, execution continues where it left off (as opposed to your reset thing), so it would only sleep once at most. That's probably not the behavior you want.

I think these issues (remnants of your reset-button-controlled sleep mode) are what's causing your problems now.

OK, it sounds like this needs some overhaul then. It sounds like you’re suggesting to use 2 separate PCINT’s, 1 to control the power and another to wake up the ATtiny?

This code does just that, except the wake feature doesn’t work (I can’t wake it up at all after it goes to sleep). Also, How would I toggle the power with the “PWR” pin? I just can’t come up with a solution that toggles the sleep state on/off with the same button. I want the power button to only control sleep on/off, and the other button to wake the ATtiny up (or remain awake if the ATtiny wasn’t sleeping).

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

#define LED 10  // Physical pin 2 on ATtiny84
#define PWR 2 // Physical pin 11 to shut down ATtiny
#define SWITCH 1  // Physical pin 12 to wake up ATtiny

//int Power __attribute__ ((section (".noinit")));  // Don't initialize "Power" variable on reset

void shutDown() {
  digitalWrite (LED, LOW); // Turn off LED
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0; // Turn off ADC
  power_all_disable ();  // power off ADC, Timer 0 and 1, serial interface
  sleep_enable();
  sleep_cpu();
}

void wakeUp() {
  // I want to wake up the ATtiny84 when SWITCH is momentarily connected to GND
}
  
void setup() {
//  Power = !Power;
//  if (!Power) PowerDown();

  pinMode(PWR, INPUT_PULLUP);  // Connect switch to GND to run ISR
  pinMode(SWITCH, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  attachPCINT(digitalPinToPCINT(PWR), shutDown, CHANGE);
  attachPCINT(digitalPinToPCINT(SWITCH), wakeUp, CHANGE);
}

void loop() {
  digitalWrite(LED, HIGH);
  delay(250);
  digitalWrite(LED, LOW);
  delay(250);
}

In all the ATtiny wake up code I found online (including Nick Gammon’s), the line “shutDown()” at the end of the loop() function. This enables the interrupt to wake via the interrupt, but that means the code in loop() only runs once before it sleeps, which I don’t want.

The code below will sleep after blinking the LED once, and grounding the SWITCH pin wakes it up so that it blinks again and goes back to sleep:

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

#define LED 10  // Physical pin 2 on ATtiny84
#define PWR 2 // Physical pin 11 to shut down ATtiny
#define SWITCH 1  // Physical pin 12 to wake up ATtiny

//int Power __attribute__ ((section (".noinit")));  // Don't initialize "Power" variable on reset

void shutDown() {
  digitalWrite (LED, LOW); // Turn off LED
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0; // Turn off ADC
  power_all_disable ();  // power off ADC, Timer 0 and 1, serial interface
  sleep_enable();
  sleep_cpu();

  // This code runs after it wakes up from sleep
  sleep_disable();   
  power_all_enable(); // Power everything back on
}

void wakeUp() {
  // I want to wake up the ATtiny84 when SWITCH is momentarily connected to GND
}
  
void setup() {
//  Power = !Power;
//  if (!Power) PowerDown();

  pinMode(PWR, INPUT_PULLUP);  // Connect switch to GND to run ISR
  pinMode(SWITCH, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  attachPCINT(digitalPinToPCINT(PWR), shutDown, CHANGE);
  attachPCINT(digitalPinToPCINT(SWITCH), wakeUp, CHANGE);
}

void loop() {
  digitalWrite(LED, HIGH);
  delay(250);
  digitalWrite(LED, LOW);
  delay(250);

  shutDown();
}

Wait...

Can you go over the sleep and "power" behavior you want in more detail?

I can't figure out what the hell you're trying to do - you talk about powering it on/off as being different from sleep, but as far as I understood, you're not disconnecting power from the chip, you're just putting it to sleep and calling that mode "off" (which is common practice).

What's the difference between what the PWR switch is supposed to do and what SWITCH is supposed to do?

PCINT's only work natively when set to occur on change, you can't do a level triggered interrupt with PCINTs. If the PinChangeInterrupt library is doing something to approximate that functionality, so be it (though I can't imagine how they emulate a LOW level interrupt)

Sorry for not being too clear about that, but basically what I want to do is this:

  • "PWR": I want a momentary push-button that when pressed, the ATtiny goes to sleep. When pressed again, the ATtiny wakes up and runs the normal loop() code. I don't want the loop() to involve running something once, then going to sleep after that. Rather, I want the code to run indefinitely until the first push-button is pressed.

  • "SWITCH": I want another interrupt that will be sent a high or low signal based on an external chip or voltage line (think of something like HIGH = something else plugged in, LOW = not plugged in). Each time the state of this pin changes I would like to either wake up the ATtiny, keep it on if it's already on, or put it back to sleep. These operations would depend on the current power state of the ATtiny (sleeping or awake) as well as if the interrupt pin went from HIGH to LOW or vice versa. For now, let's just do a simple case where I want to wake up the ATtiny if the interrupt pin changes state, and remain awake if the ATtiny is already awake.

  • By "power" I did not mean physically disconnecting the power to the chip, just sleeping. Sorry for the confusion.

So basically 2 buttons, 1 controls sleep on/off and the other one controls sleep on/off as well, but is a separate interrupt pin with different logic. In the real setup, 1 is a physically push-button and 1 is a digital HIGH/LOW input from another chip.

Hope that makes things clearer.

OK, so what I really am doing in the physical setup is the attached picture. The 5V high/low is not momentary (it is either constantly high or constantly low, not just a pulse). I use a voltage divider to drop the 5V down to about 3V so it doesn’t fry the ATtiny, and the PWR pin is attached to a push button that controls sleep on/off.

So let’s just call the logic line “LOGIC” instead of “SWITCH” to make it more consistent with the schematic. I have updated the schematic to reflect this change.

EDIT: I also didn’t mean to put a 20k resistor for the LED, but you get the idea. Also, the chip is running at around 3.3V, not 5V (otherwise I wouldn’t need the voltage divider). But the 5V logic input and the ATtiny both share a common GND.

androidfanboy:
Hi All,

This is my first post on the forum, so bear with me. I have read through many many posts on this forum about ATtiny and interrupts but could not find a solution.

Right now I have an ATtiny84 that flashes an LED during normal operation (loop) and a switch on the RESET pin (physical pin 4) toggles the power state in/out of sleep mode. This works great, but I want to also be able to wake up the ATtiny84 from a change of state on one of the pins (let’s say PCINT1, or physical pin 12). The issue I think I am having is that the ISR doesn’t run when the ATtiny is sleeping, so it doesn’t do anything. I have also read Nick Gammon’s code here: Gammon Forum : Electronics : Microprocessors : Interrupts

but I don’t want the ATtiny to sleep after blinking the LED once; I want the code in loop() to run constantly until the ATtiny is turned off, then wake it up via a change of state on the interrupt pin. Also, if the ATtiny is already on, the interrupt should have the ATtiny remain on (and not toggle it off).

You should have all clocks shut off in sleep mode. This means you cannot detect pin changes. You can, however, detect level changes without a clock and that’s what you should do.

Here’s a piece of a program I wrote for an IR remote transmitter that sleeps until a button is pressed, then wakes up, sends the data and goes back to sleep. The code is for an AT Tiny85, but the idea is the same.

// INT0 handler - we arrive here when the button is pressed
// We use ATTiny85 pin 7 (PB2 / INT0) which doesn't need a clock to detect
ISR (INT0_vect)
{
    GIMSK &= ~(1 << INT0); // disable hardware INT0
    sleep_disable(); // prevent going back to sleep
}

void timerInit (uint32_t rate)
{
    cli(); // disable interrupts

////////////////////////////////////////////////////
////// pieces not related to sleep mode removed
////////////////////////////////////////////////////

    // shut off everything not used
    MCUCR |= (1 << PUD); // disable port pullups
    ADCSRA = 0x00; // disable ADC
    power_adc_disable(); // power down ADC
    power_timer1_disable(); // power down timer 1
    power_usi_disable(); // power down USI
    sleep_bod_disable(); // turn off brown-out detector
    set_sleep_mode (SLEEP_MODE_PWR_DOWN); // set CPU sleep mode

    sei(); // interrupts on
}

int main (int argc, char *argv[])
{
    uint16_t n; // generic

    timerInit (IR_FREQ); // setup 8 bit timer for IR carrier frequency

    while (1) {

        cli(); // disable interrupts

        GIMSK |= (1 << INT0); // enable INT0 to trigger active low LEVEL
        sleep_enable(); // enable CPU to be powered down

        sei(); // interrupts on

        sleep_cpu(); // power down cpu (all clocks stopped)

        ////////////////////////////////////////////////////////////////////////////
        ///////// cpu is now asleep - awaiting pushbutton LOW to fire INT0 /////////
        ////////////////////////////////////////////////////////////////////////////

        // code here runs each time the button is pressed

        ////////////////////////////////////////////////////////////////////////////

    }
}

DrAzzy:
Oh my god. I am horrified that that worked (your method using reset and noinit).

You talk about using the reset button here - why? You should be grounding the interrupt pin now that you're using PCINTs..

Also, setup() only runs once. If you're waking on an interrupt, execution continues where it left off (as opposed to your reset thing), so it would only sleep once at most. That's probably not the behavior you want.

I think these issues (remnants of your reset-button-controlled sleep mode) are what's causing your problems now.

Using reset is actually a simple and easy way to do a low power sleep mode project. Simply have the AVR do it's thing, then go to sleep - that's all it does. At each reset, it does it's thing, then goes to sleep. No pin change or level detection or even debounce required.

Thanks for the reply! The thing with my project is that I don't want it to go to sleep automatically. I want it to run the loop() code indefinitely until the button is pressed. Also, I want to be able to wake the device up from a logic HIGH from a pin, but I am not sure if that is possible because I read somewhere that only the INT0 pin can wake the ATtiny up from sleep mode. So I think what I want to do is literally impossible.

androidfanboy:
Thanks for the reply! The thing with my project is that I don't want it to go to sleep automatically. I want it to run the loop() code indefinitely until the button is pressed. Also, I want to be able to wake the device up from a logic HIGH from a pin, but I am not sure if that is possible because I read somewhere that only the INT0 pin can wake the ATtiny up from sleep mode. So I think what I want to do is literally impossible.

If you NEED to wake up the project with a logic high, why not just use a little BJT like a 2N3904? Stick like a 1K on the base, emitter to ground and collector to the INT0 pin. Apply 5V to the resistor, BJT turns on, pulls the collector low, triggers INT0?

Won't that do it?

I thought about that (actually I was thinking to use an N-MOSFET but same thing basically), but I would also need to use the INT0 pin as a button to toggle sleep/wake up. If I connect the BJT to INT0 then I won't be able to toggle power anymore. Also, I read that only a LOW logic state can make INT0 wake the ATtiny out of sleep. CHANGE won't work. I actually need it to wake up with a RISING condition so I might have to connect the MOSFET to switch INT0 to GND instead.

Is there a way I can use my original (the one that someone was "horrified" that it worked?) code with the reset button sleep toggle and implement INT0 to wake it up? That way I would have a physical button on the reset pin on the ATtiny, and the BJT/MOSFET wired up to INT0.