WDT Watchdog timer code

Hi MartinL,
Thanks for your detailed information in your posts.
I am trying to implement your code on my SAMD21 however the code won't compile for me.
I am getting the following error;

'WDT_CONFIG_PER_7' was not declared in this scope

If I comment out the following line it compiles but I have not tried to program it into a board yet.

REG_WDT_CONFIG = WDT_CONFIG_PER_7;                // Set the WDT reset timeout to 1 second

Can you provide any advice please?

Hi gjt211,

My apologies, the reason why the code doesn't compile is that the Arduino Zero's CMSIS register definitions have been updated to version 1.2.0. I wrote the code for a custom SAMD21 board using an older CMSIS version.

I've corrected the above code.

Just replace the line:

REG_WDT_CONFIG = WDT_CONFIG_PER_7;                // Set the WDT reset timeout to 1 second

with:

REG_WDT_CONFIG = WDT_CONFIG_PER_1K;              // Set the WDT reset timeout to 1 second

It should now run as advertised. Thanks for the correction.

Hi MartinL, thank you for the extra information. I ended up adding my own WDT_CONFIG_PER_... definitions to my code as I could not work it out myself.
Your way seems much easier.

I have a question on the watch dog, is there now an easy way to disable it?

Hi gjt211,

To disable the watchdog timer, just clear the ENABLE bit in its CTRL register:

WDT->CTRL.bit.ENABLE = 0;                         // Disable the WDT
while(WDT->STATUS.bit.SYNCBUSY);                  // Wait for synchronization

MartinL:
Here's the complete code that'll force a system reset if the loop() blocks for more than 1 second:

// Set up the WDT to perform a system reset if the loop() blocks for more than 1 second

void setup()
{
// Set up the generic clock (GCLK2) used to clock the watchdog timer at 1.024kHz
 REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) |            // Divide the 32.768kHz clock source by divisor 32, where 2^(4 + 1): 32.768kHz/32=1.024kHz
                   GCLK_GENDIV_ID(2);              // Select Generic Clock (GCLK) 2
 while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

REG_GCLK_GENCTRL = GCLK_GENCTRL_DIVSEL |          // Set to divide by 2^(GCLK_GENDIV_DIV(4) + 1)
                    GCLK_GENCTRL_IDC |             // Set the duty cycle to 50/50 HIGH/LOW
                    GCLK_GENCTRL_GENEN |           // Enable GCLK2
                    GCLK_GENCTRL_SRC_OSCULP32K |   // Set the clock source to the ultra low power oscillator (OSCULP32K)
                    GCLK_GENCTRL_ID(2);            // Select GCLK2        
 while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

// Feed GCLK2 to WDT (Watchdog Timer)
 REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |           // Enable GCLK2 to the WDT
                    GCLK_CLKCTRL_GEN_GCLK2 |       // Select GCLK2
                    GCLK_CLKCTRL_ID_WDT;           // Feed the GCLK2 to the WDT
 while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

REG_WDT_CONFIG = WDT_CONFIG_PER_1K;              // Set the WDT reset timeout to 1 second
 while(WDT->STATUS.bit.SYNCBUSY);                  // Wait for synchronization
 REG_WDT_CTRL = WDT_CTRL_ENABLE;                   // Enable the WDT in normal mode
 while(WDT->STATUS.bit.SYNCBUSY);                  // Wait for synchronization
}

void loop()
{
 if (!WDT->STATUS.bit.SYNCBUSY)                // Check if the WDT registers are synchronized
 {
   REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY;        // Clear the watchdog timer
 }
 // Application code goes here...
}

Hi Martin,

I wanted to thank you for posting your code for the SAMD21 watchdog timer (WDT). I'm making the switch over from AVR and it was a great help in getting me started on understanding how everything works.

I'm hoping to use the SAMD21's WDT in conjunction with alarm interrupts from an external real-time clock that will wake my Adafruit Feather M0 from deep sleep. I would really like to get the Early Warning interrupt working with the SAMD21 but have been struggling to understand some aspects of programming registers. Section 18.6.2.1 of the manual lists the steps to enable Normal Mode with Early Warning interrupt as:

  • Define the required Time-Out Period bits in the Configuration register (CONFIG.PER).
  • Define the Early Warning Interrupt Time Offset bits in the Early Warning Interrupt Control register
    (EWCTRL. EWOFFSET).
  • Set Early Warning Interrupt Enable bit in the Interrupt Enable Set register (INTENSET.EW).
    Based on one of your other very helpful post, I was able to find the SAMD21 wdt.h files to look up all the register definitions, but I'm having a bit of trouble putting it all together. It's difficult to know whether I've programmed all the necessary registers to to enable the Early Warning interrupt. There doesn't appear to be very many examples of this functionality being enabled in commonly available libraries, though the Adafruit Sleepydog library does enable the EW interrupt in window mode.

