Watchdog Timer Interrupt and External Interrupt

Hello,

I am trying to created a doorbell type device that will checkin every 4 hours so that I know the batteries are good. The following code seems to work fine separately if I have just the WDT or just the interrupt setup. When both are enabled the external interrupt on pin 2 seems to hang the device. Are there any issues with running these 2 interrupts together?

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


#define PIN_LED 13
#define PIN_WAKE 2


/**
 * @brief   Flag stating if sleep has been entered or not.
 */
volatile bool sleep_entered;

volatile int sleep_cycle = 0;

/**
 * @brief We need to enable the WDT for Interrupt mode only, so we can use it 
 *        as a sleep wakeup timer as well as for detecting lockups.
 * @note  Inline function!
 */
inline void configure_wdt(void)
{
    /* A 'timed' sequence is required for configuring the WDT, so we need to 
     * disable interrupts here.
     */
    cli(); 
    wdt_reset();
    MCUSR &= ~_BV(WDRF);
    /* Start the WDT Config change sequence. */
    WDTCSR |= _BV(WDCE) | _BV(WDE);
    /* Configure the prescaler and the WDT for interrupt mode only*/
    WDTCSR =  _BV(WDP0) | _BV(WDP3) | _BV(WDIE);
    sei();
}

/**
 * @brief Configures and enables the watchdog for the specified period. If this
 *        is not required please update the power management module.
 */
void app_sleep_init(void)
{
    /* Setup the flag. */
    sleep_entered = false;
    configure_wdt();
}

void buttonWakeup()        // here the interrupt is handled after wakeup
{
  // execute code here after wake-up before returning to the loop() function
  // timers and code using timers (serial.print and more...) will not work here.
  // we don't really need to execute any special functions here, since we
  // just want the thing to wake up
  loop();
  Serial.println("Button Pressed");
  delay(50);
}
 
/**
 * @brief   Enters the module into low-power sleep mode. Execution is blocked 
 *          here until sleep is exited.
 */
void app_sleep_enter(void)
{
    Serial.println("Entering Sleep Cycle: " + String(sleep_cycle));
    delay(50);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_entered = true;
    sleep_enable();
    attachInterrupt(0,buttonWakeup, LOW); // use interrupt 0 (pin 2) and run function
                                       // wakeUpNow when pin 2 gets LOW *ONLY LOW CAN BE USED*
    sei();
    sleep_mode(); 
    /* Execution will resume here. */
    
    
    sleep_disable();
    if (sleep_cycle < 1800) //Wake up every 1800 cycles ~4 hours
    {
      //Serial.println("Cycle: " + String(sleep_cycle));
      ++sleep_cycle;
      app_sleep_enter();
    }
    else
    {
      sleep_cycle = 0;
      power_all_enable();
    }
    
}


/**
 * @brief   The watchdog interrupt. The watchdog has two functions: 1/ to detect 
 *          if the application code has locked up. 2/ to wake up the module
 *          from sleep.
 */
ISR(WDT_vect)
{
    
}


void setup()
{
   pinMode(PIN_WAKE, INPUT);
   pinMode (PIN_LED, OUTPUT);
   
   Serial.begin(9600);
   Serial.println("Starting Setup");
   
   attachInterrupt(0,buttonWakeup, LOW); // use interrupt 0 (pin 2) and run function
                                         // wakeUpNow when pin 2 gets LOW *ONLY LOW CAN BE USED*
 
   app_sleep_init();
   
}  // end of setup

void loop()
{
  
  digitalWrite (PIN_LED, HIGH);  // awake
  
  delay(5000);
  digitalWrite (PIN_LED, LOW);  // asleep
  
  app_sleep_enter();

}  // end of loop

There should be no issues with those two interrupts. But, printing and delays should never be used in ISRs. And I can't imagine why an ISR would call loop(). If that's not what's hanging it, then it's the call to delay(). Much better to just set a flag in the ISR and let code in loop() handle it. If the delay is a debounce tactic, that can also be handled in loop. Another approach, disable the interrupt in the ISR, and wait 50ms to enable it again, from loop() of course.

void buttonWakeup()        // here the interrupt is handled after wakeup
{
    // execute code here after wake-up before returning to the loop() function
    // timers and code using timers (serial.print and more...) will not work here.
    // we don't really need to execute any special functions here, since we
    // just want the thing to wake up
    loop();
    Serial.println("Button Pressed");
    delay(50);
}

MikeR44:

    attachInterrupt(0,buttonWakeup, LOW); // use interrupt 0 (pin 2) and run function

// wakeUpNow when pin 2 gets LOW ONLY LOW CAN BE USED

If you're using an ATmega328P (Uno or similar), the datasheet is in error; rising or falling interrupts will wake the MCU. I've been in contact with Atmel on this issue and they tell me it should be corrected in the next revision of the datasheet. I might prefer to use an edge-triggered interrupt in this case, as the low-level interrupt triggers continuously as long as the button is held down.

void buttonWakeup()        // here the interrupt is handled after wakeup
{
   loop();  <----- don't do this
  Serial.println("Button Pressed");  <----- don't do this
  delay(50);  <----- don't do this
}

Apart from that, the function is fine. Oh yes, DO do this:

  detachInterrupt (0);      // stop LOW interrupt

Did they really say that? Did you put that in the thread about this issue (wherever that is)?

