Extending WDT beyond 8 seconds using interrupt counter

I have started using this Crash Tracking class (see Detect Lockups | Articles | MegunoLink ) in a project and it works very well. However, I need to extend the timeout beyond 8 seconds. I have changed the WDT ISR() routine as shown below

// Global Interrupt counter
volatile int g_iInterruptCounter = 0;
ISR(WDT_vect, ISR_NAKED)
{

  g_iInterruptCounter++;   // Increment the interrupt counter 
  if(g_iInterruptCounter == 5)                              
  {
    
    // Setup a pointer to the program counter. It goes in a register so we
    // don't mess up the stack. 
    upStack = (uint8_t*)SP;
  
  // The stack pointer on the AVR micro points to the next available location
  // so we want to go back one location to get the first byte of the address 
  // pushed onto the stack when the interrupt was triggered. There will be 
  // PROGRAM_COUNTER_SIZE bytes there. 
    ++upStack;

  
  // Newer versions of GCC don't like when naked functions call regular functions
  // so we use call an assembly gate function instead
  __asm__ __volatile__ (    "call appMon_asm_gate \n"  );
  }    
}

I have left the setting of the WDT at 4 seconds. However, when I run the code (and time with a stopwatch), I only get a timeout of 8 seconds (i.e. twice 4 seconds). If I change the WDT setup to 8 seconds, I get a timeout of 16 seconds (i.e. twice 8 seconds) I can't understand why if(g_iInterruptCounter == 5) is not being executed correctly !!

Any help would be GREATLY appreciated !

Eight seconds is a very long time for a microprocessor. Why not just work with the standard 8 second interval?

...R

Robin2:
Eight seconds is a very long time for a microprocessor. Why not just work with the standard 8 second interval?

...R

My app has some occasions where it interracts with the network disk (can be asleep) and internet servers. I was getting a number of false timeouts with 8 seconds - using the 2 * 8 seconds, these (apart from one which I need to look at) have now disappeared !!

My basic question is, what have I got wrong with my ISR that is causing to only get invoked TWICE rather than the test for 5 ??

Can nobody help me with this ??
Not being able to use Serial.print() in an ISR, I don't know how to debug this !

Why are you using ISR_NAKED? Seems like you are making work (and trouble) for yourself.
I reckon you would need to at least save and restore SREG, and is there a RETI there somewhere? What about any clobbered registers?
Maybe GCC has good reasons why it doesn't like regular functions called in naked isrs, are you sure you understand all the implications?

Code snippets are difficult to diagnose.

Is there no way of accessing the network services in a non blocking fashion?

I use the WDT by default on all my systems with 8s interval and a counter in the ISR. Unless otherwise disturbed, they wake every 34 minutes or so to do their stuff. I've never had any problems and don't have NAKED ISRs, volatile asm statements or independent stacks. So, my recommendation would be to get rid of everything you've done, start from scratch and write the code in a somewhat less flamboyant fashion.

DKWatson:
I use the WDT by default on all my systems with 8s interval and a counter in the ISR. Unless otherwise disturbed, they wake every 34 minutes or so to do their stuff. I've never had any problems and don't have NAKED ISRs, volatile asm statements or independent stacks. So, my recommendation would be to get rid of everything you've done, start from scratch and write the code in a somewhat less flamboyant fashion.

As I said in my original posting, I "inherited" this code (see see Detect Lockups | Articles | MegunoLink ) and it worked fine until I added the interrupt counter. It seemed such a simple addition !! However, I will do further experiments ! Can I ask what WDT code (if any) you have in your ISR where the counter has NOT been satisfied - I suspect that is where my problem resides ...... !

buttimer:
As I said in my original posting, I "inherited" this code (see see Detect Lockups | Articles | MegunoLink ) and it worked fine until I added the interrupt counter. It seemed such a simple addition !! However, I will do further experiments ! Can I ask what WDT code (if any) you have in your ISR where the counter has NOT been satisfied - I suspect that is where my problem resides ...... !

Ok, I see how the code is mostly "inherited", not sure how I missed that in your OP.
Anyway, it was bothering me a little, especially the double timeout bit, so I've taken another look at it.

