Wednesday, October 10, 2012

Arduino Low Power Tutorial

Ive been working with power save mode for the Arduino lately, and Ive found that while there are multitudes of examples out there, nobody specifically gives you a working example to run with. In this post, I'll walk you through my code, and at the end, I'll provide you with a great working example of using an interrupt button to bring the Arduino back out of a low power state for a few seconds and then back to sleep. This is great for applications that require the use of a battery for long periods of time and charging is scarce.

You can follow the interrupted sleep tutorial by NoMi Design to learn just how to set things up.

My code, however, is just like this example, provided on Engblaze.com, except that Ive added some serial communications to see that its working visually and to re-enable the interrupt attach so that I can constantly bring the device out of sleep every time it goes to sleep.

//remove the space between '<' and 'avr'.
#include < avr/interrupt.h>
#include < avr/power.h>
#include < avr/sleep.h>
#include < avr/io.h>

void setup()
{
   Serial.begin(9600);
    DDRD &= B00000011;       // set Arduino pins 2 to 7 as inputs, leaves 0 & 1 (RX & TX) as is
    DDRB = B00000000;        // set pins 8 to 13 as inputs
    PORTD |= B11111100;      // enable pullups on pins 2 to 7
    PORTB |= B11111111;      // enable pullups on pins 8 to 13
    pinMode(13,OUTPUT);      // set pin 13 as an output so we can use LED to monitor
    digitalWrite(13,HIGH);   // turn pin 13 LED on
}

void loop()
{
    // Stay awake for 1 second, then sleep.
    // LED turns off when sleeping, then back on upon wake.
    delay(2000);
    Serial.println("Entering Sleep Mode");
    sleepNow();
    Serial.println(" ");
    Serial.println("I am now Awake");
}
                //
void sleepNow()
{
    
    // Choose our preferred sleep mode:
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);
    //
    interrupts();
    // Set pin 2 as interrupt and attach handler:
    attachInterrupt(0, pinInterrupt, HIGH);
    //delay(100);
    //
    // Set sleep enable (SE) bit:
    sleep_enable();
    //
    // Put the device to sleep:
    digitalWrite(13,LOW);   // turn LED off to indicate sleep
    sleep_mode();
    //
    // Upon waking up, sketch continues from this point.
    sleep_disable();
    digitalWrite(13,HIGH);   // turn LED on to indicate awake
}

void pinInterrupt()
{
    detachInterrupt(0);
    attachInterrupt(0, pinInterrupt, HIGH);
}

Dont mind any avr's at the end. Its a glitch the website keeps doing when I post #includes at the top of the code on the blog.

Sleep Modes

Finally, its important to discuss the types of Sleep Modes that you can choose from. There are 6 sleep modes available on the Arduino Uno (ATMEGA328):
  • SLEEP_MODE_IDLE                   – least power savings
  • SLEEP_MODE_ADC
  • SLEEP_MODE_EXTENDED_STANDBY  
  • SLEEP_MODE_PWR_SAVE
  • SLEEP_MODE_STANDBY
  • SLEEP_MODE_PWR_DOWN    – most power savings
SLEEP_MODE_IDLE provides the least power savings but also retains the most functionality.  SLEEP_MODE_PWR_DOWN uses the least power but turns almost everything off, so your options for wake interrupts and the like are limited.  Power reduction management methods are described in more detail on the avr-libc documentation page. For details on what features are available with each power saving mode for the Arduino Uno, please refer to the ATMEGA168/328p Datasheet (look out, its a 12mb file). For all other Arduino's refer to either the Arduino website, or the Atmel website.

Ive decided to go with SLEEP_MODE_PWR_SAVE for this example, just because it gives me some flexibility with waking it, and because the power savings are a bit better than idle. Power down is overkill for my applications, and its comparative to actually turning off the device, which I don't need my hardware to do.

You're welcome to ask questions, about this code if you arent sure whats going on. The bit of setting up the pins in the Arduino hardware is explained more on the Engblaze post.

