Skip to content

Instantly share code, notes, and snippets.

@JChristensen
Last active March 31, 2024 18:42
Show Gist options
  • Star 47 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save JChristensen/5616922 to your computer and use it in GitHub Desktop.
Save JChristensen/5616922 to your computer and use it in GitHub Desktop.
AVR microcontroller sleep demonstrations
Simple demonstrations of putting AVR microcontrollers to sleep in power-down mode,
which results in minimum current. Coded with Arduino IDE version 1.0.4 (and with
the Arduino-Tiny core for the ATtiny MCUs, http://code.google.com/p/arduino-tiny/)
For ATmega328P, ~0.1µA.
For ATtinyX5 revisions that implement software BOD disable, ~0.1µA,
for ATtinyX5 revisions that don't, ~20µA.
/*----------------------------------------------------------------------*
* Sleep demo for ATmega328P. *
* Wire a button from digital pin 2 (INT0) to ground. *
* Wire an LED with an appropriate dropping resistor from pin D13 to *
* ground. *
* Pushing the button wakes the MCU. *
* After waking, the MCU flashes the LED, then waits 10 seconds before *
* going back to sleep. *
* *
* Jack Christensen 07May2013 *
* *
* Tested with Arduino 1.0.5 and an Arduino Uno. *
* Test conditions for all results below: *
* 5V regulated power supply, fed to the Vin pin *
* 16MHz system clock *
* Fuse bytes (L/H/E): 0xFF / 0xDE / 0x05 *
* Optiboot bootloader *
* *
* Uno R1 *
* 38mA active, 26mA with MCU in power-down mode. *
* *
* Uno SMD edition *
* 42mA active, 31mA power-down. *
* *
* Adafruit Boarduino *
* Power select jumper set to "USB", USB (FTDI) not connected. *
* 15mA active, 3mA power-down. *
* *
* Adafruit Boarduino without power LED *
* 12mA active, 0.1µA power-down. *
* *
* Breadboarded ATmega328P-PU *
* 12mA active, 0.1µA power-down. *
* *
* This work is licensed under the Creative Commons Attribution- *
* ShareAlike 3.0 Unported License. To view a copy of this license, *
* visit http://creativecommons.org/licenses/by-sa/3.0/ or send a *
* letter to Creative Commons, 171 Second Street, Suite 300, *
* San Francisco, California, 94105, USA. *
*----------------------------------------------------------------------*/
#include <avr/sleep.h>
const int LED = 13; //LED on pin 13
const unsigned long KEEP_RUNNING = 10000; //milliseconds
void setup(void)
{
//to minimize power consumption while sleeping, output pins must not source
//or sink any current. input pins must have a defined level; a good way to
//ensure this is to enable the internal pullup resistors.
for (byte i=0; i<20; i++) { //make all pins inputs with pullups enabled
pinMode(i, INPUT_PULLUP);
}
pinMode(LED, OUTPUT); //make the led pin an output
digitalWrite(LED, LOW); //drive it low so it doesn't source current
}
void loop(void)
{
for (byte i=0; i<5; i++) { //flash the LED
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
}
delay(KEEP_RUNNING); //opportunity to measure active supply current
digitalWrite(LED, HIGH); //one blink before sleeping
delay(100);
digitalWrite(LED, LOW);
goToSleep();
}
void goToSleep(void)
{
byte adcsra = ADCSRA; //save the ADC Control and Status Register A
ADCSRA = 0; //disable the ADC
EICRA = _BV(ISC01); //configure INT0 to trigger on falling edge
EIMSK = _BV(INT0); //enable INT0
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli(); //stop interrupts to ensure the BOD timed sequence executes as required
sleep_enable();
//disable brown-out detection while sleeping (20-25µA)
uint8_t mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);
uint8_t mcucr2 = mcucr1 & ~_BV(BODSE);
MCUCR = mcucr1;
MCUCR = mcucr2;
//sleep_bod_disable(); //for AVR-GCC 4.3.3 and later, this is equivalent to the previous 4 lines of code
sei(); //ensure interrupts enabled so we can wake up again
sleep_cpu(); //go to sleep
sleep_disable(); //wake up here
ADCSRA = adcsra; //restore ADCSRA
}
//external interrupt 0 wakes the MCU
ISR(INT0_vect)
{
EIMSK = 0; //disable external interrupts (only need one to wake up)
}
/*----------------------------------------------------------------------*
* Sleep demo for ATtinyX4. *
* Wire a button from pin D2 (INT0, PB2, DIP pin 5) to ground. *
* Wire an LED with an appropriate dropping resistor from pin *
* D0 (PB0, DIP pin 2) to ground. *
* Pushing the button wakes the MCU. *
* After waking, the MCU flashes the LED, then waits 10 seconds before *
* going back to sleep. *
* *
* Jack Christensen 04Nov2013 *
* *
* Developed with Arduino 1.0.5. *
* Test conditions for all results below: *
* 5V or 3.3V regulated power supply *
* 8MHz system clock (internal RC oscillator) *
* Fuse bytes (L/H/E): 0xE2 / 0xD5 / 0xFF *
* Arduino-Tiny core, http://code.google.com/p/arduino-tiny/ *
* *
* ATtiny84A-PU *
* Vcc=5V: 4.9mA active, 0.1µA power-down. *
* Vcc=3.3V: 3.1mA active, 0.1µA power-down. *
* *
* This work is licensed under the Creative Commons Attribution- *
* ShareAlike 3.0 Unported License. To view a copy of this license, *
* visit http://creativecommons.org/licenses/by-sa/3.0/ or send a *
* letter to Creative Commons, 171 Second Street, Suite 300, *
* San Francisco, California, 94105, USA. *
*----------------------------------------------------------------------*/
#include <avr/sleep.h>
const int LED_PIN = 0;
const unsigned long KEEP_RUNNING = 10000; //milliseconds
void setup(void)
{
//to minimize power consumption while sleeping, output pins must not source
//or sink any current. input pins must have a defined level; a good way to
//ensure this is to enable the internal pullup resistors.
for (byte i=0; i<11; i++) { //make all pins inputs with pullups enabled
pinMode(i, INPUT_PULLUP);
}
pinMode(LED_PIN, OUTPUT); //make the led pin an output
digitalWrite(LED_PIN, LOW); //drive it low so it doesn't source current
}
void loop(void)
{
for (byte i=0; i<5; i++) { //wake up, flash the LED
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
delay(KEEP_RUNNING); //opportunity to measure active supply current
digitalWrite(LED_PIN, HIGH); //blink LED once before sleeping
delay(100);
digitalWrite(LED_PIN, LOW);
goToSleep();
}
void goToSleep(void)
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
MCUCR &= ~(_BV(ISC01) | _BV(ISC00)); //INT0 on low level
GIMSK |= _BV(INT0); //enable INT0
byte adcsra = ADCSRA; //save ADCSRA
ADCSRA &= ~_BV(ADEN); //disable ADC
cli(); //stop interrupts to ensure the BOD timed sequence executes as required
byte mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector
byte mcucr2 = mcucr1 & ~_BV(BODSE);
MCUCR = mcucr1;
MCUCR = mcucr2;
sei(); //ensure interrupts enabled so we can wake up again
sleep_cpu(); //go to sleep
sleep_disable(); //wake up here
ADCSRA = adcsra; //restore ADCSRA
}
//external interrupt 0 wakes the MCU
ISR(INT0_vect)
{
GIMSK = 0; //disable external interrupts (only need one to wake up)
}
/*----------------------------------------------------------------------*
* Sleep demo for ATtinyX5. *
* Wire a button from pin D2 (INT0, PB2, DIP pin 7) to ground. *
* Wire an LED with an appropriate dropping resistor from pin *
* D4 (PB4, DIP pin 3) to ground. *
* Pushing the button wakes the MCU. *
* After waking, the MCU flashes the LED, then waits 10 seconds before *
* going back to sleep. *
* *
* Jack Christensen 07May2013 *
* *
* Developed with Arduino 1.0.4. *
* Test conditions for all results below: *
* 5V regulated power supply *
* 8MHz system clock (internal RC oscillator) *
* Fuse bytes (L/H/E): 0xE2 / 0xD5 / 0xFF *
* Arduino-Tiny core, http://code.google.com/p/arduino-tiny/ *
* *
* Note that only the ATtinyX5 devices below have BOD disable *
* functionality implemented. With Vcc=5V, the BOD will draw *
* 20-25µA, depending on temperature. *
* ATtiny25, revision E, and newer *
* ATtiny45, revision D, and newer *
* ATtiny85, revision C, and newer *
* *
* ATtiny45V-10PU, Rev. G *
* 7.4mA active, 0.1µA power-down. *
* *
* ATtiny85V-10PU, Rev. B *
* 7.1mA active, 21µA power-down. *
* *
* This work is licensed under the Creative Commons Attribution- *
* ShareAlike 3.0 Unported License. To view a copy of this license, *
* visit http://creativecommons.org/licenses/by-sa/3.0/ or send a *
* letter to Creative Commons, 171 Second Street, Suite 300, *
* San Francisco, California, 94105, USA. *
*----------------------------------------------------------------------*/
#include <avr/sleep.h>
#define LED 4 //LED on pin 4, PB4, DIP pin 3
#define KEEP_RUNNING 10000 //milliseconds
#define BODS 7 //BOD Sleep bit in MCUCR
#define BODSE 2 //BOD Sleep enable bit in MCUCR
void setup(void)
{
//to minimize power consumption while sleeping, output pins must not source
//or sink any current. input pins must have a defined level; a good way to
//ensure this is to enable the internal pullup resistors.
for (byte i=0; i<5; i++) { //make all pins inputs with pullups enabled
pinMode(i, INPUT);
digitalWrite(i, HIGH);
}
pinMode(LED, OUTPUT); //make the led pin an output
digitalWrite(LED, LOW); //drive it low so it doesn't source current
}
void loop(void)
{
goToSleep();
for (byte i=0; i<5; i++) { //wake up, flash the LED
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
}
delay(KEEP_RUNNING); //opportunity to measure active supply current
}
void goToSleep(void)
{
byte adcsra, mcucr1, mcucr2;
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
MCUCR &= ~(_BV(ISC01) | _BV(ISC00)); //INT0 on low level
GIMSK |= _BV(INT0); //enable INT0
adcsra = ADCSRA; //save ADCSRA
ADCSRA &= ~_BV(ADEN); //disable ADC
cli(); //stop interrupts to ensure the BOD timed sequence executes as required
mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector
mcucr2 = mcucr1 & ~_BV(BODSE); //if the MCU does not have BOD disable capability,
MCUCR = mcucr1; // this code has no effect
MCUCR = mcucr2;
sei(); //ensure interrupts enabled so we can wake up again
sleep_cpu(); //go to sleep
sleep_disable(); //wake up here
ADCSRA = adcsra; //restore ADCSRA
}
//external interrupt 0 wakes the MCU
ISR(INT0_vect)
{
GIMSK = 0; //disable external interrupts (only need one to wake up)
}
@mariocaptain
Copy link

