Catching some ZZZs, Part 1: Interrupted Sleep
So you finished your lastest Arduino project, hooked up a 9volt battery to the VIN and stuck the whole thing into a project box. You come back a few hours later and the Arduino is not working and the battery is dead. So what went wrong? This is where you need to start thinking about energy saving, low power modes, and sleep states. In this multi part series we will explore various aspects of the avr lib sleep library, and even some hardware optimizations to save you power and keep your project running, and running.
Part 1: Interrupted Sleep
Interrupted Sleep on the Arduino is conceptually very similar to how most of us are woken up each morning. An event occurs, your alarm clock rings, the sun rises, your cat sits on your face, then you wake up. Usually though you will want to be woken up by a specific moment or event. So for example if you are staying at a hotel you might arrange for a wake up call before you sleep. This is exactly like setting an interrupt before the Arduino sleeps. We are jumping ahead though, the very first step is to actually fall asleep. So here is how its done.
So putting your Arduino into a sleep state that can only be woken up with an interrupt (a button press for example) is simple, it just takes a few lines:
At the top of your sketch include the avr-lib sleep.h file.
#include <avr/sleep.h>
The next thing to do is at the bottom of your sketch, add a sleep handler function, or ISR (interrupt service routine). In other words this is the function (routine) that is called after (to service) the hardware interrupt. So just remember this function is called when your Arduino wakes from sleep. It can usually be left blank, unless you need something to be updated right away. Like setting a boolean state, or incrementing a counter.
void sleepHandler() { }
After the interrupt handler code is done, we should write the code that actually makes the Arduino sleep. This part is easy. Somewhere in your loop function, first call:
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
What this does is sets the type of sleep mode we want to use. There are 6 different modes, with varying degrees of energy saving. In the line above I chose, SLEEP_MODE_PWR_DOWN, Which saves the most energy and turns of almost all systems in the Atmega. Some others you might use are SLEEP_MODE_IDLE, which is the lightest sleep, and SLEEP_MODE_PWR_SAVE. Which is a nice medium amount of energy saving. I will skip the details here, but you can read about which systems and timers are being deactivated in each mode in the datasheet.
After setting the sleep mode, we want to set up the interrupts to wake up the AVR from sleep. If we didn’t do this step the AVR would never wake up.
interrupts(); attachInterrupt(0,sleepHandler, FALLING);
These lines are standard Arduino code. The first one turns on interrupts. This is important because if you deactivated them in some other part of your code and forgot, the interrupt would never be triggered and your Arduino won’t wake up. So its best to just make sure. Next we call the attachInterrupt function which sets up our sleepHandler() function as the ISR. Please refer to the Arduino Reference if you are not familiar with how this works. The next step is to actually set the AVR into sleep mode. With the following Line:
sleep_enable();
What this does is the chip is now in sleep mode, but its not yet asleep. In other words its ready to sleep but it hasn’t actually started sleeping.
Then the AVR finally sleeps:
sleep_mode(); //sleep now
The syntax here is kind of weird. Sleep_mode() doesn’t seem like a command to make the AVR sleep, but it is. This is how its done. I wouldn’t have named the command this myself, but that the way its written.
So now the Arduino is totally asleep, and this could go on until the battery dies, which could be weeks, or months. Unless….Something triggers the interrupt! It could be a button, a sensor value, anything that is attached to our interrupt pin. When that happens, the sleepHandler() function or ISR we wrote earlier is called. After the code inside the sleepHandler() function is done (if you even put code in there). The program will continue running from just AFTER the sleep_mode() function call you made before the chip went to sleep. So the very next thing you should do is actually have the chip fully wake up and disable sleep mode.
sleep_disable(); //fully awake now
After that you can execute any code you want….Then the loop() function will end, and the whole process will start over again from the beginning. So if you are not completely asleep by now, my article has been a total failure!
So here is the whole thing altogether:
#include <avr/sleep.h> void setup(){ //setup something } loop(){ //do stuff set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); interrupts(); attachInterrupt(0,sleepHandler, FALLING); sleep_mode(); //sleep now //--------------- ZZZZZZ sleeping here sleep_disable(); //fully awake now //do more stuff } void sleepHandler() { }
One more thing…this post needs to be read with a word of warning. Even though the Atmega chip on your Arduino is completely asleep in full Power down mode, the Arduino is still a power hog! Why? Because of the FT232RL USB chip (or the ATmega8U on the UNOs) . The only way to truly save power on your Arduino is to also power down your FT232RL or ATmega8U. Which can’t be done without rewiring your Arduino, or making your own. Also there is one other source of major power loss on your Arduino and thats the linear regulator. However if you were to bypass your regulator, and power down your ft232RL these programming tricks will push your Arduino into battery run times in the range of weeks, months, or even years! In part 2 we will talk more about how to optimize your hardware for energy saving.
Awesome stuff, guys 🙂
I built a 5 button arduino remote for an ipod and I want to conserve batteries. If I want to wake the arduino with the press of any of the 5 buttons, do I need to put the above code in 5 times, one for each button loop? Or just once? One of the button loops looks like this:
void loop()
{
simpleRemote.loop();
if (playButton.update())
{
if (playButton.read() == LOW)
{
simpleRemote.sendiPodOn(); // these lines tell the iPod to wake up if it is in sleep mode
delay(50);
simpleRemote.sendButtonReleased();
simpleRemote.sendPlay(); //this command causes the iPod to play the current song when the button is pressed and released. It will pause the song the next time it is pressed.
Serial.println(“play/pause button pressed”); //causes the Arduino serial monitor to print a phrase – useful for debugging.
}
else
{
simpleRemote.sendButtonReleased();
}
}
You would put the sleep code in only once. Not 5 times. However you will need to attach interrupts for all 5 buttons if you want to wake from sleep with any of them. However the arduino only has 2 hardware interrupts. You might want to take a look at the pinchangeInt library for arduino. http://code.google.com/p/arduino-pinchangeint/ It allows you to turn any pin into an interrupt. Good luck!
Thanks a lot for this! I am finally porting my Arduino project into my own build (powered by Li-ion), so this idea helps greatly.
By the way, are you planning to do Part 2 (regarding optimizing the hardware and the FT232 power down etc.)? Greatly look forward to it.
-Gamin
At the “whole thing altogether” part, line 3 should read:
void setup(){ } //setup something
and line 5 should read:
void loop() {
Just an FYI for those who want to copy and paste.
Great article!