I have had an ongoing Teensy project that is rather complex. Once in a great while (months), one of the units will hang somewhere in networking code. This is unacceptable and I’ve needed to implement a WDT, but I’ve been hesitant because I’ve not seen an obvious and simple example and I’ve been under the impression it is easy to brick a Teensy if the WDT is not implemented correctly.
After two network hangs in a matter of weeks, I decided I couldn’t put off WDT any longer.
This post is a summary of my research of implementing WDT with (I hope) simple example programs to show how to implement it yourself.
Resources
My primary resources have been:
This Teensy forum thread:
https://forum.pjrc.com/threads/25370-Teensy-3-0-Watchdog-Timer
and the microcontroller manual.
WDT Initialization Code
For WDT to be available, it has to be initialized within 256 clock cycles after boot. Most of the example code I’ve seen deals with implementing a hook called start_early_hook which is called withing the 256 clock cycles. WDT is then enabled in that hook.
I really don’t want WDT resetting the MCU while everything is first coming up. If there is a hang during the initialization phase, rebooting probably isn’t going to make things better, at least for my own code. I’d rather not start the WDT until initialization is complete.
After doing some research I found the ‘default’ start_early_hook found in llwuh.c and it sets up the WDT to be disabled BUT allow future modifications to the settings:
void startup_early_hook( ) __attribute__ ((weak)); void startup_early_hook( ) { WDOG_STCTRLH = WDOG_STCTRLH_ALLOWUPDATE; if ( PMC_REGSC & PMC_REGSC_ACKISO ) { llwuFlag = llwu_clear_flags( );// clear flags llwu_disable( ); } }
This default is exactly what I want – to be able to startup WDT at the end of my initialization phase!
Simple WDT Timeout and Program Reset
Program to Illustrate WDT Usage
This is about the simplest program I can come up with to illustrate how to implement the WDT. Comments afterwards…
// This sample program shows how to use the watch dog timer (WDT). // When program starts, it turns LED off for 1 seconds. // It then turns LED on, and pauses for 5 seconds letting you know it is ready to start. // WDT is initialized, and loop starts. It will flash led and reset wdt for the first 10 secs. // After that, it stops resetting wdt. This causes the WDT to reboot and the cycle starts over. const int led = 13; bool ledState = false; unsigned long timer; void setup() { // initialize the digital pin as an output. pinMode(led, OUTPUT); // Indicate we are starting over by hold led off for 1s ledState = false; digitalWrite(led, ledState); delay(1000UL); // Indicate we are in setup by hold LED on ledState = true; digitalWrite(led, ledState); delay(5000UL); // Setup WDT noInterrupts(); // don't allow interrupts while setting up WDOG WDOG_UNLOCK = WDOG_UNLOCK_SEQ1; // unlock access to WDOG registers WDOG_UNLOCK = WDOG_UNLOCK_SEQ2; delayMicroseconds(1); // Need to wait a bit.. // for this demo, we will use 1 second WDT timeout (e.g. you must reset it in < 1 sec or a boot occurs) WDOG_TOVALH = 0x006d; WDOG_TOVALL = 0xdd00; // This sets prescale clock so that the watchdog timer ticks at 7.2MHz WDOG_PRESC = 0x400; // Set options to enable WDT. You must always do this as a SINGLE write to WDOG_CTRLH WDOG_STCTRLH |= WDOG_STCTRLH_ALLOWUPDATE | WDOG_STCTRLH_WDOGEN | WDOG_STCTRLH_WAITEN | WDOG_STCTRLH_STOPEN | WDOG_STCTRLH_CLKSRC; interrupts(); } void loop() { timer = millis() + 10000UL; // length of time we will reset WDT while (true) { ledState = !ledState; digitalWrite(led, ledState); delay(100UL); if (millis() < timer) { // Have we timed out yet? noInterrupts(); // No - reset WDT WDOG_REFRESH = 0xA602; WDOG_REFRESH = 0xB480; interrupts(); } } // while }
Commentary
The idea behind this program is to setup WDT with a 1 second timeout, start flashing an LED, then after 10 seconds stop resetting WDT so it causes a reboot. You just watch the LED to verify the program works.
To setup WDT, disable interrupts, then write a 2 byte sequence to the WDOG_UNLOCK register. Once you write this sequence, you have 256 clock cycle to complete all WDT settings, so don’t do anything else except WDT setup.
Next I indicate how long I want the WDT timer to run, 1 second in this case. That means I must reset (e.g. kick or pat) the watch dog in < 1 sec or a reboot occurs.
I did a lot of messing around and found that if the prescaler (WDOG_PRESC) is set to 0x400, then the WDT timer runs at about 7.2MHz. (Using a prescaler of 0 gives a clock rate of 36MHz). Note that these values are for my Teensy’s clock of 72MHz. If you over clock, I would assume these values change.
So if I want a 1 second timeout, I would use 7,200,000 which is 0x006DDD00 which is what you see I assign to the WDOG_TOVALH and WDOG_TOVALL registers.
Finally I enable the WDT AND allow future modifications. You are only allowed 1 write to this register after you unlock it, so you must do a single assignment.
Don’t forget to turn interrupts back on.
The program should then be fairly self-explanatory down to the point where I reset the WDT.
To do the reset you are going to write a byte sequence to the WDOG_REFRESH register. This, too, must be done with interrupts off.
Now that I have it up and running, the Teensy WDT is just as easy as the Arduino’s!
Passing Information into the Program after a WDT Reset
Reset Reason
While researching WDT for the Teensy, I found one of “Duff’s” posts in the forum post mentioned in the resources to be quite interesting. He examines the RCM_SRS0 and RCM_SRS1 registers to display why the MCU was reset. I tested those that I could and here is a summary:
RCM_SRS1 & RCM_SRS1_SACKERR Stop Mode Acknowledge Error Reset RCM_SRS1 & RCM_SRS1_MDM_AP MDM-AP Reset RCM_SRS1 & RCM_SRS1_SW Software Reset True when reboot with SCB_AIRCR = 0x05FA0004 RCM_SRS1 & RCM_SRS1_LOCKUP Core Lockup Event Reset RCM_SRS0 & RCM_SRS0_POR Power-on Reset True when power removed/reapplied RCM_SRS0 & RCM_SRS0_PIN External Pin Reset True when reboot due to software download RCM_SRS0 & RCM_SRS0_WDOG Watchdog(COP) Reset True when WDT reboots MCU RCM_SRS0 & RCM_SRS0_LOC Loss of External Clock Reset RCM_SRS0 & RCM_SRS0_LOL Loss of Lock in PLL Reset RCM_SRS0 & RCM_SRS0_LVD Low-voltage Detect Reset Also true when power removed/reapplied
Global DMAMEM Variables
It would be nice to know where I was in the code when the WDT occurred. When the WDT occurs, your program is re-initialized for the most part.
In the program I’m developing this for, I actually maintain a queue of the last <n> events the program processed. For argument’s sake, let’s say I would like to save the last event so it is available to the program when it restarts.
It turns out that if you declare a variable DMAMEM (e.g. DMAMEM int), that variable will NOT be re-initialized when WDT occurs. This allows you to detect and
Here is the example program:
// This sample program shows how to use the watch dog timer (WDT). // To use, download and start Teensy. LED will turn solid red meaning // connect to Teensy with terminal. // Once connected, some information will be printed. After done, you // will be asked to disconnect to proceed with test. // Once you disconnect, the LED will flash fast until WDT reboots the // Teensy at which time the LED is on solid again, awaiting the terminal // to be connected. #include <Arduino.h> #include <kinetis.h> // contains WDOG constants const int led = 13; DMAMEM unsigned int dmaTest; DMAMEM unsigned int dmaTestInit; bool ledState = false; unsigned long timeOut = 0UL; unsigned long timer = 1000UL; void setup() { pinMode(led, OUTPUT); // initialize the digital pin as an output. ledState = true; // while waiting for terminal to connect, hold LED on digitalWrite(led, ledState); while (!Serial.dtr()) { // wait for terminal to connect } // notify user we are connected and running ledState = false; digitalWrite(led, ledState); Serial.println("Terminal Connected."); // Display reason the Teensy was last reset Serial.println(); Serial.println("Reason for last Reset: "); if (RCM_SRS1 & RCM_SRS1_SACKERR) Serial.println("Stop Mode Acknowledge Error Reset"); if (RCM_SRS1 & RCM_SRS1_MDM_AP) Serial.println("MDM-AP Reset"); if (RCM_SRS1 & RCM_SRS1_SW) Serial.println("Software Reset"); // reboot with SCB_AIRCR = 0x05FA0004 if (RCM_SRS1 & RCM_SRS1_LOCKUP) Serial.println("Core Lockup Event Reset"); if (RCM_SRS0 & RCM_SRS0_POR) Serial.println("Power-on Reset"); // removed / applied power if (RCM_SRS0 & RCM_SRS0_PIN) Serial.println("External Pin Reset"); // Reboot with software download if (RCM_SRS0 & RCM_SRS0_WDOG) Serial.println("Watchdog(COP) Reset"); // WDT timed out if (RCM_SRS0 & RCM_SRS0_LOC) Serial.println("Loss of External Clock Reset"); if (RCM_SRS0 & RCM_SRS0_LOL) Serial.println("Loss of Lock in PLL Reset"); if (RCM_SRS0 & RCM_SRS0_LVD) Serial.println("Low-voltage Detect Reset"); Serial.println(); if (dmaTestInit != 0xaaaa) { // on first time run, dmaTestInit contains random data, not the 0xAAA(1010101010...) // we seek. When dmaTestInit is garbage, we give a warning and set it. Serial.println("dmaTest value is invalid."); dmaTestInit = 0xaaaa; } else { // If dmaTestInit is correct, dmaTest has a good value Serial.printf("Current dmaTest Value: %u\n\r", dmaTest); } // give dmaTest a random "good" value and let user know what it will be randomSeed(analogRead(0)); dmaTest = random(0, 32768); Serial.printf("Next dmaTest Value: %u.\n\r", dmaTest); Serial.println(); // You cannot allow Teensy to reboot with USB connected, to tell user to disconnect terminal. Serial.println("Disconnect terminal to proceed with WDT test!"); while (Serial.dtr()) {} ledState = !ledState; // init LED digitalWrite(led, ledState); // enable WDT noInterrupts(); // don't allow interrupts while setting up WDOG WDOG_UNLOCK = WDOG_UNLOCK_SEQ1; // unlock access to WDOG registers WDOG_UNLOCK = WDOG_UNLOCK_SEQ2; delayMicroseconds(1); // Need to wait a bit.. WDOG_TOVALH = 0x006d; // 1 second WDT WDOG_TOVALL = 0xdd00; WDOG_PRESC = 0x400; WDOG_STCTRLH |= WDOG_STCTRLH_ALLOWUPDATE | WDOG_STCTRLH_WDOGEN | WDOG_STCTRLH_WAITEN | WDOG_STCTRLH_STOPEN | WDOG_STCTRLH_CLKSRC; interrupts(); } void loop() { timer = millis() + 10000UL; // length of time we will reset WDT while (true) { ledState = !ledState; digitalWrite(led, ledState); delay(100UL); if (millis() < timer) { // Have we timed out yet? noInterrupts(); // No - reset WDT WDOG_REFRESH = 0xA602; WDOG_REFRESH = 0xB480; interrupts(); } } // while }
Commentary
I used the prior example as the base program, so the actual setup and usage of the WDT is the same.
All of the new code is in the setup function. First, the reason for the last reset is displayed using the RCM_SRC1 and RCM_SRS1 registers.
Next, we look to see if dmaTestInit has a valid value. If not, we can assume dmaTest is not properly set.
Regardless, we then set dmaTestInit to a known value and dmaTest value to some random integer which we tell the user so he can re-check the value of dmaTest on the next WDT reset.
Triggering WDOG Interrupt
In the prior example, I experimented with writing a value to RAM not EEPROM. Why? 2 reasons. First, writing to EEPROM is way slower, which may or may not be an issue depending on how fast my events occur. Second, there are a fixed number of EEPROM writes allowed, about 100,000 (https://www.pjrc.com/teensy/td_libs_EEPROM.html). So I don’t want to go crazy writing to EEPROM.
HOWEVER, it would be really nice to write the LAST event to EEPROM when a WDT reset occurs.
With the Arduino and ATTiny MCUs I’ve used, there has been no graceful way to handle the WDT reset (at least as far a I know). The WDT reset is the same as if the user simply reset power.
This example explores setting up an ISR to be called when the WDT resets. I won’t write EEPROM in this example – I just want to verify I could for when I implement this code into the production project.
Example Program
Here is the example program. It is very close to the prior program with just a few additions:
// This sample program shows how to use the watch dog timer (WDT). // To use, download and start Teensy. LED will turn solid red meaning // connect to Teensy with terminal. // Once connected, some information will be printed. After done, you // will be asked to disconnect to proceed with test. // Once you disconnect, the LED will flash fast until WDT reboots the // Teensy at which time the LED is on solid again, awaiting the terminal // to be connected. #include <Arduino.h> #include <kinetis.h> // contains WDOG constants const int led = 13; DMAMEM unsigned int dmaTest; DMAMEM unsigned int dmaTestInit; bool ledState = false; unsigned long timeOut = 0UL; unsigned long timer = 1000UL; // This routine MUST be named watchdog_isr. It will be called // when the WDT pops and the reset is intiated. // This function must complete in < time than the WDT would take. // In this example, I'm using a WDT timeout of 1s, so it must be done // in < 1 second. void watchdog_isr() { // set dmaTest to 777, letting me know this function was called dmaTest = 777; } void setup() { pinMode(led, OUTPUT); // initialize the digital pin as an output. ledState = true; // while waiting for terminal to connect, hold LED on digitalWrite(led, ledState); while (!Serial.dtr()) { // wait for terminal to connect } // notify user we are connected and running ledState = false; digitalWrite(led, ledState); Serial.println("Terminal Connected."); // Display reason the Teensy was last reset Serial.println(); Serial.println("Reason for last Reset: "); if (RCM_SRS1 & RCM_SRS1_SACKERR) Serial.println("Stop Mode Acknowledge Error Reset"); if (RCM_SRS1 & RCM_SRS1_MDM_AP) Serial.println("MDM-AP Reset"); if (RCM_SRS1 & RCM_SRS1_SW) Serial.println("Software Reset"); // reboot with SCB_AIRCR = 0x05FA0004 if (RCM_SRS1 & RCM_SRS1_LOCKUP) Serial.println("Core Lockup Event Reset"); if (RCM_SRS0 & RCM_SRS0_POR) Serial.println("Power-on Reset"); // removed / applied power if (RCM_SRS0 & RCM_SRS0_PIN) Serial.println("External Pin Reset"); // Reboot with software download if (RCM_SRS0 & RCM_SRS0_WDOG) Serial.println("Watchdog(COP) Reset"); // WDT timed out if (RCM_SRS0 & RCM_SRS0_LOC) Serial.println("Loss of External Clock Reset"); if (RCM_SRS0 & RCM_SRS0_LOL) Serial.println("Loss of Lock in PLL Reset"); if (RCM_SRS0 & RCM_SRS0_LVD) Serial.println("Low-voltage Detect Reset"); Serial.println(); if (dmaTestInit != 0xaaaa) { // on first time run, dmaTestInit contains random data, not the 0xAAA(1010101010...) // we seek. When dmaTestInit is garbage, we give a warning and set it. Serial.println("dmaTest value is invalid."); dmaTestInit = 0xaaaa; } else { // If dmaTestInit is correct, dmaTest has a good value Serial.printf("Current dmaTest Value: %u\n\r", dmaTest); } // give dmaTest a random "good" value and let user know what it will be randomSeed(analogRead(0)); dmaTest = random(0, 32768); Serial.printf("Next dmaTest Value: %u.\n\r", dmaTest); Serial.println(); // You cannot allow Teensy to reboot with USB connected, to tell user to disconnect terminal. Serial.println("Disconnect terminal to proceed with WDT test!"); while (Serial.dtr()) {} ledState = !ledState; // init LED digitalWrite(led, ledState); // enable WDT noInterrupts(); // don't allow interrupts while setting up WDOG WDOG_UNLOCK = WDOG_UNLOCK_SEQ1; // unlock access to WDOG registers WDOG_UNLOCK = WDOG_UNLOCK_SEQ2; delayMicroseconds(1); // Need to wait a bit.. WDOG_TOVALH = 0x006d; // 1 second WDT WDOG_TOVALL = 0xdd00; WDOG_PRESC = 0x400; WDOG_STCTRLH |= WDOG_STCTRLH_ALLOWUPDATE | WDOG_STCTRLH_WDOGEN | WDOG_STCTRLH_WAITEN | WDOG_STCTRLH_STOPEN | WDOG_STCTRLH_CLKSRC | WDOG_STCTRLH_IRQRSTEN; // Tell WDOG to trigger the interrupt interrupts(); NVIC_ENABLE_IRQ(IRQ_WDOG); // enable the call to the watchdog_isr function } void loop() { timer = millis() + 10000UL; // length of time we will reset WDT while (true) { ledState = !ledState; digitalWrite(led, ledState); delay(100UL); if (millis() < timer) { // Have we timed out yet? noInterrupts(); // No - reset WDT WDOG_REFRESH = 0xA602; WDOG_REFRESH = 0xB480; interrupts(); } } // while }
Commentary
To enable the IRQ, you must set the WDOG control register to transmit the WDOG interrupt. You must also call NVIC_ENABLE_IRQ to have the MCU process the interrupt and call the watchdog_isr service routing.
Once that is done, it is just a matter of putting the necessary code in the watchdog_isr.
In this example, I simply overwrite the value of dmaTest in the ISR so I can verify it was called.
This makes it evident the ISR was called. Here is example of the output:
Terminal Connected. Reason for last Reset: Power-on Reset Low-voltage Detect Reset dmaTest value is invalid. Next dmaTest Value: 1298. Disconnect terminal to proceed with WDT test! <Your 'COM30' connection has terminated> Terminal Connected. Reason for last Reset: Watchdog(COP) Reset Current dmaTest Value: 777 Next dmaTest Value: 6826. Disconnect terminal to proceed with WDT test!
The first time connecting to the Teensy, dmaTest is uninitialized and set to 1298.
The WDT resets, calls the ISR, and dmaTest is now set to 777. This is seen when reconnecting to the Teensy.
Conclusion
Implementing the Teensy watchdog timer is nearly as straight-forward as it is for the Arduino or ATTiny 85.
Unlike the Arduino the Teensy can to communicate a WDT reset and pass status via DMAMEM variable (actually maybe Arduino can do this as well and I just have never seen it).
Finally, being able to hook the WDT reset so code can be executed before hand will end up being very useful for my intended project.
As long as you use the default ‘start_early_hook’, it should be about impossible to brick the Teensy. Since it starts up with WDT disabled, loops interrupted by the WDT should not have a chance before you can force a software update.
Update
I implemented the WDT into my production code and found that the eeprom library I’m using isn’t working with the watchdog_isr. I used DMAMEM to pass what I needed to the newly started code and haven’t research the eeprom problem any further.
Nov 2017 Update
To disable WDT, use this code:
NVIC_DISABLE_IRQ(IRQ_WDOG); noInterrupts(); // don't allow interrupts while setting up WDOG WDOG_UNLOCK = WDOG_UNLOCK_SEQ1; // unlock access to WDOG registers WDOG_UNLOCK = WDOG_UNLOCK_SEQ2; delayMicroseconds(1); // Need to wait a bit.. // Set options to enable WDT. You must always do this as a SINGLE write to WDOG_CTRLH WDOG_STCTRLH = WDOG_STCTRLH_ALLOWUPDATE; interrupts();
Dear Dan:
Excelllent and very easy to understand.
Thanks; i will try to start using this in my next projects
rgds
Carlos Frondizi
Very helpful post. Thanks for sharing!
Thank you !
Dan, trying to unsubscribe you -as requested- but currently wordpress won’t let me scroll my followers list. One screen down and all goes blank. I will keep trying
ok, should be done
As Teensy 3.2 is now unavailable did you try to do the same on Teensy 4.0?
I had not heard that so went to the website and see due to supply chain issues, they don’t expect the 3.2 to be available until next year, 2023. Wow!
I have not experimented w/ the Teensy 4.
You might have a look at this post in the Teensy forum: https://forum.pjrc.com/threads/59257-WDT_T4-Watchdog-Library-for-Teensy-4