The issue is simple: The WDT has multiple operating modes

  • stopped
  • interrupt mode
  • system reset mode
  • interrupt and system reset mode
  • system reset mode

You are using the red one, which means that the first timeout causes an interrupt. You do your counter thing but unfortunately you don't reset the interrupt handling, so the next timeout causes a reset.
And, as I mentioned before, the use of the naked ISR is ok if the only action is going to be to reset the device, but since you are wanting to continue execution in some cases, you need to protect the pre-ISR state.

I've attached a modified ApplicationMonitor.cpp that might do what you want, with a few caveats:

  • it was tested on an Arduino Micro with the demo code from the website, the code may be MCU specific, YMMV
  • it's not thoroughly tested, no warranty, use at your own risk
  • for it to work in your use-case, the WDIE bit of the WDTCSR register has to be set inside of the ISR - this could potentially compromise the reset handling in some (possibly rare circumstances) - see the datasheet for more details

The main change is the ISR is now written in inline ASM, I'll post it here inline in case anyone else wants to pick through it:

// How many watchdog interrupt repeats to perform.
const byte c_iIsrMaxCount = 5;

/*
Function called when the watchdog interrupt fires. The function is naked so that
we don't get program stated pushed onto the stack. Consequently the top two
values on the stack will be the program counter when the interrupt fired. We're
going to save that in the eeprom then let the second watchdog event reset the
micro. We never return from this function. 
*/
uint8_t *upStack __attribute__((used));

extern "C" {
  void appMon_asm_gate(void) __attribute__((used));
  void appMon_asm_gate(void){
    ApplicationMonitor.WatchdogInterruptHandler(upStack);
  } 
}

// Global Interrupt counter
volatile byte g_iInterruptCounter = 0;

ISR(WDT_vect, ISR_NAKED)
{
  __asm__ __volatile__ (
      "; save SP \n\t"
      "push r24 \n\t"
      "push r25 \n\t"
      "in r24, __SP_L__ \n\t"
      "in r25, __SP_H__ \n\t"
  
      "; save SREG \n\t"
      "push r16 \n\t"
      "in r16, __SREG__ \n\t"
      "push r16 \n\t"
      
      "; handle ISR repeats \n\t"
      "lds r16, g_iInterruptCounter \n\t"
      "cpi r16, %[IsrMaxCount] \n\t"
      "brlo no_reset \n\t"
  
      "; store the stack location of pre-ISR PC \n\t"
      "adiw r24, 0x03 \n\t"
      "sts upStack+1, r25 \n\t"
      "sts upStack, r24 \n\t"
  
      "; clear zero reg and invoke gate \n\t"
      "clr r1 \n\t"
      "call appMon_asm_gate ; reset occurs here \n\t"
  
    "no_reset:\n\t"

      "; increment counter \n\t"
      "inc r16 \n\t"
      "sts g_iInterruptCounter, r16 \n\t"
      
      "; reset watchdog interrupt, some DANGER involved, see WDIE docs \n\t"
      "lds r16, 0x60 ; WDTCSR \n\t"
      "sbr r16, 0x40 ; set WDIE bit \n\t"
      "sts 0x60, r16 ; WDTCSR \n\t"
      
      "; restore SREG \n\t"
      "pop r16 \n\t"
      "out __SREG__,r16 \n\t"
      "pop r16 \n\t"
  
      "; restore regs \n\t"
      "pop r25 \n\t"
      "pop r24 \n\t"
      
      "reti \n\t"
    
    :: [IsrMaxCount] "M" (c_iIsrMaxCount)
  );
}

HTH.

ApplicationMonitor.cpp (7.19 KB)

This is the ISR,

ISR(WDT_vect)
{
    if (all_ok) Clear(all_ok);
    else    // if all_ok is not set, reset system
    {
        WDTCSR = (1 << WDCE) | (1 << WDE);
        WDTCSR = (1 << WDE);
    }
    wd_count++;
}

In my main loop

Set(all_ok);
wdr();
sleep_mode();

The wd_count can be tested or it just goes back to sleep.

Thank you both for your help. I have implemented the assembler solution and it works fine on the Mega 2560 (not a great fan of mixing assembler & C, but if it works ................. !)
Thank you again guys !!