Hi,
I'm developing an Open Source solar powered arduino platform at Rachel's Electronics (Loading...), and have got some good results so far! This post is in regard to power saving techniques. My application is a sensor that does not have to run all the time, so I want the little bugger to sleep hard between readings. I've been working off of the almighty datasheet and these resources:
Embedded Systems: Sleeping Arduino - Part 1
http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
My application of the sleepy arduino needs to be the most power conservative, so I focused on SLEEP_MODE_PWR_DOWN mode and would like to host a comprehensive sleepy-duino resource that addresses the other mode options in the future. So much of what we do with this thing is about waiting around, and it's always good to conserve energy ![]()
The code I'm posting here is called Sleepy Bones, and I'll follow with some notes and questions
/*
Watchdog Sleepy Bones (the skeleton is the essential armature for any living thing)
This is the basic structure that you need to Sleep_Mode_Pwr_Down/Watchdog interrupt wake
Provided for general consumption and regurgitation by Rachel's Electronics http://www.rachelselectronics.com
Under the CC licence here (http://creativecommons.org/licenses/by-sa/3.0/us/)
Rachel's Electronics: Making the World Safe for Robots!
Built in part from code posted by:
* KHM 2008 / Lab3/ Martin Nawrath nawrath@khm.de
* Kunsthochschule fuer Medien Koeln
* Academy of Media Arts Cologne
http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
*/
#include <avr/sleep.h>
#include <avr/wdt.h>
#define sleepTime 3 //number of 8 second sleep cycles
volatile byte wdt=0; //used to count number of sleep cycles
void setup(){
setup_watchdog(); // set prescaler and enable interrupt
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here Power Down uses the least current
// system clock is turned off, so millis won't be reliable!
delay(10);
}
void loop(){
// do what you want to before you sleep here
system_sleep();
// do what you want to after you sleep here
}
void system_sleep() {
byte dummy_0 = DDRB; // save port directions
byte dummy_1 = DDRD; // save port directions
byte dummy_2 = TWSR; // save TW Status Register (I2C)
byte dummy_3 = TWBR; // save TW Bitrate Register (I2C)
byte dummy_4 = SPCR; // save SPI Control Register
byte dummy_5 = SPSR; // save SPI Status Register
PRR = 0xEF; // disable peripherals
ADCSRA |= (0<<ADEN); // disable ADC
DDRB = 0x00; // set ports to intput to save power
DDRD = 0x00; // set ports to intput to save power
sleep_enable(); // enable sleeping
sleep_mode(); // activate system sleep
// sleeping ...
// first action after leaving WDT Interrupt Vector:
if (wdt==sleepTime) { // sleep for this number times 8 seconds
sleep_disable(); // disable sleep
wdt=0; // reset watchdog counter
PRR = 0x00; // enable peripherals
ADCSRA |= (1<<ADEN); // enable ADC
DDRB = dummy_0; // set ports to pre-sleep state
DDRD = dummy_1; // set porst to pre-sleep state
TWSR = dummy_2; // reset TW Status Register
TWBR = dummy_3; // reset TW Bitrate Register
SPCR = dummy_4; // reset SPI Control Register
SPSR = dummy_5; // reset SPI Status Register
}else{
sleep_mode(); // go back to sleep until it's time
}
}
void setup_watchdog() {
MCUSR = 0x00; //clear all reset flags
//set WD_ChangeEnable and WD_resetEnable to alter the register
WDTCSR |= (1<<WDCE) | (1<<WDE); // this is a timed sequence to protect WDTCSR
// set new watchdog timeout value to 1024K cycles (~8.0 sec)
WDTCSR = (1<<WDP3) | (1<<WDP0);
//enable watchdog interrupt
WDTCSR |= (1<<WDIE);
}
// Watchdog Interrupt Service Routine.
// Very first thing after sleep wakes with WDT Interrupt
ISR(WDT_vect) {
wdt++; // increment the watchdog timer
}
NOTES:
You have to follow the instructions in the almighty datasheet when you mess around with the WDTCSR. Any alteration to this register is a two part process. First, set the Change_Enable bit and the Reset_Enable bit. Then you can do whatever you want as long as you clear the Change_Enable bit in your next move.
Using PWR_DOWN will stop the system clock (duh) and so any attempt to use millis() will fail.
My application is a solar sensor, and so I need long sleep times. I set the watchdog prescaler to the longest time (~8 seconds) and increment a counter to maximize sleep for longer periods. You may want to change this for your application. I am using the arduino board as a programmer, and dropping the ATmega into my breadboarded prototype circuit. Check out Rachel's blog above for future posts that have diagrams and stuph.
QUESTIONS:
What happens inside the sleep_mode() command? I have gone ahead and placed all my pins as inputs, and saved the peripheral fuses with dummy bytes, and put the extra clocks into shutdown, but does this already happen for, say, the SPI clock since I'm using PWR_DOWN option? I'm trying to keep this skeleton as light as possible. Right now it weighs in at 732 bytes compiling to the Duemilanove w/328.
I appreciate your comments and suggestions. See you at the Open Source Hardware Summit!