WDT module reference

Header :
#include <wdt.h>
Makefile : wdt is already included by default

Description

The Watchdog Timer (WDT) is a special decrementing 32-bit timer/counter (completely independant from TC and the Systick) with one distinctive feature : if the counter reaches zero the microcontroller is reset.

This feature is especially useful in critical systems to mitigate deadlocks that might happen during the execution of the program : if the watchdog is not periodically cleared (because a subroutine is trapped in an infinite loop for example), a reset will occur and the execution will start again from the beginning.

Ideally, the program should be able to save its execution state and resume its current operation after the reset, for example by configuring the watchdog in interrupt mode (see below) and checking at boot time if the value of PM::resetCause() is PM::ResetCause::WDT.

Keep in mind that the actual timeout will be greater than the value given to enable() because the initial counter value can only be initialized to a power of two.

Interrupt mode

Interrupt Mode is enabled by passing an interrupt handler to init(). In this mode, when the watchdog counter reaches zero, the microcontroller is not immediately reset : instead, the counter starts again and an interrupt is triggered and the given handler is called. The interrupt must be manually cleared by calling clearInterrupt(). If, for some reason, the interrupt is not cleared before the next timeout, the watchdog will reset the microcontroller.

This mode allows you to implement an efficient strategy to deal with deadlocks. For example, the handler may :

  • try to find the source of the deadlock and if it was able to fix it, clear the interrupt to prevent the reset
  • or instead save the current execution, maybe gather information about the error, and either force reset with Core::reset() or simply wait for the watchdog reset on the next timeout.

If the handler returns without clearing the interrupt, the execution will be blocked until the reset occurs to prevent the handler to be immediately called again.

The watchdog interrupt has by default one of the highest priority possible, so it shouldn't be masked by another interrupt constantly activated because of a bug.

Window mode

Window Mode is enabled by passing a value greater than 0 to the windowStart argument of init(). In this mode, a minimum time is required between two clear operations, otherwise the microcontroller is reset in the same way that it would be after a timeout. This can be useful in certain cases to prevent a loop to endlessly clear the watchdog while still blocking the execution.

Examples

API

void enable(unsigned int timeout, Unit unit=Unit::MILLISECONDS, void (*timeoutHandler)()=nullptr, unsigned int windowStart=0, bool useOSC32K=true)
Enable the watchdog to trigger after the given timeout. If a handler is given, Interrupt Mode is enabled. If windowStart is greater than 0 (given in the same unit as timeout), Window Mode will be enabled (see module description above). By default the watchdog is clocked by the OSC32K clock which is more stable, but it can be configured to use RCSYS instead which is faster but less precise.
void disable()
Disable the watchdog.
bool isEnabled()
Return true if the watchdog is enabled.
void clear()
Clear the watchdog. This must be called regularly to prevent a watchdog reset.
void clearInterrupt()
Clear the watchdog interrupt. See Interrupt Mode in the module description above for more information.

Hacking

The WDT has a few more features that are not implemented in the driver :

  • the WDT can be automatically enabled at reset, see the WDTAUTO bit in the Flash User Page (ยง14.11.2 First Word of the User Page) and the CTRL.DAR bit
  • for extra protection, the CTRL.SFV bit (Store Final Value) can be set to prevent further modifications of the CTRL register until the next reset; be careful because this will prevent Core::resetToBootloader(unsigned int delayMs) from working properly

Code

Header

#ifndef _WDT_H_
#define _WDT_H_

#include <stdint.h>

// Watchdog Timer
// This module is able to automatically reset the chip after a
// specified delay unless it is periodically serviced. This is
// useful to recover from an unexpected behaviour which leads the
// execution to hang.
namespace WDT {

    // Peripheral memory space base address
    const uint32_t WDT_BASE = 0x400F0C00;


    // Registers addresses
    const uint32_t OFFSET_CTRL =     0x000; // Control Register
    const uint32_t OFFSET_CLR =      0x004; // Clear Register
    const uint32_t OFFSET_SR =       0x008; // Status Register
    const uint32_t OFFSET_IER =      0x00C; // Interrupt Enable Register
    const uint32_t OFFSET_IDR =      0x010; // Interrupt Disable Register
    const uint32_t OFFSET_IMR =      0x014; // Interrupt Mask Register
    const uint32_t OFFSET_ISR =      0x018; // Interrupt Status Register
    const uint32_t OFFSET_ICR =      0x01C; // Interrupt Clear Register