18 comments:

  1. VERY Interesting! However in both Arduino 0023 and 1.01 I get this error when pasting this code:
    ---------------------( COPY )----------------------
    sketch_oct11a.cpp:1:41: error: C:\Users\TerryKing\Desktop\ArduinoDev\arduino-1.0.1\hardware\arduino\cores\arduino/avr interrupt.h="interrupt.h": Invalid argument
    -----------------( END COPY )----------------------
    Am I missing some library??

    Thanks!

    Regards, Terry King
    ...In The Woods in Vermont, USA
    terry@yourduino.com

    ReplyDelete
    Replies
    1. It should be ' #include < avr/interrupt.h' > without quotes and spaces. Blogger reformatted how the code was supposed to display. I've fixed it now.

      Delete
  2. You can give this a try instead:

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

    with no space between < and avr.

    I know for sure that those work in my code. Let me know how it goes. If so, I'll update the code above to reflect this edit.

    ReplyDelete
  3. I dont get the setting of the pins as input or outputs and pull-ups. Im trying to get a FIO based app as energy efficient as possible.

    Besides pins, I do:
    -power down sleep
    -ADC disable
    -AC disable
    -BOD disable

    This gets me down to 108 uA, of which I assume the majority to be 'unsaveable' due to it being used by the voltage regulator (~100uA).

    When I add in your pin code (copy paste, input and pull-up), it goes up to 155 uA while sleeping. So I dont see how this is saving power, and different sources seem to disagree on the web. Any explanation?

    PS: only FIO board being used, no peripherals/sensors/XBEE. 9V battery powered

    ReplyDelete
  4. It could be because you are using a 9V battery. Try using a 3.3V Li-Ion battery and then let me know. It could also be the use of The PWR SAVE mode. Try PWR DOWN to see if you save more power.

    ReplyDelete
    Replies
    1. Seems there was a problem with pin 13. Although I did put it as input, the pull-up seems to make the LED draw a tiny amount of current, even making it glow, albeit very dim.

      Anyway, after I tested again (already used power down sleep), and now results are there isnt any difference between all 4 combinations (in/out and high/low).

      The 9V battery is only used in development/testing. For the real deal Ill be switching to 40AH 6V battery. The device must last several years at a calculated avg current of 1 mA....I also have no LIPO at my disposal atm.

      For now Ill keep the code in, perhaps I havent been in situations where the difference is more profound...currently at a stable 105 uA sleep power. Thanks anyways!

      Delete
    2. Sorry I couldnt help with your project. Perhaps you can share what you did in your code and maybe I can see whats going on? Do you necessarily need the Fio? There are plenty of other uC's out there that are way less power hungry than the Atmega32u4 on the Fio.

      Delete
  5. hi may I have the library for avr/interrupt, avr/power, avr/sleep, avr/io because I could not find the link to download it from the web. if its possible could you zip the file? thank you and sorry for the trouble.

    ReplyDelete
    Replies
    1. They are libraries that come with the Arduino software. Download the latest version of Arduino from www.arduino.cc and you will have them.

      Delete
  6. Hi I understand it already thank you so much! However I am getting error when verifying. Is it that the function cannot work with the arduino version that I have? Below is the error that I get.

    sleepmode.ino: In function 'void sleepNow()':
    sleepmode:32: error: 'SLEEP_MODE_PWR_SAVE' was not declared in this scope
    sleepmode:32: error: 'set_sleep_mode' was not declared in this scope
    sleepmode:40: error: 'sleep_enable' was not declared in this scope
    sleepmode:44: error: 'sleep_mode' was not declared in this scope
    sleepmode:47: error: 'sleep_disable' was not declared in this scope

    ReplyDelete
  7. Hi for the error that I ask you earlier on I solved it. So sorry to trouble you cause I forgotten to //remove the space between '<' and 'avr'.

    ReplyDelete
  8. Hi Tamir, thanks for the very useful tutorial!

    I've experienced an issue with the Serial.print before sleepNow(); and just figured out, it had no time to transfer the text. You might consider to give it a delay(30) before executing sleepNow(); just in order to properly Serial.print the text of choice.

    .
    .
    .
    Serial.println("Entering Sleep Mode");
    delay(30);
    sleepNow();
    .
    .
    .

    ReplyDelete
    Replies
    1. Interesting input on the delay bit. I hadnt thought of that. Thanks for sharing!

      Delete
  9. Are you using the Arduino Due? Because I couldn't get this to work for me. The documentation says you can't set an interrupt for HIGH as you have, except for in the Due. It has to be RISING or CHANGED. Then when you do that, the only power mode you can sleep in is IDLE, not PWRSAVE.

    ReplyDelete
    Replies
    1. As the post says, its anything with an Atmega 168/328p. I was using a Fio. I know this example works, because I used it many times. I dont post example code unless Ive tried it and used it in my projects.

      Delete
    2. Also, which documentation are you referring to? You should use the chip manufacturer's manuals not Arduino.

      Delete
  10. Just a question, if the Arduino uses bluetooth as a serial connection, and I set the Arduino to sleep, will it still provide enough power to the Bluetooth to wake it up via serial?

    ReplyDelete
    Replies
    1. It should. I was able to successfully wake the Arduino from sleep using XBee commands, so I would think that Bluetooth is similar enough to work.

      The whole purpose of sleep mode is to conserve power. But if you program to wake on serial, then it will sleep as long as there is no serial communication. I would approach your coding as the following: Once the wireless device receives the signal to wake, it will wake the Arduino...do whatever it needs to do, transmit again, and then sleep again.

      Hope this helps!

      Delete