Yes, they said next datasheet update. Didn't update the thread. I asked them one more very direct question just so there was no doubt, didn't get a reply to that. And I probably don't have the emails any longer. Although I think it was a ticket on their site so the whole conversation is there I hope.

My theory is that someone copied/pasted the wrong text regarding interrupts (likely from some ATtiny). I checked several other ATmega MCUs, they all say they will wake on edge-triggered external interrupts.

Found the email, updated the thread regarding the interrupt.

Awesome! I updated the ISR and removed serial print and delay. Also, calling loop was just from my testing.

The problem I am now running into is that the interrupt is now just incrementing the sleep cycle. Would I just set a flag when the the first interrupt is triggered that would bypass the counter and run the loop?

MikeR44:
Awesome! I updated the ISR and removed serial print and delay. Also, calling loop was just from my testing.

The problem I am now running into is that the interrupt is now just incrementing the sleep cycle. Would I just set a flag when the the first interrupt is triggered that would bypass the counter and run the loop?

Post the current code, please?

Here is the current code. I am thinking I need to add a statement that skips the counting logic.
Also, Serial.print('Running Loop'); prints to the console as "28528". Is there some other type of encoding that needs to be used?

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

#define PIN_LED 13
#define PIN_WAKE 2

/**
 * @brief   Flag stating if sleep has been entered or not.
 */
volatile bool sleep_entered;
volatile bool interrupt1 = false;
volatile bool interrupt2 = false;
volatile int sleep_cycle = 0;

/**
 * @brief We need to enable the WDT for Interrupt mode only, so we can use it 
 *        as a sleep wakeup timer as well as for detecting lockups.
 * @note  Inline function!
 */
inline void configure_wdt(void)
{
    /* A 'timed' sequence is required for configuring the WDT, so we need to 
     * disable interrupts here.
     */
    cli(); 
    wdt_reset();
    MCUSR &= ~_BV(WDRF);
    /* Start the WDT Config change sequence. */
    WDTCSR |= _BV(WDCE) | _BV(WDE);
    /* Configure the prescaler and the WDT for interrupt mode only*/
    WDTCSR =  _BV(WDP0) | _BV(WDP3) | _BV(WDIE);
    sei();
}

/**
 * @brief Configures and enables the watchdog for the specified period. If this
 *        is not required please update the power management module.
 */
void app_sleep_init(void)
{
    /* Setup the flag. */
    sleep_entered = false;
    configure_wdt();
}

void buttonWakeup()        // here the interrupt is handled after wakeup
{
  // execute code here after wake-up before returning to the loop() function
  // timers and code using timers (serial.print and more...) will not work here.
  // we don't really need to execute any special functions here, since we
  // just want the thing to wake up
  interrupt1 = true;
}
 
/**
 * @brief   Enters the module into low-power sleep mode. Execution is blocked 
 *          here until sleep is exited.
 */
void app_sleep_enter(void)
{
    Serial.println("Entering Sleep Cycle: " + String(sleep_cycle));
    delay(25);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_entered = true;
    interrupt1 = false;
    interrupt2 = false;
    sleep_enable();
    attachInterrupt(0,buttonWakeup, LOW); // use interrupt 0 (pin 2) and run function
                                       // wakeUpNow when pin 2 gets LOW *ONLY LOW CAN BE USED*
    sei();
    sleep_mode(); 
    /* Execution will resume here. */
    
    Serial.println("Interrupt1: " + String(interrupt1));
    delay(25);
    
    sleep_disable();
    if (sleep_cycle < 8)
    {
      //Serial.println("Cycle: " + String(sleep_cycle));
      ++sleep_cycle;
      app_sleep_enter();
    }
    else
    {
      sleep_cycle = 0;
      power_all_enable();
    }
    
}


/**
 * @brief   The watchdog interrupt. The watchdog has two functions: 1/ to detect 
 *          if the application code has locked up. 2/ to wake up the module
 *          from sleep.
 */
ISR(WDT_vect)
{
    
}


void setup()
{
   pinMode(PIN_WAKE, INPUT);
   pinMode (PIN_LED, OUTPUT);
   
   Serial.begin(9600);
   Serial.println("Starting Setup");
    
   attachInterrupt(0,buttonWakeup, LOW); // use interrupt 0 (pin 2) and run function
                                         // wakeUpNow when pin 2 gets LOW *ONLY LOW CAN BE USED*
 
   app_sleep_init();
   
}  // end of setup

void loop()
{
  Serial.print('Running Loop');
  digitalWrite (PIN_LED, HIGH);  // awake
  
  delay(5000);
  digitalWrite (PIN_LED, LOW);  // asleep
  
  app_sleep_enter();

}  // end ofloop

MikeR44:
Also, Serial.print('Running Loop'); prints to the console as "28528". Is there some other type of encoding that needs to be used?

MikeR44:
The problem I am now running into is that the interrupt is now just incrementing the sleep cycle.

Again:

Couple more questions.

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

  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.

Did you mean trap #23?

No, #22.

Serial.print('Running Loop');

Should be double quotes.

    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: Gammon Forum : Electronics : Microprocessors : Power saving techniques for microprocessors

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.

Good approach. I'd do Serial.flush() then Serial.end() before sleeping. The delay(25) could then be dispensed with.

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

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.

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 cycles Seconds between loop runs
3 43
6 69
12 121
24 225
48 433
2140
1299
867
433
433
1299
433

MikeR44:
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.

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.