Go Down

Topic: Watchdog Timer Interrupt and External Interrupt (Read 5549 times) previous topic - next topic

Nick Gammon

Code: [Select]

    sleep_mode();
    /* Execution will resume here. */
   
    Serial.println("Interrupt1: " + String(interrupt1));


It is probable that serial output will not immediately be available after waking. You might want to do Serial.end() before sleeping and Serial.begin() after waking.

I have a whole lot of sleep and power related sketches here: http://www.gammon.com.au/power
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Jack Christensen


No, #22.

Code: [Select]

Serial.print('Running Loop');


Should be double quotes.


Ah, missed that.  Then there's also #21, I see there is a recursive call to app_sleep_enter().  That might work but I might avoid the recursion.  Not understanding the goal completely.  Looks like perhaps the idea is to wait for 8 interrupts (which could be any combination of  button pushes or WDT interrupts) before "really" waking up. 
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen


It is probable that serial output will not immediately be available after waking. You might want to do Serial.end() before sleeping and Serial.begin() after waking.


Good approach.  I'd do Serial.flush() then Serial.end() before sleeping.  The delay(25) could then be dispensed with.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

MikeR44

I didn't realize this rolled pages already and missed your further posts!


Couple more questions.

1. Is there an external pullup resistor on the button?

Pin 2 is connected to 3.3v. The button is connected to ground which pulls the voltage down. My understanding is that pullups will not work when the arduino is in powered down sleep mode.


2. Do I understand the intended operation correctly: Illuminate the LED for 5 sec, then sleep the MCU.  Wake when the button is pushed OR when the WDT interrupt occurs (8 sec).

And a couple observations:

Access to variables shared by an ISR must be atomic.  For instance, sleep_cycle is a 2-byte int so it could become corrupted if the mainline code (i.e. loop or app_sleep_enter or whatever) accesses it while the ISR is updating it.  I usually inhibit interrupts, copy the variable of interest to another variable, enable interrupts, then work with the copy.  AVR Libc has ATOMIC_BLOCK macros to facilitate this.

String variables are evil and will chew up all your SRAM when you're not looking.  They're actually probably OK in this sketch, but I highly recommend against using them.


The intended operation is to loop through the 8 second sleep cycles until a predetermined time is reached. In testing I used 8 cycles which is about 1 minute. The the button is pressed it should skip the count and run the loop. I believe I just need to wrap the counting code in an if statement unless there is a function that can be called skip there.

Update: I have commented out anything not in the loop function that outputs to serial and everything seems to be working much better. I also flush and end the serial connection before going to sleep then begin after wake.

The 8 second sleep cycles seem consistent until I reach 48 cycles. At 48 sleep cycles the timing becomes unstable. I tested this by outputting serial data to my computer and time stamping  the output. Here are the results below. Occasionally cycles 3-24 would have a 1 second variance but this was expected. The results for 48 cycles were all over the board. Any thoughts on what could cause the timing to be off randomly?









# 8 second cyclesSeconds between loop runs
343
669
12121
24225
48433
2140
1299
867
433
433
1299
433

Jack Christensen


Pin 2 is connected to 3.3v. The button is connected to ground which pulls the voltage down. My understanding is that pullups will not work when the arduino is in powered down sleep mode.


Yikes! That sounds like pushing the button creates a dead short across the power supply.  Pullups work fine in sleep mode.  I'd use the internal pullup (fewer components to fool with) but an external resistor (try 10K) will also work fine.

Quote

The intended operation is to loop through the 8 second sleep cycles until a predetermined time is reached. In testing I used 8 cycles which is about 1 minute. The the button is pressed it should skip the count and run the loop. I believe I just need to wrap the counting code in an if statement unless there is a function that can be called skip there.

Update: I have commented out anything not in the loop function that outputs to serial and everything seems to be working much better. I also flush and end the serial connection before going to sleep then begin after wake.

The 8 second sleep cycles seem consistent until I reach 48 cycles. At 48 sleep cycles the timing becomes unstable. I tested this by outputting serial data to my computer and time stamping  the output. Here are the results below. Occasionally cycles 3-24 would have a 1 second variance but this was expected. The results for 48 cycles were all over the board. Any thoughts on what could cause the timing to be off randomly?


Post your current code, we can have another look if you like.

The WDT oscillators that I have checked almost always run 10-15% slow. And the so-called 8.0 second delay is actually 220 cycles of the 128kHz oscillator. If the oscillator were right on, that'd be closer to 8.2 seconds, but I'd expect something between 9 and 10 seconds.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Nick Gammon

Quote

The 8 second sleep cycles seem consistent until I reach 48 cycles. At 48 sleep cycles the timing becomes unstable.


How are you timing while asleep? Like Jack says, post your new code.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Jack Christensen

