Skip to content

Session 2: Power, Uptime, and Long-Running Stability

Synopsis

Examines practical concerns for continuous operation, including watchdog-minded design, recovery workflows, and balancing responsiveness with resource usage.

Session Content

Session 2: Power, Uptime, and Long-Running Stability

Session Overview

In this session, learners will explore how to design Raspberry Pi Pico 2 W projects that run reliably for long periods. The focus is on power-related behavior, boot-time robustness, watchdog usage, memory discipline, safe startup/shutdown patterns, and basic strategies for keeping MicroPython applications stable during extended operation.

This session combines theory with hands-on MicroPython exercises using the Pico 2 W and Thonny.

Session Duration

~45 minutes


Learning Objectives

By the end of this session, learners will be able to:

  • Explain why long-running MicroPython programs fail in embedded systems
  • Identify common stability issues such as blocking code, memory fragmentation, and brownouts
  • Use machine.WDT to recover from hangs
  • Structure a program for safe startup and resilient execution
  • Monitor uptime and basic runtime health
  • Build a simple long-running Pico 2 W application with periodic activity

Prerequisites

  • Raspberry Pi Pico 2 W
  • USB cable
  • Thonny IDE installed
  • MicroPython firmware flashed on the Pico 2 W
  • Basic familiarity with Python syntax
  • Optional: onboard LED-only setup, no external hardware required

Development Environment Setup

Thonny Configuration

  1. Connect the Pico 2 W to your computer via USB.
  2. Open Thonny.
  3. Go to Tools > Options > Interpreter.
  4. Select:
  5. MicroPython (Raspberry Pi Pico)
  6. Choose the correct serial port if prompted.
  7. Confirm the REPL is working by running: python print("Hello Pico 2 W")

Suggested Project Layout

Use a simple single-file approach for this session:

  • main.py — long-running application that starts automatically on boot

Theory: Why Long-Running Stability Matters

Embedded systems often run without interruption for hours, days, or months. Common causes of failure include:

  • Blocking loops that prevent background tasks from running
  • Memory leaks or repeated allocations that lead to heap fragmentation
  • Network dropouts causing stuck connections
  • Brownouts or unstable power supplies
  • Unhandled exceptions that stop the main loop
  • Peripherals left in bad states after a reboot

Stability Best Practices

  • Keep the main loop short and predictable
  • Use sleep() to yield time between iterations
  • Reuse objects instead of creating new ones repeatedly
  • Catch exceptions around risky code
  • Reset or reinitialize hardware on startup
  • Use a watchdog to recover from lockups
  • Log runtime state with lightweight prints

Theory: Power and Uptime Considerations

USB Power vs External Power

The Pico 2 W can be powered by USB, but real projects may use: - regulated battery power - USB wall adapters - external power management circuits

Common Power Issues

  • Voltage dips during Wi-Fi transmission
  • Weak cables causing intermittent resets
  • Inadequate current capability from power sources
  • Peripheral startup spikes

Practical Advice

  • Use a good quality USB cable
  • Avoid powering heavy loads directly from the Pico
  • Keep Wi-Fi activity modest and periodic
  • Test with the same power source used in deployment

Theory: Watchdog Basics

The watchdog timer helps recover from software hangs. If your program stops responding and fails to refresh the watchdog, the Pico resets automatically.

When to Use It

  • Long-running loops
  • Wi-Fi-based applications
  • Sensor polling systems
  • Remote devices with no human supervision

Important Note

The watchdog is not a substitute for good code. It is a safety net.


Hands-On Exercise 1: Uptime and Heartbeat Monitor

Goal

Create a simple program that: - tracks uptime - blinks the onboard LED - prints periodic status messages - runs indefinitely without heavy memory usage

Wiring

  • No external wiring required
  • Uses onboard LED

main.py

from machine import Pin
import time

# Onboard LED on Raspberry Pi Pico 2 W
led = Pin("LED", Pin.OUT)

