Watch Dog Timer (WDT) for Teensy 3.1 and 3.2

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();

 

Advertisements
This entry was posted in c-teensy, Uncategorized and tagged , . Bookmark the permalink.

One Response to Watch Dog Timer (WDT) for Teensy 3.1 and 3.2

  1. carlos frondizi says:

    Dear Dan:
    Excelllent and very easy to understand.

    Thanks; i will try to start using this in my next projects

    rgds
    Carlos Frondizi

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.