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.WDTto 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
- Connect the Pico 2 W to your computer via USB.
- Open Thonny.
- Go to Tools > Options > Interpreter.
- Select:
- MicroPython (Raspberry Pi Pico)
- Choose the correct serial port if prompted.
- 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
passloop 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()andtime.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
- Why should long-running embedded programs avoid heavy blocking code?
- What problem does a watchdog solve?
- Why is
time.ticks_diff()preferable for uptime calculations? - What are common causes of unexpected resets on a Pico 2 W?
- Why is memory reuse important in MicroPython?
- 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