UPDATE 2:
After double checking everything, and referring to the article Adventures in Low Power Land by SparkFun, I experiment with removing the following lines in your code:

for (byte i=0; i<20; i++) { //make all pins inputs with pullups enabled
pinMode(i, INPUT_PULLUP);
}

And it works!
My multimeter shows 0.000 mA (can't display anything below this). So I hope it is actually 0.1uA.

Thanks for the great code! And any further comment from you is greatly appreciated.

Cheers,
Dave

@JChristensen
Copy link
Author

JChristensen commented Sep 4, 2017

Late reply but without seeing all the code and the schematic, I can only speculate. Using the internal pullup resistors is definitely not an issue, and is actually recommended to ensure minimum current consumption (I do this all the time). Note that my code above calls pinMode() a second time on the LED pin, changing it to an output (and turning the pullup off). If the LED is wired from the pin to ground, and the pullup is enabled, then current will flow from the pin while the MCU sleeps.

@pittex
Copy link

pittex commented Jan 26, 2018

Hello, thank you for your work.

I'm using sleep_ATtinyX5.ino and it works normally as described, but if the PB2 pin remains in the same state after the KEEP_RUNNING the program contained in the loop no longer runs.

Do you have any idea to help me?

Thanks

@Surgbc
Copy link

Surgbc commented Feb 6, 2018

The problem is that your code and the pin connected to INT0 remaining low will take your MCU to sleep with external interrupts disabled so that it will not be able to wake up.
MCUCR &= ~(_BV(ISC01) | _BV(ISC00)); //INT0 on low level
GIMSK |= _BV(INT0); //enable INT0

GIMSK = 0;

sleep_cpu(); //go to sleep

Check the sequence of those three lines. As long as PB2 is low, the ISR is called after (//enable INT0), then ISR disables all interrupts ( GIMSK = 0 ) so that sleep_cpu() is called without interrupts enabled.

You can solve this by changing the interrupt is activated to either rising or falling edge or just any logical change, eg MCUCR &= _BV(ISC00);

@maufl
Copy link

maufl commented Jul 8, 2018

Hey, I'm new to programming AVRs and found this very useful, thanks! I use the ATmega328P and the code snippet works. However, I hand to comment out the code in the interrupt service routine, otherwise it would always only wake once then had to be reset. Do you have an educated guess why?

@SixProjects
Copy link

I have been having issues where the device does not wake up if noise is present during execution.
I have moved all of void loop to void sleep

void loop(void)
{
    goToSleep();
}

void goToSleep(void)
{
    delay(1000);           //opportunity to measure active supply current 
    digitalWrite(LED, HIGH);       //one blink before sleeping
    delay(1000);
    digitalWrite(LED, LOW);   
    byte adcsra = ADCSRA;          //save the ADC Control and Status Register A
    ADCSRA = 0;                    //disable the ADC
    EICRA = _BV(ISC00);            //configure INT0 to trigger on change

and changed interrupts at the end so it can trigger even if switched rapidly
//external interrupt 0 wakes the MCU ISR(INT0_vect) { EIMSK = _BV(INT0); }
I think it works now

@JChristensen
Copy link
Author

@SixProjects That seems like a software solution for a hardware problem. Without knowing all the particulars, perhaps that's a reasonable approach but my first thought would be to address the noise.

@OsoianMarcel
Copy link

Thank you for sharing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment