Attiny85 & Watchdog Wake-Up

I've been reading around to wrap my head around using the watchdog to wake an attiny85 from sleep. I think I may have a grasp of it now, but there are a few questions that aren't quite clear.

Here is the code I currently have (from Nick Gammon):

// Example of sleeping and saving power
// 
// Author: Nick Gammon
// Date:   25 May 2011

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

// watchdog interrupt
ISR(WDT_vect) 
  {
  wdt_disable();  // disable watchdog
  }

void myWatchdogEnable(const byte interval) 
  {  
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000000 | interval;    // set WDIE, and appropriate delay
  
  wdt_reset();
  set_sleep_mode (SLEEP_MODE_PWR_DOWN); 
  sleep_mode();            // now goes to Sleep and waits for the interrupt
  } 

void setup()
{
  pinMode (LED, OUTPUT);
}  // end of setup

void loop()
{
  digitalWrite (LED, HIGH);  // awake
  delay (2000);    // ie. do stuff here
  digitalWrite (LED, LOW);  // asleep

  for (int i = 0; i < 15; i++){
  myWatchdogEnable (0b000110);  // 1 second
  }

}  // end ofloop

// sleep bit patterns:
//  1 second:  0b000110
//  2 seconds: 0b000111
//  4 seconds: 0b100000
//  8 seconds: 0b100001

