Watchdog Timer — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 19, 2025
Overview
The TM4C123GH6PM includes two independent watchdog timers: WDT0 (on the APB bus) and WDT1 (on the AHB bus, always-on in deep sleep). A watchdog timer generates a reset if software fails to periodically “pet” (reset) it within a defined timeout. This prevents the system from hanging indefinitely due to software bugs, infinite loops, or stack corruption.
Beginner Level — What & Why
What is a Watchdog Timer?
A watchdog is a hardware timer that counts down from a loaded value. If your software does not reset the timer before it reaches zero, the watchdog assumes the system has hung and issues a hardware reset. Your software must “pet the dog” (write to the reload register) regularly to prove it is still running.
Real-World Analogy
A watchdog is like a “dead man’s switch” on a train. As long as the operator pushes the button regularly, the train runs. If the operator becomes incapacitated (software hang) and stops pressing the button, the train automatically brakes (microcontroller resets). The system is safe even without any human (software) intervention.
What Problem Does It Solve?
- Automatic recovery from software deadlocks, infinite loops, and stack corruption
- Ensures the system returns to a known-good state without manual intervention
- Required by safety standards (IEC 61508, ISO 26262) for reliable embedded systems
Key Terms
| Term | Meaning |
|---|---|
| WDT0 | Watchdog Timer 0 on APB — standard peripheral |
| WDT1 | Watchdog Timer 1 on AHB — always clocked in deep sleep |
| WDTLOAD | Reload value register — sets timeout period |
| WDTVALUE | Current countdown value (read-only) |
| WDTCTL | Control register — enable interrupt and reset |
| WDTLOCK | Write 0x1ACCE551 to unlock, any other value to lock |
| Pet/Feed | Writing WDTLOAD to reset the countdown |
Intermediate Level — How It Works
WDT0 vs WDT1
| Feature | WDT0 | WDT1 |
|---|---|---|
| Bus | APB | AHB |
| Clock | System clock | System clock |
| Deep sleep | Stops | Continues |
| Reset cause | WDTSTAT bit 1 (WDT0) | WDTSTAT bit 2 (WDT1) |
| Base address | 0x40000000 |
0x40001000 |
| Recommended use | Normal operation | Extended sleep protection |
Key Registers
| Register | Offset | Description |
|---|---|---|
| WDTLOAD | 0x000 |
Load value (countdown starts here) |
| WDTVALUE | 0x004 |
Current counter value (read-only) |
| WDTCTL | 0x008 |
Control: INTEN (bit0), RESEN (bit1), INTTYPE (bit2) |
| WDTRIS | 0x00C |
Raw interrupt status |
| WDTMIS | 0x010 |
Masked interrupt status |
| WDTICR | 0x00C |
Interrupt clear (writing clears timeout interrupt) |
| WDTTEST | 0x418 |
Test register (STALL bit for CCS debug halt) |
| WDTLOCK | 0xC00 |
Lock register |
WDTCTL bit fields:
| Bit | Name | Function |
|---|---|---|
| 0 | INTEN | Enable watchdog interrupt (first timeout fires ISR) |
| 1 | RESEN | Enable reset on second timeout |
| 2 | INTTYPE | 0 = maskable IRQ, 1 = NMI (non-maskable) |
Two-Stage Timeout Behavior
With INTEN=1 and RESEN=1:
- First timeout: Watchdog fires an interrupt (ISR can still pet the dog)
- Second timeout (if not cleared): Generates a system reset
Setting INTTYPE=1 makes the first timeout a Non-Maskable Interrupt (NMI) — it cannot be disabled even with IntMasterDisable().
TivaWare DriverLib API
#include "driverlib/watchdog.h"
/* Enable WDT0 clock */
SysCtlPeripheralEnable(SYSCTL_PERIPH_WDOG0);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_WDOG0));
/* Set timeout: 1 second at 80 MHz = 80,000,000 cycles */
WatchdogReloadSet(WATCHDOG0_BASE, 80000000);
/* Enable watchdog interrupt (first timeout = IRQ) */
WatchdogIntEnable(WATCHDOG0_BASE);
/* Enable reset on second timeout */
WatchdogResetEnable(WATCHDOG0_BASE);
/* Unlock registers before modifying (if previously locked) */
/* WatchdogUnlock(WATCHDOG0_BASE); */
/* Enable the watchdog — once enabled, RESEN cannot be cleared */
WatchdogEnable(WATCHDOG0_BASE);
/* Lock the watchdog to prevent accidental disable */
WatchdogLock(WATCHDOG0_BASE);
/* Enable watchdog interrupt in NVIC */
IntEnable(INT_WATCHDOG);
IntMasterEnable();
Feeding (Petting) the Watchdog
/* Reset the countdown — call this regularly to prevent timeout */
void WatchdogFeed(void)
{
WatchdogReloadSet(WATCHDOG0_BASE, 80000000); // reload = reset countdown
}
Advanced Level — Deep Dive
Bare-Metal Watchdog Configuration
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_watchdog.h"
void WDT0_Init(uint32_t ui32Timeout)
{
/* Enable WDT0 clock */
HWREG(SYSCTL_RCGCWD) |= (1U << 0);
while (!(HWREG(SYSCTL_PRWD) & (1U << 0)));
/* Unlock the watchdog (write magic key 0x1ACCE551) */
HWREG(WATCHDOG0_BASE + WDT_O_LOCK) = WDT_LOCK_UNLOCK; // 0x1ACCE551
/* Set reload value */
HWREG(WATCHDOG0_BASE + WDT_O_LOAD) = ui32Timeout;
/* Enable interrupt (INTEN=1) and reset (RESEN=1) */
HWREG(WATCHDOG0_BASE + WDT_O_CTL) = WDT_CTL_INTEN | WDT_CTL_RESEN;
/* Enable stall-in-debug (stops WDT when CCS halts CPU) */
HWREG(WATCHDOG0_BASE + WDT_O_TEST) |= WDT_TEST_STALL;
/* Lock registers to prevent accidental modification */
HWREG(WATCHDOG0_BASE + WDT_O_LOCK) = WDT_LOCK_LOCKED; // any value ≠ key
/* Enable NVIC interrupt for WDT0 (IRQ 18) */
HWREG(NVIC_EN0) = (1U << (INT_WATCHDOG - 16));
}
/* Feed the watchdog — must be called before timeout */
void WDT0_Feed(void)
{
/* Unlock to allow writing LOAD */
HWREG(WATCHDOG0_BASE + WDT_O_LOCK) = WDT_LOCK_UNLOCK;
HWREG(WATCHDOG0_BASE + WDT_O_LOAD) = 80000000; // reload 1 second
HWREG(WATCHDOG0_BASE + WDT_O_LOCK) = WDT_LOCK_LOCKED;
}
Watchdog ISR (First Timeout Handler)
void Watchdog_Handler(void)
{
/* Clear the watchdog interrupt (WDTICR) */
WatchdogIntClear(WATCHDOG0_BASE);
/* Log the event, attempt recovery */
/* WARNING: if we return without petting the dog,
* the second timeout (RESEN) will generate a hardware reset */
/* Attempt emergency pet if we can safely do so */
/* WatchdogFeed(); */
/* Or intentionally let the second timeout reset the system */
}
Detecting Watchdog Reset at Startup
After a watchdog reset, firmware can determine the cause by reading the Reset Cause Register (RESC):
void CheckResetCause(void)
{
uint32_t ui32ResetCause = SysCtlResetCauseGet();
SysCtlResetCauseClear(ui32ResetCause); // clear for next boot
if (ui32ResetCause & SYSCTL_CAUSE_WDOG0)
{
/* System was reset by WDT0 — log it, notify user */
/* Set a flag in a non-volatile RAM region preserved across resets */
}
else if (ui32ResetCause & SYSCTL_CAUSE_POR)
{
/* Power-on reset — normal startup */
}
else if (ui32ResetCause & SYSCTL_CAUSE_EXT)
{
/* External reset pin was asserted */
}
}
Reset Cause Register (RESC) Bits
| Bit | Symbol | Cause |
|---|---|---|
| 0 | EXT | External reset pin |
| 1 | POR | Power-on reset |
| 2 | BOR | Brown-out reset |
| 3 | WDT0 | Watchdog Timer 0 reset |
| 4 | SW | Software reset (SysCtlReset) |
| 5 | WDT1 | Watchdog Timer 1 reset |
| 20 | MOSCFAIL | Main oscillator failure reset |
Choosing the Right Timeout
| System type | Recommended timeout |
|---|---|
| Simple polling loop (10 ms period) | 50–100 ms |
| RTOS with 1 ms tick | 50–200 ms |
| ADC + control loop (100 Hz) | 100–500 ms |
| Slow communication with host | 1–5 seconds |
The timeout must be shorter than the maximum acceptable hang time but longer than the maximum time between pet operations including all possible blocking calls.
Gotchas
- Once WDTCTL is enabled (INTEN=1), it cannot be disabled by software. Only a reset clears it. This is intentional — a misbehaving program cannot disable its own watchdog.
- Locking the watchdog with
WatchdogLock()prevents any register writes (including LOAD). You must unlock before petting if you locked. - CCS debug halt problem: When you halt the CPU in CCS, the watchdog continues counting and will reset the MCU. Set the STALL bit in WDTTEST to pause the watchdog during debug halts.
- WDT1 and deep sleep: Use WDT1 if the system enters deep sleep, as WDT0 stops when the APB clock is gated.
- Do not pet the dog in an ISR (unless absolutely necessary) — if the main loop hangs and only ISRs run, the dog being fed by an ISR gives a false “healthy” indication.
Step-by-Step Example
/*
* watchdog_demo.c
* WDT0 configured for 1-second timeout.
* Main loop pets the dog every 500 ms.
* SW1 (PF4) held down simulates a hang — WDT resets the system.
* After reset, the firmware detects the WDT cause and blinks blue LED.
* Board : TM4C123GXL EK LaunchPad
* SDK : TivaWare_C_Series-2.2.x
*/
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/watchdog.h"
#include "driverlib/interrupt.h"
static bool g_bWdtReset = false;
void Watchdog_Handler(void)
{
WatchdogIntClear(WATCHDOG0_BASE);
/* Do not pet — allow second timeout to reset system */
}
int main(void)
{
/* Step 1: 80 MHz clock */
SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
/* Step 2: Check if last reset was caused by WDT */
uint32_t ui32Cause = SysCtlResetCauseGet();
SysCtlResetCauseClear(ui32Cause);
if (ui32Cause & SYSCTL_CAUSE_WDOG0)
g_bWdtReset = true;
/* Step 3: Configure LEDs and SW1 */
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_4);
GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_4,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
/* Step 4: Indicate WDT reset with blue LED on boot */
if (g_bWdtReset)
{
/* Blink blue LED 5 times to signal WDT reset */
int i;
for (i = 0; i < 5; i++)
{
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2);
SysCtlDelay(SysCtlClockGet() / 6); // 500 ms
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0);
SysCtlDelay(SysCtlClockGet() / 6);
}
}
/* Step 5: Configure WDT0 for 1-second timeout */
SysCtlPeripheralEnable(SYSCTL_PERIPH_WDOG0);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_WDOG0));
WatchdogReloadSet(WATCHDOG0_BASE, SysCtlClockGet()); // 1 second
WatchdogIntEnable(WATCHDOG0_BASE);
WatchdogResetEnable(WATCHDOG0_BASE);
/* Pause watchdog during CCS debug halt */
WatchdogStallEnable(WATCHDOG0_BASE);
WatchdogEnable(WATCHDOG0_BASE);
WatchdogLock(WATCHDOG0_BASE);
IntEnable(INT_WATCHDOG);
IntMasterEnable();
/* Step 6: Normal operation — pet the dog every 500 ms
* Hold SW1 to simulate a hang (no pet → WDT resets) */
while (1)
{
/* Normal heartbeat: green LED on */
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3);
SysCtlDelay(SysCtlClockGet() / 6); // 500 ms
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, 0);
SysCtlDelay(SysCtlClockGet() / 6); // 500 ms
/* If SW1 is held: do not pet → WDT fires and resets */
if (!GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4))
{
/* Simulate hang: Red LED on, no pet */
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_PIN_1);
while (!GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4));
/* When WDT fires, Watchdog_Handler clears ISR but does NOT pet
→ second timeout → hardware reset */
}
/* Pet the watchdog (unlock → reload → lock) */
WatchdogUnlock(WATCHDOG0_BASE);
WatchdogReloadSet(WATCHDOG0_BASE, SysCtlClockGet());
WatchdogLock(WATCHDOG0_BASE);
}
}
Summary
| Key Point | Details |
|---|---|
| Modules | WDT0 (APB) and WDT1 (AHB, deep-sleep capable) |
| Timeout | WDTLOAD / system_clock = seconds |
| Two-stage | First timeout = IRQ, second timeout = reset |
| Lock key | 0x1ACCE551 to unlock, any other to lock |
| Debug stall | WDTTEST STALL bit — pauses WDT on CCS halt |
| Cannot disable | INTEN/RESEN cannot be cleared after INTEN set |
| Reset detection | SysCtlResetCauseGet() checks SYSCTL_CAUSE_WDOG0 |
| Pet operation | WatchdogReloadSet (after unlock) |