Go Down

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

MikeR44

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?

Code: [Select]

#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

Jack Christensen

#1
Nov 17, 2013, 10:03 pm Last Edit: Nov 17, 2013, 10:10 pm by Jack Christensen Reason: 1
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.

Code: [Select]
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);
}
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen


Code: [Select]

   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.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Nick Gammon

Code: [Select]

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:

Code: [Select]

  detachInterrupt (0);      // stop LOW interrupt


http://www.gammon.com.au/interrupts
http://www.gammon.com.au/electronics

Nick Gammon


 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.


Did they really say that? Did you put that in the thread about this issue (wherever that is)?
http://www.gammon.com.au/electronics

Jack Christensen

#5
Nov 17, 2013, 11:56 pm Last Edit: Nov 17, 2013, 11:58 pm by Jack Christensen Reason: 1


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.


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.
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

Jack Christensen

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

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?

Jack Christensen


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?
MCP79411/12 RTC ... "One Million Ohms" ATtiny kit ... available at http://www.tindie.com/stores/JChristensen/

MikeR44

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?

Code: [Select]

#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

Nick Gammon


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


http://www.gammon.com.au/forum/?id=12153#trap22
http://www.gammon.com.au/electronics

Nick Gammon


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


Again:


Oh yes, DO do this:

Code: [Select]

  detachInterrupt (0);      // stop LOW interrupt


http://www.gammon.com.au/interrupts
http://www.gammon.com.au/electronics

Jack Christensen

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.



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

Jack Christensen

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

Nick Gammon

No, #22.

Code: [Select]

Serial.print('Running Loop');


Should be double quotes.
http://www.gammon.com.au/electronics

Go Up