Electronic – arduino – Watchdog timer not being called in sleep mode

arduinoatmegaavrsleepwatchdog

On an ATMega328p, I'm trying to setup a program that counts 1 second intervals via the watchdog timer, even when the chip is in sleep mode. A button interrupt puts the chip into sleep mode. The problem I'm having is that once in sleep mode, it seems that the watchdog vector is never being called. I think I have everything setup like it should be but cannot figure out what's wrong. Any thoughts?

#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
//#include <avr/io.h>

#define SLEEP_ENABLED MCUCR & _BV(SE)

void setup()
{
    pinMode(2, INPUT);
    digitalWrite(2, HIGH); //enable pullup

    Serial.begin(115200);

    MCUSR &= ~(1<<WDRF); //Clear WDT Reset Flag

    /* In order to change WDE or the prescaler, we need to
    * set WDCE (This will allow updates for 4 clock cycles).
    */
    WDTCSR |= (1<<WDCE) | (1<<WDE);

    /* set new watchdog timeout prescaler value */
    WDTCSR = 1<<WDP2 | 1<<WDP1; /* 1.0 seconds */
    //WDTCSR = 1<<WDP3 | 1<<WDP0; /* 8.0 seconds */

    /* Enable the WD interrupt (note no reset). */
    WDTCSR |= _BV(WDIE);

    attachInterrupt (0, initSleep, LOW);
}

volatile uint32_t _count = 0;
volatile uint32_t _sleepCount = 0;
ISR(WDT_vect)
{
    _count++;
    //if(PIND & _BV(PIND2))
    if(SLEEP_ENABLED)// && _count >= _sleepCount + 5)
    {
        wakeSleep();
    }   
}

volatile uint8_t _oldADC = ADCSRA;

void wakeSleep()
{
    attachInterrupt (0, initSleep, LOW);

    ADCSRA = _oldADC;
    //setClock16();

    sleep_disable();
}

inline void _doSleep()
{
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    //Do actual sleep
    // turn off brown-out enable in software
    MCUCR = bit (BODS) | bit (BODSE); 
    MCUCR = bit (BODS);
    sleep_cpu();
    /////////////////
}

void initSleep()
{
    _sleepCount = _count;
    detachInterrupt(0);

    //setClock8();

    //disable ADC to save power
    _oldADC = ADCSRA;
    ADCSRA = 0;
    /////////////////////////////

    _doSleep();
}

static uint8_t __clock_prescaler = (CLKPR & (_BV(CLKPS0) | _BV(CLKPS1) | _BV(CLKPS2) | _BV(CLKPS3)));
void setClock(uint8_t prescale) //Only call from within an interrup
{
    // Disable interrupts.
    uint8_t oldSREG = SREG;
    //cli();
    // Enable change.
    CLKPR = _BV(CLKPCE); // write the CLKPCE bit to one and all the other to zero
    // Change clock division.
    CLKPR = prescale; // write the CLKPS0..3 bits while writing the CLKPE bit to zero
    __clock_prescaler = prescale;
    // Recopy interrupt register.
    SREG = oldSREG;
    //sei();
}

void setClock16() { setClock(0x0); }
void setClock8() { setClock(0x1); }


void loop()
{
    static uint32_t lastCount = -1;
    if(!SLEEP_ENABLED)
    {
        if(lastCount != _count)
        {
            Serial.println(_count, DEC);
            lastCount = _count;
            delay(100);
        }
    }
    else
    {
        _doSleep();
    }
}

Best Answer

Found the solution. _doSleep was getting called from within the INT0 interrupt, which meant that during the sleep period, it was effectively inside an interrupt. And therefore the watchdog interrupt could not fire. Changed it to set a flag in the INT0 interrupt and then actually go to sleep in the main loop.