by

Low-Power Arduino Using the Watchdog Timer

A project I am working on requires temperature data to be sent periodically (about every 5 minutes) from a sensor node to a data logger using an XBee radio. The project requires the sensor to operate using battery power for two weeks. To conserve power, I built a 3.3 V breadboard Arduino with a efficient switching DC to DC voltage regulator and put both the XBee and the ATmega328 to sleep most of the time. This post shows code to use the ATmega328 watchdog timer to keep the Arduino asleep most of the time only waking periodically to perform short tasks like read ing and sending temperature data.

#include <avr/sleep.h>
// This library contains functions to set various low-power 
// states for the ATmega328

// This variable is made volatile because it is changed inside
// an interrupt function
volatile int sleep_count = 0; // Keep track of how many sleep
// cycles have been completed.
const int interval = 5; // Interval in minutes between waking
// and doing tasks.
const int sleep_total = (interval*60)/8; // Approximate number 
// of sleep cycles needed before the interval defined above 
// elapses. Not that this does integer math.

void setup(void) {
watchdogOn(); // Turn on the watch dog timer.

// The following saves some extra power by disabling some 
// peripherals I am not using.

// Disable the ADC by setting the ADEN bit (bit 7) to zero.
ADCSRA = ADCSRA & B01111111;

// Disable the analog comparator by setting the ACD bit
// (bit 7) to one.
ACSR = B10000000;

// Disable digital input buffers on all analog input pins
// by setting bits 0-5 to one.
DIDR0 = DIDR0 | B00111111;
}

void loop(void) {
goToSleep(); // ATmega328 goes to sleep for about 8 seconds
// and continues to execute code when it wakes up

if (sleep_count == sleep_total) {

// CODE TO BE EXECUTED PERIODICALLY

}

}

void goToSleep()   
{
// The ATmega328 has five different sleep states.
// See the ATmega 328 datasheet for more information.
// SLEEP_MODE_IDLE -the least power savings 
// SLEEP_MODE_ADC
// SLEEP_MODE_PWR_SAVE
// SLEEP_MODE_STANDBY
// SLEEP_MODE_PWR_DOWN -the most power savings
// I am using the deepest sleep mode from which a
// watchdog timer interrupt can wake the ATMega328

set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode.
sleep_enable(); // Enable sleep mode.
sleep_mode(); // Enter sleep mode.
// After waking from watchdog interrupt the code continues
// to execute from this point.

sleep_disable(); // Disable sleep mode after waking.
                     
}

void watchdogOn() {
  
// Clear the reset flag, the WDRF bit (bit 3) of MCUSR.
MCUSR = MCUSR & B11110111;
  
// Set the WDCE bit (bit 4) and the WDE bit (bit 3) 
// of WDTCSR. The WDCE bit must be set in order to 
// change WDE or the watchdog prescalers. Setting the 
// WDCE bit will allow updtaes to the prescalers and 
// WDE for 4 clock cycles then it will be reset by 
// hardware.
WDTCSR = WDTCSR | B00011000; 

// Set the watchdog timeout prescaler value to 1024 K 
// which will yeild a time-out interval of about 8.0 s.
WDTCSR = B00100001;

// Enable the watchdog timer interupt.
WDTCSR = WDTCSR | B01000000;
MCUSR = MCUSR & B11110111;

}

ISR(WDT_vect)
{
sleep_count ++; // keep track of how many sleep cycles
// have been completed.
}

