1
\$\begingroup\$

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();
    }
}
\$\endgroup\$
5
  • \$\begingroup\$ Is this the complete code? Is it possible that you never set the "Global Interrupt Enable" bit in SREG to enable interrupts? \$\endgroup\$
    – Rev
    Commented Mar 7, 2014 at 9:07
  • \$\begingroup\$ Yeah, that's all of it. Weird... the interrupts are working until it goes to sleep (that's how it gets put to sleep). I'll try re-enabling it anyways. \$\endgroup\$
    – Adam Haile
    Commented Mar 7, 2014 at 13:37
  • \$\begingroup\$ Got it figured out... it was just a weird timing issue with when things were getting called. \$\endgroup\$
    – Adam Haile
    Commented Mar 7, 2014 at 20:22
  • \$\begingroup\$ If you can be a little bit more specific, this could help others in the future. \$\endgroup\$
    – Rev
    Commented Mar 7, 2014 at 20:29
  • \$\begingroup\$ As an aside, you may want to put the expression in the #define in parens. \$\endgroup\$ Commented Mar 7, 2014 at 23:05

1 Answer 1

2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.