After delving into the Adafruit Sleepydog library, I believe that I'll need to add the following code to what you've provided above:

WDT->EWCTRL.bit.EWOFFSET = 0xA;                   // Set the Early Warning Interrupt Time Offset to 8 seconds
//REG_WDT_EWCTRL = WDT_EWCTRL_EWOFFSET_8K;

WDT->INTENSET.bit.EW = 1;                         // Enable the Early Warning interrupt
//REG_WDT_INTENSET = WDT_INTENSET_EW;               // Is this the equivalent analog comparator register definition?

// Configure and enable WDT interrupt
NVIC_DisableIRQ(WDT_IRQn);
NVIC_ClearPendingIRQ(WDT_IRQn);
NVIC_SetPriority(WDT_IRQn, 0);
NVIC_EnableIRQ(WDT_IRQn);

// Watchdog interrupt service routine (ISR)
void WDT_Handler()
{
WDT->INTFLAG.bit.EW = 1;                        // Clear the Early Warning interrupt flag
//REG_WDT_INTFLAG = WDT_INTFLAG_EW;
WDT->CLEAR.bit.CLEAR = 0xA5;                    // Clear the Watchdog Timer and restart time-out period
//REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY;
while (WDT->STATUS.bit.SYNCBUSY);               // Await synchronization of registers between clock domains
}

I've done some testing with this additional code and everything appears to be working properly. I'm now hoping someone will be able to tell me if I've forgotten or made any mistakes with programming these registers. I'm also not overly worried about performance at this time, and am okay with the synchronization delay in the WDT ISR.

Cheers,
Adam

I have this based on the Adafruit library and datasheet

void watchdogSetup() {
  // One-time initialization of watchdog timer.
  // Insights from rickrlh and rbrucemtl in Arduino forum!

  // Generic clock generator 2, divisor = 32 (2^(DIV+1))
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
  // Enable clock generator 2 using low-power 32KHz oscillator.
  // With /32 divisor above, this yields 1024Hz(ish) clock.
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
                      GCLK_GENCTRL_GENEN |
                      GCLK_GENCTRL_SRC_OSCULP32K |
                      GCLK_GENCTRL_DIVSEL;
  while(GCLK->STATUS.bit.SYNCBUSY);
  // WDT clock = clock gen 2
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
                      GCLK_CLKCTRL_CLKEN |
                      GCLK_CLKCTRL_GEN_GCLK2;

  // Enable WDT early-warning interrupt
  NVIC_DisableIRQ(WDT_IRQn);
  NVIC_ClearPendingIRQ(WDT_IRQn);
  NVIC_SetPriority(WDT_IRQn, 0); // Top priority
  NVIC_EnableIRQ(WDT_IRQn);

  WDT->INTENSET.bit.EW   = 1;      // Enable early warning interrupt
  WDT->EWCTRL.bit.EWOFFSET = 0xA;  // Early Warning Interrupt Time Offset 0xA
  WDT->CONFIG.bit.PER    = 0xB;   // Set period for chip reset 0xB 16384 clock cycles
  WDT->CTRL.bit.WEN      = 0;      // Disable window mode
  while(WDT->STATUS.bit.SYNCBUSY); // Sync CTRL write
  WDT->CTRL.bit.ENABLE = 1; // Start watchdog now!
  while(WDT->STATUS.bit.SYNCBUSY);
}

void watchdogLoop() {
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
  while(WDT->STATUS.bit.SYNCBUSY);
}

void WDT_Handler(void) {  // ISR for watchdog early warning, DO NOT RENAME!
  shutdown();
  WDT->CLEAR.reg = 0xFF; // value different than WDT_CLEAR_CLEAR_KEY causes reset
  while(true);
}

Juraj:
I have this based on the Adafruit library and datasheet

void watchdogSetup() {

// One-time initialization of watchdog timer.
  // Insights from rickrlh and rbrucemtl in Arduino forum!

// Generic clock generator 2, divisor = 32 (2^(DIV+1))
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
  // Enable clock generator 2 using low-power 32KHz oscillator.
  // With /32 divisor above, this yields 1024Hz(ish) clock.
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
                      GCLK_GENCTRL_GENEN |
                      GCLK_GENCTRL_SRC_OSCULP32K |
                      GCLK_GENCTRL_DIVSEL;
  while(GCLK->STATUS.bit.SYNCBUSY);
  // WDT clock = clock gen 2
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
                      GCLK_CLKCTRL_CLKEN |
                      GCLK_CLKCTRL_GEN_GCLK2;

// Enable WDT early-warning interrupt
  NVIC_DisableIRQ(WDT_IRQn);
  NVIC_ClearPendingIRQ(WDT_IRQn);
  NVIC_SetPriority(WDT_IRQn, 0); // Top priority
  NVIC_EnableIRQ(WDT_IRQn);

WDT->INTENSET.bit.EW  = 1;      // Enable early warning interrupt
  WDT->EWCTRL.bit.EWOFFSET = 0xA;  // Early Warning Interrupt Time Offset 0xA
  WDT->CONFIG.bit.PER    = 0xB;  // Set period for chip reset 0xB 16384 clock cycles
  WDT->CTRL.bit.WEN      = 0;      // Disable window mode
  while(WDT->STATUS.bit.SYNCBUSY); // Sync CTRL write
  WDT->CTRL.bit.ENABLE = 1; // Start watchdog now!
  while(WDT->STATUS.bit.SYNCBUSY);
}

