# Watchdog Timers That Catch Real Firmware Hangs

Build a watchdog timer that catches stalled firmware tasks. RP2040 C example, timeout calculation, reset-cause logging, internal vs. external watchdogs, and tests.

Written by [Ryan Fitzgerald](https://www.flux.ai/ryanf)

Published Nov 2026 - 12 min read

![Watchdog Timers That Catch Real Firmware Hangs](https://www.flux.ai/p/assets/blogs/watchdog-timer-firmware-hangs/main.webp)

Flux can write watchdog firmware for your device, compile it, and run host tests. Describe which jobs must keep making progress, their deadlines, and the reset behavior you want. Its [firmware workflow](https://docs.flux.ai/reference/write-firmware) adds watchdog and error-handling code with build and flash instructions. Its [build-and-test workflow](https://docs.flux.ai/reference/firmware-build-and-test) returns firmware images and test results in your project.

A **watchdog timer** resets a microcontroller when firmware stops refreshing a hardware counter before its timeout. The important design decision is not where to call the refresh function. It is what must happen before a refresh is allowed. Feed the watchdog only when every critical job makes useful progress. Otherwise, a responsive interrupt or a busy main loop can hide a failed sensor task indefinitely.

This guide builds that policy into a complete Raspberry Pi Pico C example. The sensor job stops after five seconds. The status job and main loop keep running. The watchdog still resets the chip, reports the failed job on the next boot, and returns to normal operation.

To have Flux build this policy for your device, open **Chat** in your project and ask:

> “Add a watchdog policy to this industrial sensor controller. Require acquisition progress every 300 ms and status-service progress every 600 ms. Make one supervisor own the feed. Latch missed deadlines, preserve the reset cause, and add host tests for startup, late completion, and counter wrap. Build the firmware and return the image and flashing command.”

## What a watchdog timer detects

A hardware watchdog is a countdown mechanism. Firmware periodically reloads it, also called feeding, kicking, or refreshing it. If the counter expires, hardware requests a reset. This can recover from an infinite loop, a deadlock, or a driver that never returns.

A watchdog does not know whether a measurement is valid or a control output is correct. Your health policy defines that. Mark progress after a complete unit of work: a validated sensor transaction, a processed control cycle, or a communication state-machine step. Do not mark it when the task merely wakes up.

Consider an industrial monitoring device. Its acquisition job can stop while its status indicator and serial interface continue to work. Refreshing the watchdog in the serial job makes the device look healthy forever. A supervisor that checks acquisition progress catches the failure.

## Internal, external, and window watchdogs

| Type | How it works | Design decision |
| --- | --- | --- |
| Internal hardware watchdog | A peripheral inside the MCU expires unless firmware refreshes it. | Check its clock source, reset coverage, sleep behavior, and debug settings in the MCU documentation. |
| External watchdog IC | A separate device monitors transitions on a watchdog input and asserts an output on timeout. | Choose its supply, timing tolerance, startup behavior, and connection to reset or power control. |
| Window watchdog | A refresh must occur within an allowed time window. Refreshing too early can also trigger a fault. | Use it when excessively fast execution must be detected as well as missing execution. |
| Software health supervisor | Code checks deadlines or progress from several jobs. | Use it to control the hardware feed. It cannot replace hardware when the CPU itself stops executing. |

An internal watchdog is not automatically independent of every MCU clock or power fault. For example, STM32 independent watchdogs use an internal low-speed oscillator, while the Pico SDK configures its watchdog tick from the RP2040 clock system. Make the clock-failure requirement explicit before choosing the architecture.

For a separate component, the [TI TPS3431](https://www.ti.com/product/TPS3431) is one example of a programmable external watchdog with an active-low, open-drain output. Its data sheet specifies timeout tolerance and output behavior. Connect that output to the intended reset path with the correct pull-up and voltage levels. A GPIO driven by a free-running timer is not a health signal: it can keep toggling after application work stops.

## Choose the timeout from a recovery budget

Separate the **job deadline** from the **hardware watchdog timeout**. The job deadline says how long useful progress can be absent. The hardware timeout says how long the chip can run after the final permitted feed.

Use these settings for the worked example. They are design inputs, not measured board results.

| Item | Value | Meaning |
| --- | --- | --- |
| Sensor job period | 100 ms | Read the internal ADC temperature channel. |
| Sensor progress deadline | 300 ms | Stop feeding if the latest completed sample becomes older than this. |
| Status job period / deadline | 250 ms / 600 ms | Print the latest raw ADC value; monitor this path separately. |
| Supervisor period | 50 ms | Check both jobs and conditionally refresh. |
| Hardware timeout | 1,000 ms | Reset after feeds stop. |

For a job that stops making progress, a conservative reset bound is **job deadline + supervisor period + maximum hardware timeout**. Here that is 300 + 50 + 1,000 = **1,350 ms** from the last completed sensor sample, before boot time. If the entire CPU hangs immediately after a feed, the hardware timeout alone bounds the wait for reset.

For a production requirement, use the watchdog's maximum actual timeout, including clock and component tolerances, in that recovery bound. Also verify that the minimum actual timeout exceeds the longest legitimate feed gap. Include interrupt latency, flash erase, startup, firmware update, and sleep transitions. Do not make a network outage a reset condition: progress through a bounded retry or offline state is still useful work.

## Worked example: a stalled sensor with a live main loop

Use a Raspberry Pi Pico with the [Pico C/C++ SDK](https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_watchdog). The example uses two cooperative jobs, so it needs no RTOS or external sensor. The on-chip temperature ADC supplies real samples. The same progress policy can supervise RTOS tasks.

Save the following as `main.c`. The timestamps advance only after the jobs finish. A fault is latched: a late sensor sample cannot resume feeding after a deadline violation.

```c
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/watchdog.h"

#define RECORD_MAGIC 0x57444731u
#define SENSOR_FAULT 1u
#define STATUS_FAULT 2u

int main(void) {
    stdio_init_all();
    const bool watchdog_boot = watchdog_enable_caused_reboot();
    const bool record_valid = watchdog_hw->scratch[0] == RECORD_MAGIC;
    const uint32_t previous_fault = record_valid
        ? watchdog_hw->scratch[1] : 0u;
    printf("boot: watchdog=%u fault_mask=%lu\n",
           (unsigned)watchdog_boot,
           (unsigned long)(watchdog_boot ? previous_fault : 0u));
    watchdog_hw->scratch[0] = RECORD_MAGIC;
    watchdog_hw->scratch[1] = 0u;

    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4);

    const uint32_t start = to_ms_since_boot(get_absolute_time());
    uint32_t sample_at = 0, status_at = 0, check_at = 0;
    uint32_t sample_done = 0, status_done = 0;
    bool sample_seen = false, status_seen = false;
    uint32_t latched_fault = 0;
    uint16_t raw = 0;
    watchdog_enable(1000, false);

    while (true) {
        const uint32_t now =
            to_ms_since_boot(get_absolute_time()) - start;
        const bool stall_sensor = !watchdog_boot && now >= 5000u;

        if ((uint32_t)(now - sample_at) >= 100u) {
            sample_at = now;
            if (!stall_sensor) {
                raw = adc_read();
                const uint32_t completed =
                    to_ms_since_boot(get_absolute_time()) - start;
                if ((uint32_t)(completed - sample_done) > 300u)
                    latched_fault |= SENSOR_FAULT;
                sample_done = completed;
                sample_seen = true;
            }
        }

        if ((uint32_t)(now - status_at) >= 250u) {
            status_at = now;
            printf("sample=%u sensor_stalled=%u\n",
                   (unsigned)raw, (unsigned)stall_sensor);
            const uint32_t completed =
                to_ms_since_boot(get_absolute_time()) - start;
            if ((uint32_t)(completed - status_done) > 600u)
                latched_fault |= STATUS_FAULT;
            status_done = completed;
            status_seen = true;
        }

        const uint32_t checked =
            to_ms_since_boot(get_absolute_time()) - start;
        if ((uint32_t)(checked - check_at) >= 50u) {
            check_at = checked;
            if ((uint32_t)(checked - sample_done) > 300u)
                latched_fault |= SENSOR_FAULT;
            if ((uint32_t)(checked - status_done) > 600u)
                latched_fault |= STATUS_FAULT;
            watchdog_hw->scratch[1] = latched_fault;
            if (sample_seen && status_seen && !latched_fault)
                watchdog_update();
        }
        sleep_ms(1);
    }
}
```

The watchdog starts with a one-second allowance. Both jobs must complete before the first refresh. Each completion checks the gap against the previous timestamp before replacing it, so a slow job cannot erase a missed deadline. The initial zero timestamps also enforce startup deadlines: a sensor that never completes fails after 300 ms. Unsigned timestamp subtraction handles a 32-bit millisecond counter wrap for these short intervals.

The injected fault stops *sensor progress*, not the whole processor. Status messages continue with `sensor_stalled=1`. The supervisor detects an old sensor timestamp and stops feeding. After the watchdog reset, `watchdog_boot` suppresses the injection so the device can recover. Remove the injection condition for a release build.

### Build and flash the example

With the Pico SDK and ARM embedded toolchain installed, put `main.c` and this `CMakeLists.txt` in one directory. Set `PICO_SDK_PATH` to your SDK checkout.

```cmake
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(watchdog_demo C CXX ASM)
set(CMAKE_C_STANDARD 11)
pico_sdk_init()
add_executable(watchdog_demo main.c)
target_link_libraries(watchdog_demo pico_stdlib hardware_adc hardware_watchdog)
pico_enable_stdio_uart(watchdog_demo 1)
pico_enable_stdio_usb(watchdog_demo 0)
pico_add_extra_outputs(watchdog_demo)
```

```bash
cmake -S . -B build -DPICO_BOARD=pico
cmake --build build -j4
```

Hold BOOTSEL while connecting the Pico, then copy `build/watchdog_demo.uf2` to the RPI-RP2 drive. Read UART0 at 115200 baud through a 3.3 V USB-to-UART adapter: Pico GP0 to adapter RX, plus a common ground. The Pico's USB connector is used for flashing; this example prints through UART, not USB serial.

## Keep the reset cause, then recover deliberately

Expected behavior is an initial `watchdog=0` boot, changing ADC samples, a deliberately frozen sample with continuing status messages, then a `watchdog=1 fault_mask=1` boot. Samples should change again after restart. These are acceptance criteria for your hardware test, not a captured serial trace.

The example keeps a compact fault mask in RP2040 watchdog scratch registers 0 and 1. The [SDK reset-cause function](https://github.com/raspberrypi/pico-sdk/blob/2.1.1/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h) uses its own marker in scratch register 4. Read the reset cause before enabling the watchdog again. Check the marker before interpreting your application record, and do not treat these registers as a permanent power-loss log.

If the CPU locks before the supervisor records a fault, the watchdog can still reset it. A zero fault mask then means no software deadline was recorded, not that the application was healthy. For a field device, also preserve the firmware version, boot count, reset flags, and last completed operation in an appropriate retained record.

Recovery is a device behavior, not just an MCU restart. Put motor enables, heater outputs, and power switches in a safe electrical state during reset. Reinitialize peripherals, discard stale samples, and qualify new data before enabling outputs. An MCU reset does not necessarily reset an external sensor. Provide bounded bus recovery or a controlled sensor power cycle where the product needs it.

Repeated watchdog boots should enter a defined safe recovery mode instead of cycling an actuator indefinitely. Record enough information to diagnose the fault, then use bounded retries. Keep reset-time safety independent of a log write or a network connection.

## Test the policy, not just the reset instruction

| Test | Expected result |
| --- | --- |
| Normal sampling for longer than the recovery interval | No watchdog reset; both jobs keep meeting their deadlines. |
| Leave the supplied sensor-stall injection enabled | Status messages continue, then reset occurs with sensor fault bit 1. |
| Skip status-job work and its completion timestamp | The supervisor records status fault bit 2 and stops feeding. |
| Insert an infinite loop after watchdog enable | Hardware resets even when the supervisor never runs. |
| Complete a job after its fault has latched | Feeding does not resume before reset. |
| Delay a sensor completion by 400 ms or a status completion by 650 ms | The completed job still latches its missed deadline; a fresh timestamp does not hide the gap. |
| Exercise maximum valid latency, update, and sleep paths | Healthy operation stays inside the measured minimum timeout. |

Run reset tests without a debugger halting the core. This example passes `false` for pause-on-debug. Measure the last feed, reset assertion, and return to useful operation on hardware. Host unit tests can check the deadline arithmetic and fault latching, including startup and timestamp wrap. The hardware test checks the reset path, clocks, peripheral recovery, and output safety.

For an RTOS version, give each critical task a progress counter or completion timestamp. Take a consistent snapshot with atomics or the RTOS synchronization primitives; `volatile` alone is not synchronization. Use one supervisor as the sole hardware-feed owner. A high-priority task or timer interrupt that feeds unconditionally defeats the policy.

## Build the watchdog policy with the whole device in Flux

Flux is an AI hardware engineer. It connects firmware work to the electronics and mechanical design in the same project. Its [firmware workflow](https://docs.flux.ai/reference/write-firmware) includes watchdog and error-handling code, startup order, and build and flash instructions. Its [build-and-test workflow](https://docs.flux.ai/reference/firmware-build-and-test) compiles firmware, runs host tests, and returns image files and debugging notes.

Then connect recovery to the device design. Ask Flux to review the sensor power and reset nets, startup pin states, and programming connections in the [electronics design](https://www.flux.ai/p/eda). For the mechanical assembly, keep the reset and service access reachable in the enclosure. Record sensor position and mounting orientation with the firmware constants using [firmware and hardware in one project](https://docs.flux.ai/reference/firmware-with-your-hardware).

Bring the board's serial log back to the project. Ask Flux to diagnose the reset sequence and update the code and test plan. The result is a recovery policy connected to the board, the enclosure, and the actual device behavior.

[Start Free Trial](https://www.flux.ai/signup) to have Flux build and test watchdog firmware for your device.

## FAQs about watchdog timers

### What is a watchdog timer used for?

A watchdog timer resets a microcontroller or signals a fault when firmware fails to refresh it before a deadline. A health supervisor should permit refreshes only while critical application jobs make useful progress.

### Where should I feed the watchdog?

Feed it from one supervisor after checking the completion timestamps or progress counters of every critical job. Do not feed unconditionally from a timer interrupt, idle hook, or status task.

### How do I choose the watchdog timeout?

Measure the longest valid gap between feeds. Keep that gap below the minimum actual hardware timeout. Check that each job deadline plus supervisor latency plus the maximum actual hardware timeout and boot time fits the product recovery requirement.

### Do I need an external watchdog timer?

Use an external watchdog when your fault analysis requires supervision outside the MCU or control of a reset or power path that the internal watchdog cannot provide. Check the selected part's supply, clock independence, output behavior, and timing tolerance.

### Can a watchdog fix a firmware bug?

A watchdog provides a recovery path. Reset-cause records and fault injection help you find and correct the underlying bug. A complete policy also defines safe output states and what to do after repeated failures.

## About the author

[Ryan Fitzgerald](https://www.flux.ai/ryanf)

Electronics Engineer at Flux

Ryan is an electronics and electrical systems engineer with a focus on bridging the gap between deep learning intelligent algorithms and innovative hardware design. Find him on Flux [@ryanf](https://www.flux.ai/ryanf)

Let’s start building

Build your big idea together with Flux. All you need to do is describe what you want to make, or what problem you want to solve. Flux will work with you to make it a reality.

![Close-up photo of a PCB made with Flux, with an ESP32 module, buttons, and a microSD slot.](https://www.flux.ai/p/assets/blogs/miscellaneous/sidebar-cta-pcb.webp) [Try it Free](https://www.flux.ai/signup)

## Let’s start building.

Let’s build your idea together. All you need to do is describe what you want to make, or what problem you want to solve. Flux will work with you to make it a reality.

[Try it Free](https://www.flux.ai/signup)

 ![Close-up of a Flux-designed printed circuit board](https://www.flux.ai/p/assets/homepage-v2/mission-visual.webp)
