Go Down

Topic: Attiny85 & Watchdog Wake-Up (Read 2689 times) previous topic - next topic

silly_cone

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):

Code: [Select]
// 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);)
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()?

Smajdalf

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.

silly_cone

#2
May 07, 2017, 09:37 pm Last Edit: May 08, 2017, 04:41 am by silly_cone
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.

Code: [Select]

#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();

silly_cone

#3
May 08, 2017, 04:32 am Last Edit: May 08, 2017, 05:35 pm by silly_cone
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.

olf2012

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


silly_cone

Thanks, so the updated code looks like:
Code: [Select]


#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

silly_cone

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

Code: [Select]

#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:

Code: [Select]
#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.

Smajdalf

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;

silly_cone

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

Smajdalf

You can try for example this:
Code: [Select]

#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
  }
}

Go Up