    // Subregisters
    const uint32_t CTRL_EN = 0;     // WDT Enable
    const uint32_t CTRL_DAR = 1;    // WDT Disable After Reset
    const uint32_t CTRL_MODE = 2;   // WDT Mode
    const uint32_t CTRL_SFV = 3;    // WDT Control Register Store Final Value
    const uint32_t CTRL_IM = 4;     // Interrupt Mode
    const uint32_t CTRL_FCD = 7;    // Flash Calibration Done
    const uint32_t CTRL_PSEL = 8;   // Timeout Prescale Select
    const uint32_t CTRL_CEN = 16;   // Clock Enable
    const uint32_t CTRL_CSSEL = 17; // Clock Source Select
    const uint32_t CTRL_TBAN = 18;  // Time Ban Prescale Select
    const uint32_t CTRL_KEY = 24;   // Key
    const uint32_t CLR_WDTCLR = 0;  // Watchdog Clear
    const uint32_t CLR_KEY = 24;    // Key
    const uint32_t SR_WINDOW = 0;   // Counter inside the PSEL period
    const uint32_t SR_CLEARED = 1;  // WDT cleared
    const uint32_t ISR_WINT = 2;    // Watchdog interrupt


    // Constants
    const uint32_t CTRL_KEY_1 = 0x55 << CTRL_KEY;
    const uint32_t CTRL_KEY_2 = 0xAA << CTRL_KEY;
    const uint32_t CLR_KEY_1 = 0x55 << CLR_KEY;
    const uint32_t CLR_KEY_2 = 0xAA << CLR_KEY;


    enum class Unit {
        MILLISECONDS,
        MICROSECONDS
    };

    // Module API
    void enable(unsigned int timeout, Unit unit=Unit::MILLISECONDS, void (*timeoutHandler)()=nullptr, unsigned int windowStart=0, bool useOSC32K=true);
    void disable();
    bool isEnabled();
    void clear();
    void clearInterrupt();

}


#endif

Module

#include "wdt.h"
#include "scif.h"
#include "bscif.h"
#include "core.h"

namespace WDT {

    // Watchdog interrupt
    extern uint8_t INTERRUPT_PRIORITY;
    void (*_interruptHandler)() = nullptr;
    void interruptHandlerWrapper();

    // Clock source
    bool _useOSC32K = false;