void watchdogLoop() {
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
  while(WDT->STATUS.bit.SYNCBUSY);
}

void WDT_Handler(void) {  // ISR for watchdog early warning, DO NOT RENAME!
  shutdown();
  WDT->CLEAR.reg = 0xFF; // value different than WDT_CLEAR_CLEAR_KEY causes reset
  while(true);
}

Thanks @Juraj!

It looks like most of my code is similar to what you have. I notice for your WDT_Handler you're writing 0xFF to the CLEAR register. Shouldn't this be 0xA5? Also, I don't think it's necessary to explicitly disable window mode, as this is the default setting.

Cheers,
Adam

Hi Adam,

What you're doing appears completely correct, although clearing the watchdog in the early warning interrupt might cause problems, as writing to this register will take around 5 to 6 milliseconds to synchronize due to the fact that the WDT peripheral is being clocked by the 32.768kHz oscillator.

adam_g:
Thanks @Juraj!

It looks like most of my code is similar to what you have. I notice for your WDT_Handler you're writing 0xFF to the CLEAR register. Shouldn't this be 0xA5? Also, I don't think it's necessary to explicitly disable window mode, as this is the default setting.

Cheers,
Adam

I force a reset with FF. I needed long watchdog time, but if the interrupt had the same time as reset, the chip did a reset while I handled the interrupt. so I use only the interrupt and in it I force reset

MartinL:
Hi Adam,

What you're doing appears completely correct, although clearing the watchdog in the early warning interrupt might cause problems, as writing to this register will take around 5 to 6 milliseconds to synchronize due to the fact that the WDT peripheral is being clocked by the 32.768kHz oscillator.

Hi Martin,

Thanks for the confirmation that I had all the correct registers. My Watchdog Timer with Early Warning interrupt has been working quite well so far! The 5-6 ms delay isn't too much of a concern given my application (weather station), but I could certainly see how it might cause issues with more complex applications.

Thanks again for posting your WDT code! It was a great help!

Cheers,
Adam

Added a WDTZero Class Libary for slow WDT times.
It defines a Hard Watchdog : for stalled processor resets : 2-16 second interval
It defines a Soft Watchdog via EarlyWarning Timer ISR : for stalled software loops : 4 sec - 10 minutes.

Specificly if you are working with IoT projects, you can have long software loop delays for server access and replies, this WDT can help you resetting when it really takes a long time aka minutes.

