Question about watchdog timer and external interrupts

Hi, I’m working on a project with an Attiny85 that wakes up every 8 seconds using the WDT and counting until it reaches ~15 minutes which works fine. But I’m also counting external interrupts during this time which messes up the timing by potentially resetting the watchdog before the 8 seconds is up.

As a test I’ve modified some code from Nick Gammon’s site and it seems to get around the issue, but I’m wondering if what I’ve done could cause some problems with a poorly timed interrupt or something. I don’t want to miss a wdt reset and end up stuck asleep.

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


const byte LED = 4;
const byte INT = 1;

volatile int wdt_count = 0;
volatile int button_count = 0;
volatile bool flag = false;

void setup () { 
  pinMode(INT, INPUT); 
  attachPinChangeInterrupt(INT, test, CHANGE); 
}
void flash (int count)
  {
  pinMode (LED, OUTPUT);
  for (int i = 0; i < (count+3); i++)
    {
    digitalWrite (LED, HIGH);
    delay (50);
    digitalWrite (LED, LOW);
    delay (50);
    }
    
  pinMode (LED, INPUT);
    
}  // end of flash

// watchdog interrupt
ISR (WDT_vect) 
{
  wdt_count++;
  wdt_disable();  // disable watchdog
}  // end of WDT_vect
 
void test(){
  button_count++;
  flag = true;
}

void loop () 
{
  while(wdt_count<2){
    goToSleep();
  }
  wdt_disable(); 
  wdt_count=0;
  flash (button_count);
  button_count=0;
  
} // end of loop

void goToSleep (){
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  ADCSRA = 0;            // turn off ADC
  power_all_disable ();  // power off ADC, Timer 0 and 1, serial interface
  noInterrupts ();       // timed sequence coming up
  if(flag)
    flag = false;
  else
    resetWatchdog ();      // get watchdog ready
  sleep_enable ();       // ready to sleep
  interrupts ();         // interrupts are required now
  sleep_cpu ();          // sleep                
  sleep_disable ();      // precaution
  power_all_enable ();   // power everything back on
}
void resetWatchdog ()
  {
  // clear various "reset" flags
  MCUSR = 0;     
  // allow changes, disable reset, clear existing interrupt
  WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF);
  // set interrupt mode and an interval (WDE must be changed from 1 to 0 here)
  WDTCR = bit (WDIE) | bit (WDP3) | bit (WDP0);    // set WDIE, and 8 seconds delay
  // pat the dog
  wdt_reset();  
}

Thanks.

Interrupts don't get lost, they are only processed in sequential order.

i don't understand what you do with the watchdog timer. Why not let it run continuously, until the wake up time (or whatsoever) is reached?

The problem I was having was if an interrupt came say, 5 seconds into the 8 second watchdog timer, it would wake up and reset the timer back to 8 seconds giving a total of 13 seconds for that loop and throwing off the timing.

Why would an interrupt reset the WDT?

Because when it wakes up from the interrupt goToSleep() is called again which resets the wdt, which I’m trying to avoid by using flag to skip the resetWatchdog() call.

But looking at my current code it seems like if an interrupt arrives right after the wdt fires and before it gets reset, then a new wdt will never get set.

Edit: Ok I found a different example more suited to what I’m trying to do that has helped my understanding of the watchdog somewhat. I think this version should be more reliable?

#include <avr/wdt.h>            // library for default watchdog functions
#include <avr/sleep.h>          // library for sleep
#include <avr/power.h>          // library for power control
#include <PinChangeInterrupt.h>

volatile int wdt_count; 
volatile int button_count = 0;

#define LED 4
#define INT 1

void setup(){
  
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  
  pinMode(INT, INPUT); 
  attachPinChangeInterrupt(INT, test, RISING); 
  
  delay(1000);
  
  // configure the watchdog
  configure_wdt();

  // blink twice
  blink(2);
}

void loop(){

  sleep(2);
  wdt_disable();
  
  blink(button_count+1);
  button_count=0;
  
  configure_wdt();
}

ISR(WDT_vect)
{
  wdt_count--;
  // reset the watchdog
  wdt_reset();
}

void test(){
  button_count++;
}

// function to configure the watchdog: let it sleep 8 seconds before firing
// when firing, configure it for resuming program execution
void configure_wdt(void)
{
 
  cli();                           // disable interrupts for changing the registers

  MCUSR = 0;                       // reset status register flags

                                   // Put timer in interrupt-only mode:                                
  WDTCR |= 0b00011000;             // Set WDCE (5th from left) and WDE (4th from left) to enter config mode,
                                   // using bitwise OR assignment (leaves other bits unchanged).
  WDTCR =  0b01000000 | 0b100001;  // set WDIE: interrupt enabled
                                   // clr WDE: reset disabled
                                   // and set delay interval (right side of bar) to 8 seconds

  sei();                           // re-enable interrupts
 
}

// Put the Arduino to deep sleep. Only an interrupt can wake it up.
void sleep(int ncycles)
{  
  wdt_count = ncycles; // defines how many cycles should sleep

  // Set sleep to full power down.  Only external interrupts or
  // the watchdog timer can wake the CPU!
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 
  // Turn off the ADC while asleep.
  power_adc_disable();
 
  while (wdt_count > 0){ // while some cycles left, sleep!

    // Enable sleep and enter sleep mode.
    sleep_mode();
   
    // When awake, disable sleep mode
    sleep_disable(); 
  }
 
  // put everything on again
  power_all_enable();
 
}

void blink(int count){
  for(int i=0; i<count; i++){
    digitalWrite(LED, HIGH);   
    delay(100);               
    digitalWrite(LED, LOW);   
    delay(100); 
  }
}

Why do you ever want to do a wdt_reset()? The WDT interrupt flag is automatically cleared when the ISR executes, no need to reset anything there.

Oh, does it? It's just been in all the examples I've looked at.
So it looks like you only need to use wdt_reset() to avoid resets if you're using a reset watchdog?

I agree that it may be called for preventing a programmed reset. But it does not make sense to program a reset first, then disable it unconditionally in the ISR. It would make sense to call it outside the WDT ISR, but not inside it.

You can find out yourself, what happens if you omit the wdt_reset() from your code.

Yes I removed it from the ISR and it still works ok.