    void enable(unsigned int timeout, Unit unit, void (*timeoutHandler)(), unsigned int windowStart, bool useOSC32K) {
        // If the WDT is already enabled, disable it first
        if (isEnabled()) {
            disable();
        }

        // Interrupt mode
        bool interruptMode = false;
        if (timeoutHandler != nullptr) {
            interruptMode = true;
            _interruptHandler = timeoutHandler;
            // Actual interrupt enabling is done at the end if the function, after the WDT is enabled
        }

        // Window mode
        bool windowMode = false;
        if (windowStart > 0 && windowStart < timeout) {
            windowMode = true;
            timeout -= windowStart; // Assume that timeout starts at T0, not at windowStart
        }

        // Compute timings
        uint8_t psel = 0;
        uint8_t tban = 0;
        if (timeout > 0) {
            // Get the base period of the input clock in 10th of microseconds
            unsigned int basePeriod = 10000000L / (useOSC32K ? BSCIF::getOSC32KFrequency() : SCIF::getRCSYSFrequency());

            // Convert the timeout in units of input clock
            if (unit == Unit::MILLISECONDS) {
                timeout *= 1000; // Convert to microseconds
            }
            timeout *= 10;  // Convert to 10th of microseconds
            timeout = timeout / basePeriod;

            // Find the closest power of two greater than the computed timeout
            // cf datasheet p 491 (20. WDT / 20.6 User Interface / 20.6.1 CTRL Control Register)
            for (psel = 0; psel < 32; psel++) {
                if (psel == 31 || static_cast<unsigned int>(1 << (psel + 1)) >= timeout) {
                    break;
                }
            }

            if (windowMode) {
                // Convert the time in units of input clock
                if (unit == Unit::MILLISECONDS) {
                    windowStart *= 1000; // Convert to microseconds
                }
                windowStart *= 10;  // Convert to 10th of microseconds
                windowStart = windowStart / basePeriod;

                // Find the closest power of two lower than the computed time
                // cf datasheet p 491 (20. WDT / 20.6 User Interface / 20.6.1 CTRL Control Register)
                for (tban = 30; tban >= 0; tban--) {
                    if (static_cast<unsigned int>(1 << (tban + 1)) <= windowStart) {
                        break;
                    }
                }
            }
        }

        // Clock source
        if (useOSC32K != _useOSC32K) {
            // CTRL (Control Register) : disable the watchdog clock
            uint32_t ctrl = (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & 0x00FFFFFF;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = (ctrl & ~(uint32_t)(1 << CTRL_CEN))
                | CTRL_KEY_1;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = (ctrl & ~(uint32_t)(1 << CTRL_CEN))
                | CTRL_KEY_2;

            // Wait for the clock to be disabled
            while ((*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & (1 << CTRL_CEN));

            // Select the clock
            ctrl = (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & 0x00FFFFFF;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = (ctrl & ~(uint32_t)(1 << CTRL_CSSEL))
                | static_cast<int>(useOSC32K) << CTRL_CSSEL
                | CTRL_KEY_1;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = (ctrl & ~(uint32_t)(1 << CTRL_CSSEL))
                | static_cast<int>(useOSC32K) << CTRL_CSSEL
                | CTRL_KEY_2;

            // Enable the watchdog clock again
            ctrl = (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & 0x00FFFFFF;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = ctrl
                | 1 << CTRL_CEN
                | CTRL_KEY_1;
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
                = ctrl
                | 1 << CTRL_CEN
                | CTRL_KEY_2;

            // Wait for the clock to be enabled
            while (!((*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & (1 << CTRL_CEN)));

            _useOSC32K = useOSC32K;
        }

        // CTRL (Control Register) : configure then enable the watchdog
        // The WDT must first be configured, then, in a second time, enabled.
        // The CTRL register must be written twice for each operation, the 
        // first time with the first key (0x55), then with the second key (0xAA).
        // cf datasheet p483 (20. WDT / 20.5 Functional Description / 20.5.1 Basic 
        // Mode / 20.5.1.1 WDT Control Register Access)
        uint32_t ctrl = 1 << CTRL_DAR                               // DAR : disable the watchdog after a reset
                      | static_cast<int>(windowMode) << CTRL_MODE   // MODE : Basic or Window mode
                      | static_cast<int>(interruptMode) << CTRL_IM  // IM : Interrupt Mode
                      | 1 << CTRL_FCD                               // FCD : skip flash calibration after reset
                      | psel << CTRL_PSEL                           // PSEL : timeout counter
                      | 1 << CTRL_CEN                               // CEN : enable the clock
                      | static_cast<int>(useOSC32K) << CTRL_CSSEL   // CEN : enable the clock
                      | tban << CTRL_TBAN;                          // TBAN : time ban for window mode
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) // Configure
            = ctrl
            | CTRL_KEY_1;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
            = ctrl
            | CTRL_KEY_2;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) // Enable, keeping the same configuration
            = ctrl
            | 1 << CTRL_EN
            | CTRL_KEY_1;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
            = ctrl
            | 1 << CTRL_EN
            | CTRL_KEY_2;

        // Wait until the WDT is enabled
        while (!isEnabled());

        // Interrupt mode
        if (interruptMode) {
            // IER (Interrupt Enable Register) : enable the watchdog interrupt
            (*(volatile uint32_t*)(WDT_BASE + OFFSET_IER))
                    = 1 << ISR_WINT;

            // Set the handler and enable the module interrupt at the Core level
            Core::setInterruptHandler(Core::Interrupt::WDT, interruptHandlerWrapper);
            Core::enableInterrupt(Core::Interrupt::WDT, INTERRUPT_PRIORITY);
        }
    }

    bool isEnabled() {
        return (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & (1 << CTRL_EN);
    }

    void disable() {
        // CTRL (Control Register) : disable the watchdog by setting the EN bit to 0
        uint32_t ctrl = (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL)) & 0x00FFFFFF;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
            = (ctrl & ~(uint32_t)(1 << CTRL_EN))
            | CTRL_KEY_1;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CTRL))
            = (ctrl & ~(uint32_t)(1 << CTRL_EN))
            | CTRL_KEY_2;

        // Wait until the WDT is disabled
        while (isEnabled());
    }

    void clear() {
        // SR (Status Register) : wait until the previous clear operation is finished
        while (!((*(volatile uint32_t*)(WDT_BASE + OFFSET_SR)) & (1 << SR_CLEARED)));

        // CLR (Clear Register) : clear the watchdog counter
        // The register must be written twice, first with the key 0x55
        // then with the key 0xAA
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CLR))
            = 1 << CLR_WDTCLR
            | CLR_KEY_1;
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_CLR))
            = 1 << CLR_WDTCLR
            | CLR_KEY_2;
    }

    void interruptHandlerWrapper() {
        // Call the user interrupt handler
        if (_interruptHandler != nullptr) {
            _interruptHandler();
        }

        // If the handler didn't clear the interrupt with clearInterrupt(), loop
        // indefinitely to prevent the handler from being immediately called again.
        // The watchdog will reset the microcontroller after the next timeout.
        if ((*(volatile uint32_t*)(WDT_BASE + OFFSET_ISR)) & (1 << ISR_WINT)) {
            while (true);
        }
    }

    void clearInterrupt() {
        // ICR (Interrupt Clear Register) : clear the watchdog interrupt
        (*(volatile uint32_t*)(WDT_BASE + OFFSET_ICR))
            = 1 << ISR_WINT;
    }
}