Wiznet makers

ruilixin6

Published August 24, 2026 ©

187 UCC

0 VAR

0 Contests

0 Followers

0 Following

Original Link

From Theory to Practice: Pico Multi‑Threaded Watchdog‑Core Logic & Implementation

Core logic and implementation of Pico multi‑threaded software watchdog

COMPONENTS
PROJECT DESCRIPTION

【Preliminary Note】The original hardware example in this article was written based on the RP2040. The actual hardware used in this hands-on demonstration features the W55RP20 as the main controller chip. The circuit logic and UF2 flashing operation principles are universally applicable, with only the main controller model differing. The original chip model mentioned in the circuit descriptions below is provided for reference purposes only.

The core idea of multi‑threaded software watchdog is to use an independent watchdog thread to monitor the status of other worker threads. Each worker thread performs regular “feed‑dog” actions to signal normal operation. The watchdog thread monitors a global counter variable and increments it at fixed intervals. Worker threads clear this global counter after completing certain operations, which is the feed‑dog operation. If the watchdog thread detects that the global counter exceeds a predefined upper limit (for example 2), it judges that the main‑program thread has hung and takes measures to reboot the device.

Two important notes:

First, MicroPython running on RP2040 can only run two threads simultaneously. RP2040 features two cores, and each core can execute one thread only. Attempting to launch more than two threads will cause resource conflicts or other errors. This limitation requires special attention when designing multi‑threaded applications, especially for complex tasks on RP2040. If you need to manage many tasks, use appropriate task‑scheduling and coordination mechanisms such as polling or timers to simulate multi‑thread behaviour instead of spawning actual multiple threads.

All reset‑related operations (such as machine.reset() or machine.soft_reset()) must be executed inside the main thread. Calling them from other threads will trigger exceptions such as OSError. The root cause is that critical system operations like reset involve low‑level hardware and system‑state management. These operations must run in the main thread to guarantee state consistency and proper resource management. Performing reset in non‑main threads may lead to state corruption and resource contention. Furthermore, USB operations are handled by the TinyUSB driver. Reset invokes low‑level USB callback routines. Executing reset outside the main thread can trigger recursive TinyUSB callback invocations, which are prohibited.

Common error outputs when performing reset inside non‑main threads:

>>> FATAL: uncaught exception 20008830 
OSError

or:

>>> [Watchdog] Main thread is unresponsive. System should restart! 
FATAL: uncaught exception 20008830 
OSError:

or:

>>> Watchdog: Main thread is unresponsive. System should restart! 
FATAL: uncaught exception 20008870 
OSError: TinyUSB callback can't recurse

The following code implements a multi‑thread‑based software watchdog timer for monitoring the runtime status of the main thread and feed‑dog thread. When the feed‑dog thread fails to feed‑dog (clear the counter) periodically, the watchdog determines that a system fault has occurred and attempts to reboot the device.

The source code can be found in the provided resource package under elegance‑devkit v1\Demo\54 WDG_Thread.

Sample code:

# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/8/16 11:14 AM   
# @Author  : Li Qingshui            
# @File    : main.py       
# @Description : WDT watchdog timer experiment implemented with Pico multi‑threading
# ======================================== Import related modules ========================================
# Import multi‑thread module
import _thread
# Import time‑related modules
import time
# Import hardware‑related modules
import machine
# Import const constant decorator
from micropython import const
import micropython
# ======================================== Global variables ============================================
# Global variable monitored by watchdog thread
watchdog_counter = 0
# System reboots when counter exceeds this value without feed‑dog
WATCHDOG_MAX_COUNTER = const(2)
# Watchdog checking interval in seconds
WATCHDOG_CHECK_INTERVAL = const(4)
# Feed‑dog thread execution interval in seconds
FEED_INTERVAL = const(1)
# Maximum feed‑dog times, stop feeding after reaching this value
FEED_COUNT = const(10)
# ======================================== Function definitions ============================================
# Allocate emergency‑exception buffer (must be placed before any interrupt‑related code)
micropython.alloc_emergency_exception_buf(100)