Hope you don't mind a spoiler. If you do, stop reading now ;)

Anyhoo I had bits of code around for sleeping and for the watchdog, so I cobbled this together. It may not match exactly what you're trying to do, but perhaps it's somewhat close.

Code: [Select]
//Using the watchdog timer, sleep for the given number of intervals,
//then print a message. Prints a dot at each WDT wakeup.
//A button is also connected to provide an external interrupt, print
//a message when the button is pressed.
//Finally, blink an LED after each wake-up regardless of source.
//Using an ATmega328P at 16MHz and 5V, draws ~6.3µA while sleeping, which
//is consistent with only the WDT running.
//
//Jack Christensen 19Nov2013
//CC BY-SA, see http://creativecommons.org/licenses/by-sa/3.0/

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/atomic.h>
#include <Streaming.h>            //http://arduiniana.org/libraries/streaming/

const int BTN_PIN = 2;            //wire a button from pin 2 to ground
const int LED_PIN = 13;           //wire an LED from pin 13 to ground through a proper current-limiting resistor
const int WDT_INTERVALS = 50;     //number of WDT timeouts before printing message
const unsigned long LED_ON_TIME = 200;  //turn on LED for this many ms after each wake-up
const long BAUD_RATE = 9600;

volatile boolean extInterrupt;    //external interrupt flag (button)
volatile boolean wdtInterrupt;    //watchdog timer interrupt flag

void setup(void)
{
    pinMode(BTN_PIN, INPUT_PULLUP);
    pinMode(LED_PIN, OUTPUT);
    Serial.begin(BAUD_RATE);

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        wdt_reset();
        MCUSR &= ~_BV(WDRF);                            //clear WDRF
        WDTCSR |= _BV(WDCE) | _BV(WDE);                 //enable WDTCSR change
        WDTCSR =  _BV(WDIE) | _BV(WDP3) | _BV(WDP0);    //~8 sec
    }
    Serial << endl << F("Setup complete");
}

void loop(void)
{
    static int wdtCount;
    static int btnCount;

    if (wdtCount == 0 || extInterrupt) Serial << endl << F("Sleep");
    Serial.flush();
    Serial.end();
    gotoSleep();

    digitalWrite(LED_PIN, HIGH);    //blink the LED on each wakeup
    delay(LED_ON_TIME);
    digitalWrite(LED_PIN, LOW);

    Serial.begin(BAUD_RATE);
    if (wdtInterrupt) {
        Serial << '.';
        if (++wdtCount >= WDT_INTERVALS) {
            Serial << endl << F("WDT: ") << wdtCount;
            wdtCount = 0;
        }
    }
    if (extInterrupt) {
        Serial << endl << F("Button: ") << ++btnCount;
    }
}

void gotoSleep(void)
{
    byte adcsra = ADCSRA;          //save the ADC Control and Status Register A
    ADCSRA = 0;                    //disable the ADC
    EICRA = _BV(ISC01);            //configure INT0 to trigger on falling edge
    EIFR = _BV(INTF0);             //ensure interrupt flag cleared
    EIMSK = _BV(INT0);             //enable INT0
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    ATOMIC_BLOCK(ATOMIC_FORCEON) {
        wdtInterrupt = false;
        extInterrupt = false;
        sleep_enable();
        sleep_bod_disable();       //disable brown-out detection (saves 20-25µA)
    }
    sleep_cpu();                   //go to sleep
    sleep_disable();               //wake up here
    ADCSRA = adcsra;               //restore ADCSRA
}

//external interrupt 0 wakes the MCU
ISR(INT0_vect)
{
    EIMSK = 0;                     //disable external interrupts (only need one to wake up)
    extInterrupt = true;
}

//handles the Watchdog Time-out Interrupt
ISR(WDT_vect)
{
    wdtInterrupt = true;
}

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

MikeR44

I was able to get my code working in a more reliable manner. It is still a little different then the code you offered up Jack. Thanks for posting! There is a bunch of stuff for me to look up in there. The streaming library looks very helpful too- more similar to my PHP background.

pauldreed


Hope you don't mind a spoiler. If you do, stop reading now ;)
Anyhoo I had bits of code around for sleeping and for the watchdog, so I cobbled this together. It may not match exactly what you're trying to do, but perhaps it's somewhat close.


Jack, I'm now trying your code, but having a problem getting it to verify. I get the error;
watchdog_time_example.ino: In function 'void gotoSleep()':
watchdog_time_example:80: error: 'sleep_bod_disable' was not declared in this scope


I'm using Arduino v1.0.5, compiling for a Arduino Uno.

Any ideas?

Jack Christensen

Sorry about that, I'm using a later toolchain than the one that comes with the IDE and I didn't realize that function wasn't defined.  This code is equivalent:

