WDT Watchdog timer code

The attached sketch shows the Zero's watchdog timer and sample code to implement it. The code included enables the WDT with a period of about 500 milli-seconds and if not reset during that time, the WDT times out and causes a system reset. This reset returns the processor to the same state as the RESET pushbutton or initial power-on state.

A function systemReset() is included so the programmer can code a system reset at any desired point in time, as long as the WDT is running.

User code may use the included functions:

setupWDT( uint8_t period); // to set up the WDT with any valid period selection

resetWDT(); // must be called by the sketch regularly before the WDT timer times out to prevent the watchdog from resetting the system.

I did not implement the window timer function or the early warning interrupt as my application does not need them. It should not be difficult to add these.

wdt_development.ino (2.76 KB)

Hi rickrlh,

I tried to compile your code but I get the following error :

/Users/username/Downloads/wdt_development/wdt_development.ino: In function 'void WDTsync()':
wdt_development:21: error: 'void WDTsync()' was declared 'extern' and later 'static' [-fpermissive]
 static void   WDTsync() {
                       ^
wdt_development:21: error: previous declaration of 'void WDTsync()' [-fpermissive]
 static void   WDTsync() {
      ^
exit status 1
'void WDTsync()' was declared 'extern' and later 'static' [-fpermissive]

To solve it, I just removed the static keyword before the WDTsync function.

It looks like you already had a WDTsync() function declared. Glad you got it to work. I am looking now at changing the generic clock for the WDT. By default and with the maximum number of clock cycles selected for period, the WDT resets at 500 milli-seconds. My project uses a Vizic SmartGPU2 display screen and some of its functions, such as screenShot take longer than 500 mS. So I would have to re-write their library to include a call to resetWDT() while waiting for the return code from the display. Possible, but messy if they ever update their library.

If I can figure out how to access GCLK->CLKCTRL.ID to select the WDT, then set the clock divider to something like 10, I will then WDT reset in 5 seconds.

It is just some little detail I am missing. I need to write to the bit group ID in CLKCTRL, something like:

GCLK->CLKCTRL.bit.ID=3; // =3 is ID for GCLK_WDT (see Table 14-4 in datasheet)

But this seems to overwrite the whole CLKCTRL register, not the 8-bit second half register.

I could write the whole 16-bit register but I do not know which generic clock generater "GEN" (see table 14-3) the Arduino Zero init code uses.

Does anyone out there know how to do it?

I wrote a library to dump out the samd21 registers. Not all are implemented yet, but you should be able to use it to show the generic clocks.

I've been using it to see how the Arduino samd core changes things, so that I then know what to tweak. (For example, the RTCZero library sets up GEN 2 to XOSC32K with 32 scalar, and I use that to clock EIC as well.

GCLK->CLKCTRL.reg = uint16_t(
    GCLK_CLKCTRL_CLKEN |
    GCLK_CLKCTRL_GEN_GCLK2 |
    GCLK_CLKCTRL_ID( GCLK_CLKCTRL_ID_EIC_Val )
);

)

Thanks, drewfish. I have your library installed and now looking how to use it in a sketch. Do you have a sample to show how to call your functions for GCLK and WDT? I need to see the Zero default GCLK assignments for WDT.

The README.md has an example:

Looks like you care specifically about the settings for the GCLK and WDT peripherals. Here's how you would show just those:

#include <ZeroRegs.h>
void setup() {
    SerialUSB.begin(9600);
    while (! SerialUSB) {}  // wait for serial monitor to attach
    ZeroRegOptions opts = { SerialUSB, false };
    printZeroRegGCLK(opts);
    printZeroRegWDT(opts);
}

(I think that's right but I haven't tested it since I'm visiting family for the holidays and didn't want to fly with random raw electronics in case someone misinterpreted.)

Great! Your library is going to be a wealth of information as I now have "x-ray vision" into the SAMD chip. The WDT assignments do not show until enabled so I added my WDT init code to see what the default is (GCLK_WDT connects to GEN02, and GEN02 is assigned to OSCULP32K). The watchdog barked before the registers could print, so I bumped up the serial baud rate.

Now to set the WDT clock with a divider. Without clock divider the maximum WDT period is 0.5 seconds and I would like a longer period.

For others following this thread, I do not have SerialUSB in use, instead I use Serial. Changing your sample code by find/replace SerialUSB to Serial worked perfectly.

Once again, Drew, thanks. I learned from your code and quickly had the divider GENDIV set and a watchdog timeout "bark" at 10 seconds. Perfect.

When I have my code cleaned up I will post it.

Have a wonderful holiday with your family.

For anyone who found this discussion but still looking for a way to setup the clocks for the WDT... Here is a revised setupWDT for sketch rickrlh provided in the first post to setup the WDT to use 1ms periods allowing up to 16sec WDT periods for the Arduino Zero. I use GENCLK5 as it is not being used in my case...

//============= setupWDT ===================================================== setupWDT ============
void setupWDT( uint8_t period) {
// initialize the WDT watchdog timer
// Do one-time initialization of the watchdog timer.

// Setup GCLK for the watchdog using:
// - Generic clock generator 2 as the source for the watchdog clock
// - Low power 32khz internal oscillator as the source for generic clock
// generator 2.
// - Generic clock generator 2 divisor to 32 so it ticks roughly once a
// millisecond.

// Set generic clock generator 2 divisor to 4 so the clock divisor is 32.
// From the datasheet the clock divisor is calculated as:
// 2^(divisor register value + 1)
// A 32khz clock with a divisor of 32 will then generate a 1ms clock period.
GCLK->GENDIV.reg = GCLK_GENDIV_ID(5) | GCLK_GENDIV_DIV(4);
// Now enable clock generator 2 using the low power 32khz oscillator and the
// clock divisor set above.
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(5) |
GCLK_GENCTRL_GENEN |
GCLK_GENCTRL_SRC_OSCULP32K |
GCLK_GENCTRL_DIVSEL;
while (GCLK->STATUS.bit.SYNCBUSY); // Syncronize write to GENCTRL reg.
// Turn on the WDT clock using clock generator 2 as the source.
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK5;

WDT->CTRL.reg = 0; // disable watchdog
WDTsync(); // sync is required

WDT->CONFIG.reg = min(period,11); // see Table 17-5 Timeout Period (valid values 0-11)

WDT->CTRL.reg = WDT_CTRL_ENABLE; //enable watchdog
WDTsync();
}

1 Like

My apologies for resurrecting and blowing the dust off this ancient thread about the Arduino Zero Watchdog, but I'd thought I'd share some of my observations and difficulties I've had using it.

1) Generic Clock