# Record boot time in milliseconds
boot_time_ms = time.ticks_ms()

# Counter for loop iterations
loop_count = 0

print("System starting...")

while True:
    loop_count += 1

    # Calculate uptime safely using ticks_diff
    uptime_ms = time.ticks_diff(time.ticks_ms(), boot_time_ms)
    uptime_s = uptime_ms // 1000

    # Toggle LED as a heartbeat
    led.toggle()

    # Print status every 5 seconds
    if loop_count % 5 == 0:
        print("Uptime:", uptime_s, "s | Loop:", loop_count)

    # Keep the loop predictable and cooperative
    time.sleep(1)

Example Output

System starting...
Uptime: 4 s | Loop: 5
Uptime: 9 s | Loop: 10
Uptime: 14 s | Loop: 15

Discussion Points

  • Why time.ticks_diff() is used instead of direct subtraction
  • How the loop stays stable by using a fixed sleep interval
  • Why a simple heartbeat is useful for debugging

Hands-On Exercise 2: Add a Watchdog for Recovery

Goal

Build a program that refreshes the watchdog regularly and intentionally simulates a hang to demonstrate automatic recovery.

What You’ll Learn

  • Basic watchdog usage
  • How to structure a loop around regular refreshes
  • How the device behaves after a timeout

main.py

from machine import Pin, WDT
import time

led = Pin("LED", Pin.OUT)

# Create a watchdog with a 4-second timeout
wdt = WDT(timeout=4000)

boot_time_ms = time.ticks_ms()
counter = 0

print("Watchdog demo starting...")

