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:
- Do I need to turn on and off the ADC? (ADCSRA &= ~_BV(ADEN)
- Do I need to enable and disable interrupts? (sei() and cli())
- Do I need to enable and disable the sleep-bit?
- Is sleep_cpu() different than sleep_mode()?
- 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)
- You need enable them. Disable them if you want, probably not needed: either you are not using them or you want them enabled anyway
- You need enable sleep. You may disable it to have "safe code" that cannot fall asleep "by mistake".
- 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.
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
}
}