WDT
module reference#include <wdt.h>Makefile :
wdt
is already included by default
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 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 :
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 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.
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.true
if the watchdog is enabled.The WDT has a few more features that are not implemented in the driver :
WDTAUTO
bit in the Flash User Page (ยง14.11.2 First Word of the User Page) and the CTRL.DAR
bitCTRL.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#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
#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; } }