Code: [Select]
//Using the watchdog timer, sleep for the given number of intervals,
//then print a message. Prints a dot at each WDT wakeup.
//A button is also connected to provide an external interrupt, print
//a message when the button is pressed.
//Finally, blink an LED after each wake-up regardless of source.
//Using an ATmega328P at 16MHz and 5V, draws ~6.3µA while sleeping, which
//is consistent with only the WDT running.
//
//Jack Christensen 19Nov2013
//CC BY-SA, see http://creativecommons.org/licenses/by-sa/3.0/

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/atomic.h>
#include <Streaming.h>            //http://arduiniana.org/libraries/streaming/

const int BTN_PIN = 2;            //wire a button from pin 2 to ground
const int LED_PIN = 13;           //wire an LED from pin 13 to ground through a proper current-limiting resistor
const int WDT_INTERVALS = 50;     //number of WDT timeouts before printing message
const unsigned long LED_ON_TIME = 200;  //turn on LED for this many ms after each wake-up
const long BAUD_RATE = 9600;

volatile boolean extInterrupt;    //external interrupt flag (button)
volatile boolean wdtInterrupt;    //watchdog timer interrupt flag

void setup(void)
{
   pinMode(BTN_PIN, INPUT_PULLUP);
   pinMode(LED_PIN, OUTPUT);
   Serial.begin(BAUD_RATE);

   ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
       wdt_reset();
       MCUSR &= ~_BV(WDRF);                            //clear WDRF
       WDTCSR |= _BV(WDCE) | _BV(WDE);                 //enable WDTCSR change
       WDTCSR =  _BV(WDIE) | _BV(WDP3) | _BV(WDP0);    //~8 sec
   }
   Serial << endl << F("Setup complete");
}

void loop(void)
{
   static int wdtCount;
   static int btnCount;

   if (wdtCount == 0 || extInterrupt) Serial << endl << F("Sleep");
   Serial.flush();
   Serial.end();
   gotoSleep();

   digitalWrite(LED_PIN, HIGH);    //blink the LED on each wakeup
   delay(LED_ON_TIME);
   digitalWrite(LED_PIN, LOW);

   Serial.begin(BAUD_RATE);
   if (wdtInterrupt) {
       Serial << '.';
       if (++wdtCount >= WDT_INTERVALS) {
           Serial << endl << F("WDT: ") << wdtCount;
           wdtCount = 0;
       }
   }
   if (extInterrupt) {
       Serial << endl << F("Button: ") << ++btnCount;
   }
}

void gotoSleep(void)
{
   byte adcsra = ADCSRA;          //save the ADC Control and Status Register A
   ADCSRA = 0;                    //disable the ADC
   EICRA = _BV(ISC01);            //configure INT0 to trigger on falling edge
   EIFR = _BV(INTF0);             //ensure interrupt flag cleared
   EIMSK = _BV(INT0);             //enable INT0
   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
   ATOMIC_BLOCK(ATOMIC_FORCEON) {
       wdtInterrupt = false;
       extInterrupt = false;
       sleep_enable();
       byte mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector while sleeping
       byte 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
   sleep_disable();               //wake up here
   ADCSRA = adcsra;               //restore ADCSRA
}

//external interrupt 0 wakes the MCU
ISR(INT0_vect)
{
   EIMSK = 0;                     //disable external interrupts (only need one to wake up)
   extInterrupt = true;
}

//handles the Watchdog Time-out Interrupt
ISR(WDT_vect)
{
   wdtInterrupt = true;
}
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

pauldreed

That's better Jack, it compiles, loads & runs great.
This is a really useful code for maximizing battery life, and I really appreciate you posting it.

I intend to use it to take a light measurement every 15 minutes (or so), and when a certain lux is reached, it will energize a solenoid, and unlock a door - and lock it again when the  lux falls. There is no mains on site, so this should really help.

Thank you.

Jack Christensen

MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

pauldreed

Is it possible to have 2 wake-up pins?
What actually triggers the wake-up, as there does not appear to be any association between BTN_PIN and extInterrupt or any function either in the sketch or libraries?

I wish to use a momentary SPDT rocker switch which if closed either throw, will awake the unit, and then run a step motor clockwise or  c/clockwise according to which contact closed (a manual override to raise or lower a hatch!)

Paul

MichaelMeissner

I've not used interrupts (yet), but I recall that pins 2 & 3 on the Uno can be attached to external interrupts.

Nick Gammon


Is it possible to have 2 wake-up pins?


Yes, you can have multiple wake-up pins. Pin change interrupts can wake up the processor, plus external interrupts, timers, watchdog, I2C, etc.

Consult the datasheet for the exact wake-up sources at various sleep levels.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Go Up