By default SAMD21's Watchdog Timer (WDT) is connected to generic clock 2 (GCLK2), whose source is the ultra low power 32.768kHz internal oscillator (OSCULP32K). This allows the WDT to operate while the processor is sleeping and if the main oscillator happens to fail during operation.

The SAMD21 datasheet states that the OSCULP32K is capable of generating both a 32.768kHz and 1.024kHz outputs and suggests that by default the WDT is clocked by the 1.024kHz clock, giving a minimium time before reset of 8ms and a maximum of 16s.

I can only assume that the OSCULP32K's 1.024kHz clock output was at one stage intended, but never implemented, as in reality the WDT is connected directly to the 32.768kHz OSCULP32K, resulting in a minimum time before reset of 250us and a maximum of 0.5s, that's 32 times faster than advertised in the datasheet.

It's possible to obtain the 1.024kHz clock for the 8ms and 16s WDT reset times, by just dividing the OSCULP32K's 32.768kHz clock source by 32 in the GCLK2's generic clock divider register. It's also possible to clock the WDT from any other free GCLK.

2) Synchronization Delay

Furthermore, in order to keep the WDT from timing out it's necessary to load its CLEAR register with a value of 0xA5 (WDT_CLEAR_CLEAR_KEY) in your loop(), however the CLEAR register requires synchronization:

REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY;        // Clear the watchdog timer
while(WDT->STATUS.bit.SYNCBUSY);            // Wait for synchronization

The thing is, with the WDT being clocked by a 1.024kHz generic clock, this leads to a massive 5ms synchronization delay, as the code blocks in the while loop. If you insert this code into any high performance application, well...it won't be high performance any longer.

Fortunately, a user named kaktus circuits has come up with a rather neat solution on the hackaday.io website: Found and fixed a bug in sketch for Arduino Zero | Details | Hackaday.io.

The solution is to simply test for synchronization before writing to the WDT's CLEAR register, instead of using a blocking while loop:

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

You can get away with doing this, as normally this the only WDT register you need to access in the loop() portion of your code. The if() statement is just checking that the synchronization of the CLEAR register is not currently taking place, before the register is written. If you're looping faster than 5ms then occasionally you'll skip a write, but this should be OK provided your WDT timeout is long enough. This enables the SAMD21's WDT to be used with high performance applications without any synchronization delay.

Thanks kaktus circuits.

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...
}
1 Like

On the SAMD51 things are a bit easier regarding the micro-controller's Watchdog Timer (WDT).

At power on, the WDT is by default wired up to the micro-controller's internal ultra low power oscillator (OSCULP32K) 1.024kHz output. This means that unlike the SAMD21, no generic clock set-up is required.

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

// Set up the WDT to perform a system reset if the loop() blocks for more than 1 second
void setup() 
{
  REG_WDT_CONFIG = WDT_CONFIG_PER_CYC1024;          // Set the WDT reset timeout to 1 second
  REG_WDT_CTRLA = WDT_CTRLA_ENABLE;                 // Enable the WDT in normal mode
  while(WDT->SYNCBUSY.bit.ENABLE);                  // Wait for synchronization
}

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

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.