23 Comments


  1. // Reply

    Thanks for the great overview. I got it to work for my project and this was a big help. A couple things you might want to change are that the function called in the loop is goToSleep, while the one you define is sleepNow. I also had better success by changing ‘if (sleep_count == sleep_total)’ to ‘if (sleep_count > sleep_total)’ since for mysterious reasons sometimes the sleep_count would jump higher than the total so the loop wouldn’t exit. Lastly, you clear the MCUSR twice in watchdogOn(), is there a reason for this? Don’t mean to be nit picky, overall this works well.


    1. // Reply

      I am glad you found the post useful. Thanks for pointing out the “goToSleep()” typo. I have fixed it. It is strange that you had trouble with (sleep_count==sleep_total). I have been using code like this in a project that has been operating continuously for over three months and never found the MCU to hang. Nevertheless, using (sleep_count > sleep_total) is a good way to be safe. As for the second time I clear the MCUSR flag, I seem to remember reading in the ATmega328 datasheet that turning on the interrupt could accidentally set this flag. I could be remembering incorrectly but I think I put this in there to be safe. Thanks for the feedback.


      1. // Reply

        your post was really useful..my project requires ADC.and i will be using ADC pins.so if i disable ADC mode..would dat mean i wont be able to use ADC….


      2. // Reply

        I’ve been looking for some code to help me understand watchdogs for a while now, and have to say yours did the trick.

        I also had a problem with the hanging from the loop. I instead reset the counter every time it went through the assigned interval number:

        void loop(void) {
        goToSleep(); // ATmega328 goes to sleep for about 8 seconds
        if (sleep_count == sleep_total) {

        // CODE TO BE EXECUTED PERIODICALLY
        sleep_count = 0;

        etc, etc, etc

        Thanks a bunch!


  2. // Reply

    Thx a lot !!!
    Exactly what I was looking for. It will save me lot of time.
    Cheers


  3. // Reply

    Thank you for this example. I don’t know why, but in my circuit, the sleep time is only 4 seconds (not 8 seconds). Although, I tried it in the same way as you. Maybe, you can give some hints?

    Curcuit:
    [atmega328p, 16Mhz, 3.3V, Bootloader(Arduino)]

    Best regards from germany


    1. // Reply

      The eight second sleep time is an approximation. The watchdog timer continues running in the background even after the MCU wakes up so if you do four seconds of work then go back to sleep the MCU will only stay asleep for about four more seconds before the watchdog timer times out and wakes the MCU up again. Maybe this is what is happening.



  4. // Reply

    Hi, Does it work with arduino uno?


    1. // Reply

      Yes. I mostly use Uno. This code is written for Uno.


  5. // Reply

    I just tried this on 2 Arduino Leonardos (wrong chips) and they’ve both gone caput. After the bootloader has loaded the board can no longer be recognized (checked in device manager). Any idea?


    1. // Reply

      Wow, this is surprising. The ATmega32U4 on the Leonardo also has a programmable watchdog timer. I wouldn’t expect the program to brick the MCU. You may have to reload the bootloader using an AVR programmer. There are several resources (programmers and tutorials) on how to do this on sparkfun.com. I have bricked Unos a lot. I use the AVRISP MKII programmer by Atmel. Good luck.


    2. // Reply

      I tried the same with same results 🙁
      Board seems bricked.

      Problem with the Leonardo is that when it enters sleep mode, it loses the USB connection and it doesn’t come back. If you have bricked the board, the way to unbrick it is load another sketch (like the blink sketch). I haven’t managed to fix this on the Windows IDE, but on a Mac or Linux it works as follows: plug in the bricked leonardo board and slect the correct serial port (it will briefly appear in the list and then disappear), compile the sketch and upload. When the message Uploading… apperas press the reste button on the Leonardo and the sketch will upload and the board will be un-bricked.


      1. // Reply

        I too did the same with Arduino LilyPad USB and got the same results. The last sketch runs, but I couldn’t download a new sketch through USB. I was able to un-brick it by re-burning the Arduino LilyPad USB bootloader (through the SPI, using another Arduino).


  6. // Reply

    Hey Jeff.
    I noticed you disable the ADC (analog digital converter) in the setup of your sketch with this code:
    ***
    // Disable the ADC by setting the ADEN bit (bit 7) to zero.
    ADCSRA = ADCSRA & B01111111;
    ***
    this seems to work as well:
    ***
    ADCSRA = 0;
    ***

    A few noobish questions if you don’t mind me asking:

    1. Since you disabled ADC in setup(), does that mean you would not be able to take any analog readings throughout the rest of your code?

    – My project uses an analog temp sensor. Therefore I need to be able to take analog readings when the MCU is awake. So I imagine disabling ADC in setup() wouldn’t work for me. However:

    2a. Could I instead: disable ADC within the loop, just before MCU sleeps, and then enable ADC just after MCU awakes?

    2b. Would that allow me to take analog readings when MCU is awake, but still save ~100uA by having ADC disabled when the MCU is sleeping?

    3. How would I go about “enabling” ADC?
    – my guess would be:
    ***
    ADCSRA = 1;
    ***

    4. I noticed I could save ~20 uA by turning off BOD (brown-out disable) before sleeping. I was wondering if there is anything wrong with doing so, and if I should be turning BOD back on after waking?

    ***
    // turn off BOD in software

    MCUCR = bit (BODS) | bit (BODSE); // turn on brown-out enable select

    MCUCR = bit (BODS); // this must be done within 4 clock cycles of above
    ***

    Thanks Jeff.


  7. // Reply

    hey Jeff.

    I was just wondering how I could disable the ADC only for sleep, but have it function after waking-up.

    I tried a few different things, like putting:

    // Disable the ADC by setting the ADEN bit (bit 7) to zero.
    ADCSRA = ADCSRA & B01111111;

    just before my sleep function, and then putting:

    // Enable the ADC by setting the ADEN bit (bit 7) to one.
    ADCSRA = ADCSRA & B11111111;

    just after my sleep function.
    *Note: I just guessed that this is how you enable ADC since its the inverse of how you disabled it.

    anyhow, the analog readings I get after this section of my code are whacko. So something is happening after disabling and then enabling the ADC that is causing whacko analog readings.

    If I leave ADC alone (never disable it) then I can sleep and take non-whacko analog readings all day long. But I would like to disable ADC while sleeping to save more power.

    please advise.
    Thanks.


  8. // Reply

    Thank you so much for this excellent sketch. I have also lost a couple of sparkfun pro micro’s while exploring the sleep function. Through the school of hard knocks I have become rather proficient at restoring them to working condition. Again, your code is awesome.



  9. // Reply

    Hello,
    your code is not performing as it should unless you clear the flag sleep_count=0;


  10. // Reply

    Hello,
    I want Atemaga 328 to sleep for almost 20Hr and then wake up automatically to perform a specific function. Is it possible to make AT3328 controller sleep for 20 Hour without using external hardware interrupt?? If to use external hardware what will be much suitable?


    1. // Reply

      Yes, you can do this. Well, effectively. If memory serves the watchdog timer has an overflow about every 8 seconds. At that point the MCU would wake but you can just put it immediately back to sleep until the desired time has elapsed.


  11. // Reply

    Thanks JEFF,
    If I want to use external hardware to sleep controller 20 hour sleep which component would be efficient in terms of power saving??


    1. // Reply

      Gajendra,

      Don’t know. I would just use the Atmel’s low power states. Keep in mind that when the Atmel MCU is in its low power state it uses very little energy. Even though it wakes up every eight-ish seconds, if it immediately goes back to sleep it uses very little energy. It may only be awake for a hundred microseconds or less. That means over an eight second period it is only awake for 0.001% of the time.

Leave a Reply

Your email address will not be published. Required fields are marked *