Brief version: If you do not have a watchdog signal handler, the default action of a gcc-compiled program is to perform a (very) soft reset. If you set WDE to true, then WDIE is reset every interrupt. If you set WDE to false, WDIE does not change. You can find out why the device reset using MCUSR.
In more detail, and using assembly rather than AVR-libc since I worked from the data-sheets from Atmel rather than the docs from avr-libc:
I used the watchdog timer just to sleep for a second, so my watchdog signal handler actually does nothing at all:
Code: Select all
#include <avr/interrupt.h>
/* AtTinyX5 specific name, WDT_vect */
ISR( WDT_vect, ISR_NAKED ) {
asm volatile( "reti" );
}
To turn on the watchdog timer as an interrupt make sure to set both WDE and WDIE correctly. Your description is what happens when you set them to (1,1). To use the watchdog to sleep until interrupt (and otherwise, not use the watchdog) you should set them to (0,1).
Code: Select all
int main(void) {
/* ... setup ... */
// interrupt only, trigger every 1 second
WDTCR = (0<<WDIF ) | (1<<WDIE ) | (1<<WDCE ) | (0<<WDE ) | (0<<WDP3 ) | (1<<WDP2 ) | (1<<WDP1 ) | (0<<WDP0 );
// the classic blunder is to forget to enable interrupts
asm volatile("sei");
for(;;) {
/* ... loop ... */
}
}
To enter a deep sleep that only draws current in the 5μA range, I use:
Code: Select all
static void PowerDown(void) {
uint8_t a;
asm volatile(
"eor %0,%0 \n\t"
"out %[adcsra], %0 \n\t"
"ldi %0, %[prrV1] \n\t"
"out %[prrA], %0 \n\t"
"ldi %0, %[bods1] \n\t"
"out %[mcucr], %0 \n\t"
"ldi %0, %[bods2] \n\t"
"out %[mcucr], %0 \n\t"
"sleep \n\t"
"ldi %0, %[prrV2] \n\t"
"out %[prrA], %0 \n\t"
: "=d" (a)
:
[prrV1] "M" ((1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI ) | (1<<PRADC )),
[prrV2] "M" ((1<<PRTIM1) | (1<<PRTIM0) | (1<<PRUSI ) | (0<<PRADC )),
[bods1] "M" ((1<<BODS ) | (1<<SE ) | (1<<SM1 ) | (0<<SM0 ) | (1<<BODSE) | (1<<ISC01) | (1<<ISC00) | (1<<PUD )),
[bods2] "M" ((1<<BODS ) | (1<<SE ) | (1<<SM1 ) | (0<<SM0 ) | (0<<BODSE) | (1<<ISC01) | (1<<ISC00) | (1<<PUD )),
[mcucr] "I" (_SFR_IO_ADDR(MCUCR)),
[adcsra] "I" (_SFR_IO_ADDR(ADCSRA)),
[prrA ] "I" (_SFR_IO_ADDR(PRR))
);
}
This turns off peripherals, including the timers, the serial interrupter, the ADC, the brown out detector, and other interrupters. It sleeps, waits for the interrupt generated by the watchdog, and then wakes up, and turns the ADC partially back on. The next call to the analog reader then turns the ADC completely on by setting ADCSRA appropriately. My goal was low current consumption, and the 10 or 15 extra cycles is negligible since I spend millions of cycles sleeping.
If you use the analog comparator, you need to turn it off too.
You also asked:
How does this reset differentiate from a hard power down?
You can determine what caused a reset by looking at MCUSR before it is cleared by the initialization functions created by GCC. An example of doing this is in the source code to the kit "Lux Spectralis", at
http://www.sparkfun.com/commerce/produc ... ts_id=9287 (source code not available anywhere else, as far as I can see).
Roughly it goes:
Code: Select all
void init(void) __attribute__ ((naked, section(".init3")));
void init(void) {
if(bit_is_set(MCUSR, PORF)) {
// Power turned on
} else if(bit_is_set(MCUSR, EXTRF)) {
// Reset pin held high
} else if(bit_is_set(MCUSR, BORF)) {
// Brown out occurred
} else if(bit_is_set(MCUSR, WDRF)) {
// Watchdog reset, not interrupt
} else {
// The compiler has betrayed us and cleared MCUSR before we could read it!
}
}
The ".init3" stuff is only necessary if you find yourself in the last else. My brief tests (this is a new technique for me) show that it is not needed in my setup. Since the GCC initialization code is very behind-the-scenes, it seems difficult to detect this.