17
\$\begingroup\$

I'm trying to wrap my head around the watchdog timer on the ATTinyX5 series. So things I've read made it seem like you could use it for making the program do something specific ever N seconds but never really showed how. Others made it seem like it would only reset the chip unless something in code reset it's count in the meantime (which seems to be the "normal" usage).

Is there any way of using the WDT like you would TIMER1_COMPA_vect or similar. I noticed that it has a 1 second timeout mode and I would really love to be able to use that to make something happen every 1 second in my code (and preferrably sleep in between).

Thoughts?

*Update: * Since it was asked, what I'm referring to is section 8.4 of the ATTinyX5 Datasheet. Not that I fully understand it, which is my problem...

\$\endgroup\$
  • 1
    \$\begingroup\$ +1 for thinking outside the box. Extra thumbs up if you add a link to the datasheet for the AVR. \$\endgroup\$ – jippie Jul 3 '13 at 18:03
23
\$\begingroup\$

You most certainly can. According to the datasheet, the watchdog timer can be setup to reset the MCU or cause an interrupt when it triggers. It seems you are more interested in the interrupt possibility.

The WDT is actually easier to setup than a normal Timer for the same reason it is less useful: fewer options. It runs on an internally calibrated 128kHz clock, meaning its timing is not effected by the main clock speed of the MCU. It can also continue to run during the deepest sleep modes to provide a wake up source.

I will go over a couple of the datasheet examples as well as some code I have used (in C).

Included Files and Definitions

To start, you will probably want to include the following two header files for things to work:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Also, I use the Macro <_BV(BIT)> which is defined in one of the standard AVR headers as the following (which might be more familial to you):

#define _BV(BIT)   (1<<BIT)

Beginning of Code

When the MCU is first started, you would typically initialize the I/O, set up timers, etc. Somewhere here is a good time to make sure the WDT didn't cause a reset because it could do it again, keeping your program in an unstable loop.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

WDT Setup

Then, after you have setup the rest of the chip, redo the WDT. Setting up the WDT requires a "timed sequence," but it is really easy to do...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Of course, your interrupts should be disabled during this code. Be sure to re-enable them afterwards!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

WDT Interrupt Service Routine The next thing to worry about is handling the WDT ISR. This is done as such:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU Sleep

Rather than put the MCU to sleep inside of the WDT ISR, I recommend simply enabling the sleep mode at the end of the ISR, then have the MAIN program put the MCU to sleep. That way, the program is actually leaving the ISR before it goes to sleep, and it will wake up and go directly back into the WDT ISR.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }
\$\endgroup\$
  • \$\begingroup\$ WOW... very detailed. Thanks! I'm a little confused by the sleep section where you show the main loop (if (MCUCR & _BV(SE)){ // If Sleep is Enabled... etc.) I'm confused why in the main look you would continually disable and enabled interrupts. And the part at the top of that section (set_sleep_mode(SLEEP_MODE_PWR_DOWN); ) Where is that supposed to run? \$\endgroup\$ – Adam Haile Jul 3 '13 at 23:07
  • \$\begingroup\$ OK, The "set_sleep_mode(MODE)" part should be in main BEFORE the main loop, ideally in the other initialization code where you setup the I/O ports, timers, etc. You don't really need to enable_sleep(); at that point, since it will be done after your first WDT trigger. INSIDE of the main loop, that sleep code will only execute IF sleep is enabled, and it isn't entirely necessary to disable/reenable the interrupts there, only if you are doing the sleep_bod_disable(); That entire IF statement could be at the bottom (but still inside) of the MAIN loop after any other code you have executing there. \$\endgroup\$ – Kurt E. Clothier Jul 3 '13 at 23:27
  • \$\begingroup\$ Ok... that makes more sense now. Only last thing I'm fuzzy on is what this "timed sequence" is... \$\endgroup\$ – Adam Haile Jul 3 '13 at 23:53
  • \$\begingroup\$ Side note: I know why you would want to go to sleep, but I assume that you don't have to when using the WDT? \$\endgroup\$ – Adam Haile Jul 4 '13 at 0:10
  • \$\begingroup\$ Take a look at this little section of the datasheet: 8.4.1. Basically, to change the WDT register, you have to set the change bit and then set the proper WDTCR bits within so many clock cycles. The code I supplied in the WDT Setup section does this by first enabling the WD change bit. And no, you do not have to use the sleep functionality at all with the WDT. It could be a standard timed interrupt source for whatever reason you want. \$\endgroup\$ – Kurt E. Clothier Jul 4 '13 at 0:15
1
\$\begingroup\$

According to the datasheet it is possible. You can even enable both, an interrupt and the reset. If both are enabled, the first watchdog timeout will trigger the interrupt which causes the Interrupt Enable bit to be unset (interrupt disabled). The next timeout will then reset your CPU. If you enable the Interrupt directly after it was executed, the next timeout will (again) trigger only an interrupt.

You can also just enable the interrupt and not enable the reset at all. You will have to set the WDIE bit every time the interrupt has triggered.

\$\endgroup\$
  • \$\begingroup\$ Hmmm.... I think that makes sense. I will give it a shot. I always like using things for what they were not intended :) \$\endgroup\$ – Adam Haile Jul 3 '13 at 18:20
  • \$\begingroup\$ Actually I think it's a clever design. Saves you a timer while keeping watchdog functionality. \$\endgroup\$ – Tom L. Jul 3 '13 at 18:21
  • 2
    \$\begingroup\$ This initial timeout of the WDT and subsequent interrupt can also be used to advantage for some applications that are enabling the WDT for actual hangup recovery. One can look at the stacked return address in the WDT ISR to infer what the code was trying to be doing when the "unexpected timeout" happened. \$\endgroup\$ – Michael Karas Jul 3 '13 at 21:03
1
\$\begingroup\$

This is much easier than suggested above and elsewhere.

As long as the WDTON fuse is not programmed (it is not programmed by default), then you need only...

  1. Set the watchdog interrupt enable bit and the timeout in the watchdog control register.
  2. Enable interrupts.

Here is a code example that will execute an ISR once per 16ms...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

That is really it. Since we never enable the watchdog reset, we never have to mess around with the timed sequences to disable it. The watchdog interrupt flag automatically gets cleared when the ISR is called.

If you want a period different than every 1 second, you can use these values here to set the apropriate bits in WDTCR...

enter image description here

Note that you do need to execute the timed sequence to change the timeout. Here is code that sets the timeout to 1 second...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
\$\endgroup\$
  • \$\begingroup\$ Not performing the timed sequence during setup saves one line of code - a read-modify-write operation. The initial sequence is recommended in the datasheet "If the Watchdog is accidentally enabled, for example by a runaway pointer or brown-out condition" and the rest of my code is specific to using the WDT in combination with a sleep mode, as requested by the OP. Your solution is no simpler, you just neglected the recommended/required boiler plate code. \$\endgroup\$ – Kurt E. Clothier Apr 23 '18 at 14:54
  • \$\begingroup\$ @KurtE.Clothier Sorry, just trying to give the simplest working example! \$\endgroup\$ – bigjosh Apr 23 '18 at 18:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.