while True:
    counter += 1

    # Feed the watchdog before doing work
    wdt.feed()

    # Toggle LED to show the loop is alive
    led.toggle()

    uptime_ms = time.ticks_diff(time.ticks_ms(), boot_time_ms)
    print("Loop:", counter, "| Uptime:", uptime_ms // 1000, "s")

    # Simulate a hang after 10 iterations
    if counter == 10:
        print("Simulating a freeze... watchdog should reset the board.")
        while True:
            pass

    time.sleep(1)

Expected Behavior

  • LED toggles and status prints normally
  • After the simulated freeze, the board stops feeding the watchdog
  • The Pico resets automatically after the timeout

Example Output

Watchdog demo starting...
Loop: 1 | Uptime: 0 s
Loop: 2 | Uptime: 1 s
...
Loop: 10 | Uptime: 9 s
Simulating a freeze... watchdog should reset the board.

After reset, the program starts again from the beginning.

Discussion Points

  • Why the watchdog must be fed regularly
  • Why the infinite pass loop triggers a reset
  • How watchdogs improve reliability in unattended devices

Hands-On Exercise 3: Safer Long-Running Loop with Error Handling

Goal

Create a robust loop that handles exceptions, delays between retries, and continues running after a recoverable error.

main.py

from machine import Pin
import time

led = Pin("LED", Pin.OUT)
error_count = 0
iteration = 0

print("Resilient loop starting...")

while True:
    try:
        iteration += 1

        # Simulate a recoverable error every 7 iterations
        if iteration % 7 == 0:
            raise ValueError("Simulated sensor read failure")

        led.value(1)
        print("Iteration", iteration, ": OK")
        time.sleep(0.5)
        led.value(0)
        time.sleep(0.5)

    except Exception as e:
        error_count += 1
        print("Error:", repr(e))
        print("Recovering... error count =", error_count)

        # Brief pause before retrying
        time.sleep(2)

Example Output

Resilient loop starting...
Iteration 1 : OK
Iteration 2 : OK
Iteration 3 : OK
Iteration 4 : OK
Iteration 5 : OK
Iteration 6 : OK
Error: ValueError('Simulated sensor read failure')
Recovering... error count = 1
Iteration 8 : OK

Discussion Points

  • Why broad exception handling can keep a device alive
  • The importance of logging errors
  • Why recovery delays should be short and controlled

Hands-On Exercise 4: Lightweight Memory Discipline Demo

Goal

Observe how repeated allocation can affect long-running programs and learn a safer pattern.

Theory

Repeatedly creating new objects inside a fast loop can increase memory pressure. In embedded systems, it is better to: - reuse buffers - avoid unnecessary string concatenation - reduce dynamic allocations where possible

main.py

import gc
import time

iteration = 0

print("Memory discipline demo starting...")

while True:
    iteration += 1

    # Encourage garbage collection periodically
    if iteration % 20 == 0:
        gc.collect()

    free_mem = gc.mem_free()
    allocated_mem = gc.mem_alloc()

    print("Iter:", iteration,
          "| Free:", free_mem,
          "| Alloc:", allocated_mem)

    time.sleep(1)

Example Output

Memory discipline demo starting...
Iter: 1 | Free: 176832 | Alloc: 18816
Iter: 2 | Free: 176832 | Alloc: 18816
Iter: 20 | Free: 177120 | Alloc: 18528

Discussion Points

  • Why garbage collection can help stabilize memory use
  • Why monitoring memory is useful in long-running applications
  • Why many embedded applications should avoid frequent object creation

Mini-Challenge: Build a Stable LED Beacon

Task

Create a program that: - blinks the onboard LED with a fixed pattern - prints uptime every 10 seconds - uses a watchdog - catches exceptions - runs indefinitely

Suggested Requirements

  • LED short blink twice, then pause
  • Watchdog timeout: 5 seconds
  • Uptime print every 10 seconds
  • One safe restart path if an error occurs

Starter Template

from machine import Pin, WDT
import time
import gc

led = Pin("LED", Pin.OUT)
wdt = WDT(timeout=5000)

boot_ms = time.ticks_ms()
last_report_ms = boot_ms

print("Stable beacon starting...")

while True:
    try:
        wdt.feed()

        # Blink pattern: two short pulses
        for _ in range(2):
            led.on()
            time.sleep(0.2)
            led.off()
            time.sleep(0.2)

        # Pause between beacon cycles
        time.sleep(2)

        now_ms = time.ticks_ms()
        if time.ticks_diff(now_ms, last_report_ms) >= 10000:
            uptime_s = time.ticks_diff(now_ms, boot_ms) // 1000
            print("Uptime:", uptime_s, "seconds")
            last_report_ms = now_ms
            gc.collect()

    except Exception as e:
        print("Unexpected error:", repr(e))
        time.sleep(2)

Best Practices Summary

  • Use time.ticks_ms() and time.ticks_diff() for uptime tracking
  • Keep loops simple and non-blocking where possible
  • Refresh watchdogs consistently
  • Catch and log recoverable exceptions
  • Reuse resources and reduce memory churn
  • Use gc.collect() carefully and periodically
  • Prefer short, predictable sleep intervals
  • Validate power stability before deploying long-running systems

Check Your Understanding

  1. Why should long-running embedded programs avoid heavy blocking code?
  2. What problem does a watchdog solve?
  3. Why is time.ticks_diff() preferable for uptime calculations?
  4. What are common causes of unexpected resets on a Pico 2 W?
  5. Why is memory reuse important in MicroPython?
  6. How can exception handling improve uptime?

Wrap-Up

In this session, learners built a foundation for writing stable, long-running MicroPython applications on the Raspberry Pi Pico 2 W. They practiced uptime tracking, watchdog recovery, exception handling, and memory-aware programming patterns that are essential for reliable IoT and embedded systems.


Suggested Next Session Preparation

Before the next session: - Review the use of machine.Pin - Practice reading the onboard LED state - Read about uasyncio basics in MicroPython - Prepare a simple sensor or button for future asynchronous examples


Back to Chapter | Back to Master Plan | Previous Session | Next Session