[https://github.com/javos65/WDTZero\](GitHub - javos65/WDTZero: Allows to use the WatchDog functionality for Arduino Zero, MKRZero and MKR1000 only)

Have fun.

Any remarks are welcome

@voske65,

Good work! I've been thinking about putting my Watchdog code in its own class for a while now in order to protect it from accidental modification.

Are you using this in conjunction with a low power library? For me, the best solution ended up being to use the official ArduinoLowPower library with the additional Watchdog code and a sleep flag. I also use a callback function that repeatedly pets the Watchdog during longer processes (e.g GPS signal acquisition, Iridium satellite communications, etc.). Incrementing a counter during the callback is another way I imagine you could accomplish the same thing for IoT applications.

If you're interested in the code, you can view it on GitHub. Line 747 for the callback function and 915 onwards for the Watchdog.

Cheers,
Adam

@adam_g @Juraj

Thanks for the feedback, just added a callback function called 'shutdown' to the WDTZero class.
Now you can make your own Shutdown-call function that is run before the soft-timer pulls the reset via the Early warning mechanism

Thnks to Juraj for this suggestion :slight_smile:

Regards- Jay

Hi voske65, your WDTZero looks like exactly what I need for my usually sleeping IoT project. It sometimes hangs for real while communicating with the server so I want a watchdog timer, but it always takes longer than the hardware watchdog timer's 8 or 16 second periods available from various libraries so if I use one of those, the sketch never completes.

Pardon my ignorance or I'm making a mistake because I can't get your library to run. I've tried zipping the .cpp and .h files together and just the .h file, and the github clone .zip, all offered to the Arduino IDE as a library. The IDE has either said not a library or accepted it then not been able to compile.

Maybe I just need to take this literally?

Arduino Zero, MKRZero and MKR1000 only

I hoped that the MKR 1400 would work too, since I thought only the communications varied on the whole MKR series (so far).

Am I missing something?

damonlane:
Hi voske65, your WDTZero looks like exactly what I need for my usually sleeping IoT project. It sometimes hangs for real while communicating with the server so I want a watchdog timer, but it always takes longer than the hardware watchdog timer's 8 or 16 second periods available from various libraries so if I use one of those, the sketch never completes.

Pardon my ignorance or I'm making a mistake because I can't get your library to run. I've tried zipping the .cpp and .h files together and just the .h file, and the github clone .zip, all offered to the Arduino IDE as a library. The IDE has either said not a library or accepted it then not been able to compile.

Maybe I just need to take this literally?I hoped that the MKR 1400 would work too, since I thought only the communications varied on the whole MKR series (so far).

Am I missing something?

WDTZero.png

damonlane:
Hi voske65, your WDTZero looks like exactly what I need for my usually sleeping IoT project. It sometimes hangs for real while communicating with the server so I want a watchdog timer, but it always takes longer than the hardware watchdog timer's 8 or 16 second periods available from various libraries so if I use one of those, the sketch never completes.

Pardon my ignorance or I'm making a mistake because I can't get your library to run. I've tried zipping the .cpp and .h files together and just the .h file, and the github clone .zip, all offered to the Arduino IDE as a library. The IDE has either said not a library or accepted it then not been able to compile.

Maybe I just need to take this literally?I hoped that the MKR 1400 would work too, since I thought only the communications varied on the whole MKR series (so far).

Am I missing something?

@damonlane,

@Juraj's screenshot shows you exactly where the library needs to be located in order for the code to compile properly. However, what it doesn't show is the examples folder. In it, you'll see that there is a WDTZero2.ino file. You need to place this file in a folder called "WDTZero2" in order for it to show up as an example in the Arduino IDE. There is a specific file structure that needs to be adhered to in order for Arduino to recognize the examples.

@voske65 You may want to add this folder on your GitHub repo to help future users.

Cheers,
Adam

Thank you @Juraj and @adam_g! I now have the WDTZero library in the IDE, and the functions correctly compiling in my sketch. Now back to the project's issues....

Thanks, I will adapt the Github :slight_smile:
I was assuming knowledge of Library-file location wouldbe available - no problem

@MartinL You may be happy to hear I've been using the WDT code you provided in a number of instruments in the Arctic over the past few years. Some are still going, in fact!

Recently, I've been looking to transition to using the SAMD21's internal RTC, as opposed to using an external RTC. I've been using the RTCZero library to manage the alarms and general clock-related functions and it's been working well. However, upon closer inspection, I realized that both RTCZero and the WDT code are making use of the same Generic Clock Controller 2. The WDT configures GCLK2 for use with OSCULP32K and the RTC configures GCLK2 for use with XOSC32K (if an external crystal is detected).

Am I correct in assuming that the RTC and WDT should be using separate GCLKs? If so, is there any risk associated with simply changing the WDT to use GCLK1 instead?

Also to note, there have been reports of the WDTZero library conflicting with RTCZero, which I believe is also related to clock configuration.

Cheers,
Adam

@adam_g Thanks, that's nice to know.

Looking at the SAMD21 datasheet there's nothing to stop you connecting the WDT to an alternative 32k clock source, such as the external 32k crystal:

GCLK_WDT can also be clocked from other sources if a more accurate clock is needed, but at the cost of higher power consumption.

Examining the WDTZero and RTCZero libraries, it appears as though the cause of the conflict is that both attempt to re-configure GLK2 as their clock source. The WDT attempts to set it to the internal OSCULP32K ultra low power oscillator, while the RTC attempts to set it to the external crystal XOSC32K. Both libraries divide the 32k generic clock source by 32, to generate a 1.024kHz signal.

You could try setting up the RTCZero library first and then the WDTZero library with the following lines from 58 to 66 commented out in the "WDTZero.cpp" file:

  // Generic clock generator 2, divisor = 32 (2^(DIV+1))  = _x
  //GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(_x);
  // Enable clock generator 2 using low-power 32.768kHz oscillator.
  // With /32 divisor above, this yields 1024Hz clock.
  //GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
  //                    GCLK_GENCTRL_GENEN |
  //                    GCLK_GENCTRL_SRC_OSCULP32K |
  //                    GCLK_GENCTRL_DIVSEL;
  //while(GCLK->STATUS.bit.SYNCBUSY);

This should allow the WDT to simply connect to GCLK2, which has aleady been configured by the RTCZero library for the external crystal XOSC32K clock source.

1 Like