# Feed‑dog thread
def feed_thread() -> None:
    """
    Feed‑dog thread, performs periodic feed‑dog operations and clears watchdog counter to prevent main‑thread‑triggered reboot.
    Args:
        None
    Returns:
        None
    Raises:
        Exception: Prints error message upon exceptions inside feed thread without interrupting main‑thread execution.
    """
    global watchdog_counter
    feed_times = 0
    while True:
        try:
            # Perform feed‑dog every FEED_INTERVAL seconds
            time.sleep(FEED_INTERVAL)
            # Acquire mutex lock
            lock.acquire()
            if feed_times < FEED_COUNT:
                # Feed‑dog: clear watchdog_counter
                watchdog_counter = 0
                feed_times += 1
                print(f"[Feed] Feed the dog! ({feed_times}/{FEED_COUNT})")
            else:
                # Stop feed‑dog when maximum feed count is reached
                print("[Feed] Stop feeding the dog!")
                # Exit loop
                break
        except Exception as e:
            # Catch and print exception
            print(f"[Feed] Error in feed thread: {e}")
        finally:
            # Release mutex lock if held
            if lock.locked():
                lock.release()
# ======================================== Custom classes ============================================
# ======================================== Initialization ==========================================
# 3‑second power‑on delay
time.sleep(3)
# Print debug information
print("FreakStudio : Implement Watchdog Timer using a multi‑threading Test")
# Create LED object
led = machine.Pin(25, machine.Pin.OUT)
# ======================================== Main program ===========================================
# Allocate mutex lock for shared‑resource access among multiple threads
lock = _thread.allocate_lock()
# Start feed‑dog thread
_thread.start_new_thread(feed_thread, ())

# Main thread: monitor whether watchdog_counter times‑out
try:
    # Check watchdog_counter for timeout every WATCHDOG_CHECK_INTERVAL seconds
    while True:
        try:
            # Acquire mutex lock
            lock.acquire()
            # Toggle LED to indicate program is running
            led.toggle()
            # Simulate other tasks inside main thread
            time.sleep(WATCHDOG_CHECK_INTERVAL-1)
            print("[Main] Main thread is running, checking watchdog...")
            # Judge whether watchdog_counter exceeds WATCHDOG_MAX_COUNTER
            if watchdog_counter > WATCHDOG_MAX_COUNTER:
                # Feed‑dog thread has failed and cannot perform feed‑dog
                print("[Watchdog] Feed thread is unresponsive. System should restart!")
                # 1‑second delay to ensure debug messages finish output
                time.sleep(1)
                # Release mutex lock if held
                if lock.locked():
                    lock.release()
                # Attempt system reboot
                try:
                    # Soft‑reset: delete all Python objects and reset Python heap space
                    machine.soft_reset()
                except Exception as e:
                    # Catch and print exception
                    print(f"[Error] Failed to restart system: {e}")
            # Increment watchdog_counter
            watchdog_counter += 1
        except Exception as e:
            # Catch and print exception
            print(f"[Main] Error in main thread: {e}")
        finally:
            # Release mutex lock if held
            if lock.locked():
                lock.release()
except KeyboardInterrupt:
    # Catch user interrupt such as Ctrl+C
    print("[Main] Program interrupted by user.")
finally:
    # Guarantee resource release upon program exit
    print("[Main] Program ended.")

The above sample implements the following logic:

Watchdog mechanism A global variable watchdog_counter acts as watchdog counter. The main thread increments watchdog_counter periodically, while the feed‑dog thread clears it (feed‑dog). When watchdog_counter exceeds threshold WATCHDOG_MAX_COUNTER, the feed‑dog thread is regarded as faulty and system reboot is required.

Multi‑thread design Main thread monitors watchdog_counter and executes primary tasks such as toggling the LED. The feed‑dog thread performs periodic feed‑dog by clearing watchdog_counter. It feeds every FEED_INTERVAL (1) second and stops after FEED_COUNT (10) feed‑dog cycles to simulate program failure.

Mutex‑lock protection and system reboot _thread.allocate_lock() creates mutex lock lock to protect access to shared global variable watchdog_counter and avoid data inconsistency caused by multi‑thread race conditions. If watchdog detects feed‑dog thread failure, main thread invokes machine.soft_reset() to reboot system.

Program timing diagram:

1.png

machine.soft_reset() is used for system reboot:

2.png

Different from hard reset machine.reset() which re‑initialises the whole system, machine.soft_reset() only restarts the MicroPython interpreter without re‑initialising hardware peripherals. It clears MicroPython runtime states including variables, objects and stack, then reruns startup scripts boot.py and main.py. The REPL connection will remain alive.

Flash code and remotely connect Raspberry Pi Pico. Terminal output:

3.png

After stopping feed‑dog operations, the watchdog main thread triggers device reboot once timeout condition is reached. Inside multi‑task environments, multi‑threaded watchdog can respond quickly to main‑thread faults.

Documents
Comments Write