Sleep and wake intervals

fbriggs4:
Thanks again everyone, this forum is really amazing!!

The really amazing thing is that I just happened to have a very similar circuit breadboarded, I just had to move an LED to A0 to test it out! Had quite a bit of fun with it.

Some notes:

  1. Turning off many of the individual peripherals (Timers 0 and 1, TWI, SPI, etc.) is not strictly necessary; because Power Save mode stops the associated clocks, they are effectively shut down. So not necessary to replace millis(). Of course it doesn't tick while the MCU is sleeping, but we can still use it to time intervals when not sleeping.

  2. The goToSleep() function also turns off the Brown-Out Detector. I am measuring just about 1µA while the MCU is sleeping.

  3. I took the INT0/INT1 interrupt set-up code out, so it will need to be added back in.

  4. The ATOMIC_BLOCK business is just some covenience macros for manipulating the global interrupt enable bit. From the avr-libc user manual:

The macros in this header file deal with code blocks that are guaranteed to be excuted
Atomically or Non-Atmomically. The term "Atomic" in this context refers to the unability
of the respective code to be interrupted.
These macros operate via automatic manipulation of the Global Interrupt Status (I) bit
of the SREG register. Exit paths from both block types are all managed automatically
without the need for special considerations, i. e. the interrupt status will be restored to
the same value it has been when entering the respective block.
A typical example that requires atomic access is a 16 (or more) bit variable that is
shared between the main execution path and an ISR. While declaring such a variable
as volatile ensures that the compiler will not optimize accesses to it away, it does not
guarantee atomic access to it.

So here's what I ended up with, FWIW:

#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/atomic.h>

volatile long ticks;                   //global variables are initialized to zero (but see below*)
long ticksCopy;                        //copy of ticks to minimize need for atomic blocks
long maxSeconds = 64;                  //can be set to any number, but because the Timer2 overflow interrupt is set
                                       //to only occur every 8 seconds, if this is not a modulo-8 number, the LED will
                                       //not blink until the next 8-second interval elapses.

void setup()
{
    //To reduce power, set all pins as inputs with pullups
    for(int x = 1 ; x < 20 ; x++) {    //was x < 18 ... jc
        pinMode(x, INPUT);
        digitalWrite(x, HIGH);
    }
 
    //Set up TIMER2
    TIMSK2 = 0;                        //stop timer2 interrupts while we set up
    ASSR = _BV(AS2);                   //Timer/Counter2 clocked from external crystal
    TCCR2A = 0;                        //override arduino settings, ensure WGM mode 0 (normal mode)
    TCCR2B = _BV(CS22) | _BV(CS21) | _BV(CS20);    //prescaler clk/1024 -- TCNT2 will overflow once every 8 seconds
    TCNT2 = 0;                         //start the timer at zero
    while (ASSR & (_BV(TCN2UB) | _BV(TCR2AUB) | _BV(TCR2BUB))) {}    //wait for the registers to be updated    
    TIFR2 = _BV(OCF2B) | _BV(OCF2A) | _BV(TOV2);                     //clear the interrupt flags
    TIMSK2 = _BV(TOIE2);               //enable interrupt on overflow
    ATOMIC_BLOCK(ATOMIC_FORCEON) {     //Arduino enables interrupts, so RESTORESTATE would work here as well
        ticks = 0;                     //*ensure we start from zero (interrupts may have occurred before we set up the timer)
    }
}

void loop()
{
    goToSleep();
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        ticksCopy = ticks;
    }
    if(ticksCopy * 8 >= maxSeconds) {
        pinMode(A0, OUTPUT); 
        digitalWrite(A0, LOW);            //turn on the LED for a couple seconds
        delay(2000);                      //LED is wired to Vcc and connected to A0 with a current-limiting resistor
        digitalWrite(A0, HIGH);
        pinMode(A0, INPUT); 
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
            ticks = 0;
        }
    }
}

void goToSleep()
{
    byte adcsra, mcucr1, mcucr2;

    //Cannot re-enter sleep mode within one TOSC cycle. This provides the needed delay.
    OCR2A = 0;                        //write to OCR2A, we're not using it, but no matter
    while (ASSR & _BV(OCR2AUB)) {}    //wait for OCR2A to be updated  

    sleep_enable();
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);
    adcsra = ADCSRA;                  //save the ADC Control and Status Register A
    ADCSRA = 0;                       //disable ADC
    ATOMIC_BLOCK(ATOMIC_FORCEON) {    //ATOMIC_FORCEON ensures interrupts are enabled so we can wake up again
        mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
        mcucr2 = mcucr1 & ~_BV(BODSE);
        MCUCR = mcucr1;               //timed sequence
        MCUCR = mcucr2;               //BODS stays active for 3 cycles, sleep instruction must be executed while it's active
    }
    sleep_cpu();                      //go to sleep
                                      //wake up here
    sleep_disable();
    ADCSRA = adcsra;                  //restore ADCSRA
}

//The Timer/Counter2 Overflow Interrupt Handler
ISR(TIMER2_OVF_vect)
{
    ticks++;
}