The AVR Watchdog

"No piece of software, save the very smallest, is free from bugs." (Atmel, the Watchdog Timer).

Various unpredictable code issues could hang the system forever, an unwanted situation that impacts the purpose and the functionality of your electronic device. Luckily, the AVR microcontrollers have been equipped with an internal Watchdog Timer, that runs independent of the rest of the system. If your main code freezes, the watchdog will still be up and running. It can be used for two purposes: to reboot the microcontroller, or to fire an interrupt. By using any of these two directions, the original cause of the system failure is avoided - yet not fixed, but your application is guaranteed to function at least as good as it did when you tested it.

A quick example

  1. #include <avr/wdt.h>
  2. ...
  3. // main entry point
  4. int main(void) {
  5. wdt_enable(WDTO_8S);
  6. ...
  7. // main loop
  8. while(1) {
  9. // some logic
  10.  
  11. // simulating time consuming code
  12. _delay_ms(100);
  13. // signal the watchdog we are still alive: the watchdog will reset its timer
  14. wdt_reset();
  15. }
  16. }

The code configures the watchdog for a 8s interval. If no wdt_reset is received, the Watchdog will automatically reset the microcontroller. This can be simulated by using instead of _delay_ms(100) meaning a 100ms code execution delay, a bigger value of _delay_ms(10000) which is a 10seconds interval. In this case the Watchdog would reach the 8seconds limit and initiate a reset. In practice this happens when your code hangs, imagine serious code problems where the simple _delay_ms was used in the sample above.

Simple is not enough
It seems simple and straightforward and the advantages in adding the short Watchdog code to any application are obvious. Still, on some newer microcontrollers, the code above will make your device enter an endless loop of resets once the first watchdog reset has been triggered. Apparently, as explained here, on newer devices (any AVR that has the option to also generate Watchdog interrupts), the watchdog timer remains active even after a system reset (except a power-on condition), using the fastest prescaler value (approximately 15 ms). It is therefore required to turn off the watchdog early during program startup. 15ms is so quick, that we don't have any chance of calling wdt_reset() nor wdt_disable() in our main function. The solution is adding the wdt_disable() to the .init3 section (i.e. during the startup code, before main()) to disable the watchdog early enough so it does not continually reset the AVR:

  1. // This function is called upon a HARDWARE RESET:
  2. void wdt_first(void) __attribute__((naked)) __attribute__((section(".init3")));
  3.  
  4. // Clear SREG_I on hardware reset.
  5. void wdt_first(void)
  6. {
  7. MCUSR = 0; // clear reset flags
  8. wdt_disable();
  9. // http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_softreset.html
  10. }

What if 8seconds are not enough?
Some code I was working on, had a blocker function related to some Ethernet initialisations, that blocked for more than 8seconds. With the watchdog enabled and its prescaler set for 8 seconds, the longer interval of my code resulted in permanent auto-resets issued by the Watchdog. That worked as expected, but how to fix it? I didn't want to drop the Watchdog, nor add tens of wdt_reset() calls all over the Ethernet code. The solution was to use the other capabilities of the Watchdog timer, and that is it's interrupt generator.
The logic is as follows:
Configure the Watchdog to generate an interrupt at a given time interval (say 1sec), but disable its reset. So the Watchdog would count all the way to the preconfigured time interval, but will not reset the microcontroller, instead it would signal an interrupt using an ISR function. In this function, you can use a variable to count for how many times the interrupt has been fired. Assuming the prescaller was set for 1 second, we can increment the variable 60 times to know that an entire minute has elapsed. Then, on the 60th interrupt call, assuming we've reaching the extended time limit we wanted, we simply reconfigure the watchdog for reset mode, we disable the interrupt (not needed anymore) and the prescaller to the shortest interval of 15ms, resulting in... immediate reset, but all this after one entire minute has elapsed, and so we've been able to extend the 8 seconds limit of the watchdog timer.
Here's the code:

  1. int wdt_counter = 0;
  2. // This function is called upon a HARDWARE RESET:
  3. void wdt_first(void) __attribute__((naked)) __attribute__((section(".init3")));
  4.  
  5. // Clear SREG_I on hardware reset.
  6. void wdt_first(void)
  7. {
  8. // Note that for newer devices (any AVR that has the option to also
  9. // generate WDT interrupts), the watchdog timer remains active even
  10. // after a system reset (except a power-on condition), using the fastest
  11. // prescaler value (approximately 15 ms). It is therefore required
  12. // to turn off the watchdog early during program startup.
  13. MCUSR = 0; // clear reset flags
  14. wdt_disable();
  15. // http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_softreset.html
  16. }
  17.  
  18. void wdt_init() {
  19. cli();
  20. WDTCSR = (1<<WDCE) | (1<<WDE); // Enable the WD Change Bit - configure mode
  21. WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP1); // Enable WDT Interrupt, and Set Timeout to ~1 seconds ,or use WDTO_1S
  22. sei(); // enable interrupts
  23. }
  24.  
  25. // This is the interrupt code, called every second, unless wdt_reset() was called sooner
  26. ISR(WDT_vect)
  27. {
  28. wdt_counter++;
  29. if (wdt_counter < 60) { // 60 seconds limit , that is ONE MINUTE
  30. // start timer again (we are still in interrupt-only mode)
  31. wdt_reset();
  32. } else {
  33. // go for immediate reset
  34. WDTCSR = (1<<WDCE) | (1<<WDE); // Enable the WD Change Bit - configure mode
  35. WDTCSR = (1<<WDE) | (1<<WDP0); // set reset flag (WDE) and 16ms (WDP0)
  36. }
  37. }
  38.  
  39. // Use this custom function to reset, to also set the seconds counter back to 0
  40. // The watchdog will reset only if one full minute is reached , without your code calling my_wdt_reset() before that
  41. void my_wdt_reset() {
  42. wdt_counter = 0;
  43. wdt_reset();
  44. }
  45.  

You can also strip this example down to use the interrupt logic only, in case you need the Watchdog for a time counter. But it is not very accurate, so keep that in mind when designing your applications. It servers perfectly as a code guard, though.

Useful links
Atmel AVR soft reset
AVR132: Using the Enhanced Watchdog Timer
AVR Freaks forum
Arduino forum
AVR Freaks forum

Related Post

This article has 1 Comment

  1. Thank you for this article. I just found that the reasons of my Arduino Mega resets are exactly like you described. Everything works OK when I disable watchdog. I don’t know nothing about interrupts so can you prepare an example of using your code? What should I do to start this “extended” watchdog in my sketch (except copying your code to it – of course).

Leave a Reply