Here's what I'm not clear on in the sleep setup for the greatest power reduction:

  1. Do I need to turn on and off the ADC? (ADCSRA &= ~_BV(ADEN):wink:
  2. Do I need to enable and disable interrupts? (sei() and cli())
  3. Do I need to enable and disable the sleep-bit?
  4. Is sleep_cpu() different than sleep_mode()?
  1. Yes; but you don't have to turn it in if not used; also using PRR (power reduction register) and disabling analog comparator reduces consumption (probably does not matter in power-down sleep)
  2. You need enable them. Disable them if you want, probably not needed: either you are not using them or you want them enabled anyway
  3. You need enable sleep. You may disable it to have "safe code" that cannot fall asleep "by mistake".
  4. Yes

Use wdt_reset() BEFORE writing WDTCR - you may generate reset otherwise.

Thanks. I read up about the differences of sleep_cpu() vs. sleep_mode(), they were pretty straightforward.

I updated the code below. This is just a bare-bones sketch to implement the wdt-wakeup. The idea is that it will turn on an LED for 2 seconds, then turn it off, and go to sleep for 15 seconds.

Is there any redundant code I'm using here, or anything I'm missing? I haven't tested it yet, but I plan to later tonight.

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

ISR(WDT_vect) 
  {
  wdt_disable();  
  }

void myWatchdogEnable(const byte interval) 
  {
  wdt_reset();
    
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000000 | interval;    // set WDIE, and appropriate delay
  
  ADCSRA &= ~_BV(ADEN);

  set_sleep_mode (SLEEP_MODE_PWR_DOWN); 
  sleep_bod_disable();
  sei();
  sleep_mode();     
  
  
  ADCSRA |= _BV(ADEN);
  
  } 

void setup()
{
pinMode (LED, OUTPUT);
}  

void loop(){

  digitalWrite (LED, HIGH); 
  delay (2000);    
  digitalWrite (LED, LOW);  

  for (int i = 0; i < 15; i++){
  myWatchdogEnable (0b000110);  // 1 second
  }

}  

// sleep bit patterns:
//  1 second:  0b000110
//  2 seconds: 0b000111
//  4 seconds: 0b100000
//  8 seconds: 0b100001

edit: updated a forgotten "()" in the line reading: sei();

update: the code seems to work as intended. I'll measure current consumption tomorrow to make sure its actually shutting down.

edit: current consumption indicates the chip is going to sleep. when the LED is illuminated, the circuit consumes ~30 mA. When the chip goes to sleep, current drops below 1 mA (as low as the meter will go), and briefly ticks to 1 mA each time the wdt times-out and resets in the for-loop.

Some tips:

  • use noInterrupts() and interrupts() instead of cli() and sei(). Makes the sketch more readable. The resulting code is exactly the same

  • disable interrupts before fiddling with the watchdog settings. These are timed sequences that must not be interrupted e. g. by a timer interrupt. This will eventually happen and you will end up with a sleeping CPU AND an inactive watchdog

  • there is no need to enable the ADC after sleep if you do not plan to use it. If you do use it then save the ADC register to a global variable and restore it afterwards

Thanks, so the updated code looks like:

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

ISR(WDT_vect) 
  {
  wdt_disable();  
  }

void myWatchdogEnable(const byte interval) 
  {

  noInterrupts();

  wdt_reset();
    
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000000 | interval;    // set WDIE, and appropriate delay
  
  ADCSRA &= ~_BV(ADEN);

  set_sleep_mode (SLEEP_MODE_PWR_DOWN); 
  sleep_bod_disable();
  interrupts();
  sleep_mode();     
  
  } 

void setup()
{
pinMode (LED, OUTPUT);
}  

void loop(){

  digitalWrite (LED, HIGH); 
  delay (2000);    
  digitalWrite (LED, LOW);  

  for (int i = 0; i < 15; i++){
  myWatchdogEnable (0b000110);  // 1 second
  }

}  

// sleep bit patterns:
//  1 second:  0b000110
//  2 seconds: 0b000111
//  4 seconds: 0b100000
//  8 seconds: 0b100001

Can anyone tell me the reason why this code works, and the bottom code does NOT? (The only difference is the sleep-mode declared):

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

volatile boolean on = true;

ISR(WDT_vect) 
  {
  on = !on;
  wdt_disable();  
  }

void myWatchdogEnable(const byte interval) 
  {

  noInterrupts();

  wdt_reset();
    
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000110 | interval;    // set WDIE, and appropriate delay
  
  ADCSRA &= ~_BV(ADEN);

  set_sleep_mode (SLEEP_MODE_PWR_DOWN); 
  sleep_bod_disable();
  interrupts();
  sleep_mode();     
  
  } 

void setup()
{
pinMode (LED, OUTPUT);
}  

void loop(){

  digitalWrite (LED, on); 

  myWatchdogEnable (0b000110);  // 1 second

}

And this code doesn't:

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

volatile boolean on = true;

ISR(WDT_vect) 
  {
  on = !on;
  wdt_disable();  
  }

void myWatchdogEnable(const byte interval) 
  {

  noInterrupts();

  wdt_reset();
    
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000110 | interval;    // set WDIE, and appropriate delay
  
  ADCSRA &= ~_BV(ADEN);

  set_sleep_mode (SLEEP_MODE_IDLE); 
  sleep_bod_disable();
  interrupts();
  sleep_mode();     
  
  } 

void setup()
{
pinMode (LED, OUTPUT);
}  

void loop(){

  digitalWrite (LED, on); 

  myWatchdogEnable (0b000110);  // 1 second

}

That second batch of code just gives a solidly-ON LED, no blinking.

Because on Arduino there is a timer used to generate an interrupt every (about) 1ms to keep time. You stop this timer by Power Down sleep but not Idle sleep. In the second sketch the CPU wakes up every ms, and resets WDT, so WDT interrupt is never generated.

And please use bit definitions, it makes the code much more readable. I.e.
WDTCR=(1<<WDE)|(1<<WDCE) instead of 0b00001100;

Thank you for that. Can you tell me how you stop that wdt reset?

You can try for example this:

#include <avr/sleep.h>
#include <avr/wdt.h>

#define LED 0

volatile boolean on = true;

ISR(WDT_vect)
  {
  on = !on;
  wdt_disable(); 
  }

void myWatchdogEnable(const byte interval)
  {

  noInterrupts();

  wdt_reset();
   
  MCUSR = 0;                          // reset various flags
  WDTCR |= 0b00011000;               // see docs, set WDCE, WDE
  WDTCR =  0b01000110 | interval;    // set WDIE, and appropriate delay
 
  ADCSRA &= ~_BV(ADEN);

  set_sleep_mode (SLEEP_MODE_IDLE);
  sleep_bod_disable();
  interrupts();
  sleep_mode();     
 
  }

void setup()
{
pinMode (LED, OUTPUT);
} 

void loop(){

  digitalWrite (LED, on);
  if (WDTCR&(1<<WDIE) {   //still waiting for WDT interrupts, simply go sleep
    sleep_bod_disable();
    sleep_mode();
  }
  else { //WDT interrupt is disabled (from WDT ISR)
    myWatchdogEnable (0b000110);  // 1 second
  }
}