Session 4: Sleeping, Timing, and Periodic Work
Synopsis
Introduces async delays, periodic loops, rate control, and timing accuracy considerations for responsive embedded applications.
Session Content
Session 4: Sleeping, Timing, and Periodic Work
Session Overview
In this session, you will learn how to control time in MicroPython on the Raspberry Pi Pico 2 W using sleep, ticks, and periodic scheduling patterns. You will explore how to avoid blocking code, how to run tasks at regular intervals, and how timing affects responsive embedded applications.
Duration
~45 minutes
Learning Goals
By the end of this session, you will be able to:
- Use
time.sleep(),sleep_ms(), andsleep_us()correctly - Measure elapsed time with
time.ticks_ms()andtime.ticks_diff() - Build periodic tasks using a non-blocking timing loop
- Blink LEDs with different timing strategies
- Read sensors or update outputs at regular intervals without freezing the program
- Understand when blocking delays are acceptable and when they are not
Prerequisites
- Raspberry Pi Pico 2 W with MicroPython installed
- Thonny IDE installed and connected to the board
- Basic familiarity with Python variables, loops, and functions
- Optional hardware:
- Built-in LED
- External LED + 220Ω resistor
- Push button
- DHT22 or similar sensor
1. Environment Setup
Thonny Setup
- Open Thonny
- Connect the Pico 2 W via USB
- Select:
- Run > Select interpreter
- Interpreter: MicroPython (Raspberry Pi Pico)
- Confirm the correct serial port is selected
- Save files directly to the Pico when prompted
Useful Imports
Use these common imports in this session:
from machine import Pin
import time
2. Theory: Why Timing Matters in MicroPython
Embedded systems often need to do more than one thing: - blink an LED - read a sensor - check a button - send data over Wi-Fi - keep the main program responsive
A simple sleep() call pauses the entire program. This is useful, but it blocks everything else. In contrast, a periodic loop based on elapsed time lets the program keep running and do other tasks when needed.
Blocking vs Non-Blocking
- Blocking delay: program stops and waits
- Non-blocking timing: program continues and checks whether enough time has passed
Common Timing Tools
time.sleep(seconds)time.sleep_ms(milliseconds)time.sleep_us(microseconds)time.ticks_ms()time.ticks_us()time.ticks_diff(current, previous)
3. Blocking Delays
time.sleep()
Pauses execution for a number of seconds.
import time
print("Start")
time.sleep(2)
print("2 seconds later")
Example output:
Start
2 seconds later
time.sleep_ms() and time.sleep_us()
Use milliseconds or microseconds for more precise delays.
import time
print("Wait 500 ms")
time.sleep_ms(500)
print("Done")
import time
print("Wait 1000 us")
time.sleep_us(1000)
print("Done")
4. Hands-On Exercise 1: Blink the Built-In LED with sleep()
Objective
Use a blocking loop to blink the Pico 2 W built-in LED.
Wiring
No external wiring required. The Pico 2 W built-in LED is typically on GPIO 25 on many Pico boards; on Pico W-series boards, the onboard LED is controlled differently. For Pico 2 W, check the board documentation or use an external LED if needed.
Code
from machine import Pin
import time
# Use the built-in LED pin if available on your board.
# If this does not work, replace with an external LED pin.
led = Pin("LED", Pin.OUT)
while True:
led.on()
print("LED ON")
time.sleep(1)
led.off()
print("LED OFF")
time.sleep(1)
Expected Behavior
- LED turns on for 1 second
- LED turns off for 1 second
- Repeats forever
Discussion
This is simple and readable, but the program cannot do anything else during sleep().
5. Theory: Measuring Time with Ticks
When writing responsive programs, avoid waiting passively. Instead: 1. store the time when an event last happened 2. repeatedly check the current time 3. compare the difference 4. run the task when enough time has passed
Why ticks_ms()?
- returns a millisecond counter
- suitable for periodic tasks
- works well with
ticks_diff()to handle wraparound correctly
Pattern
now = time.ticks_ms()
elapsed = time.ticks_diff(now, last_time)
if elapsed >= interval:
# do periodic task
6. Hands-On Exercise 2: Non-Blocking LED Blink with ticks_ms()
Objective
Blink an LED without using sleep() in the main loop.
Code
from machine import Pin
import time
led = Pin("LED", Pin.OUT)
# Save the time of the last toggle
last_toggle = time.ticks_ms()
# Blink interval in milliseconds
interval = 1000
# Track LED state
led_state = False
while True:
now = time.ticks_ms()
# Check if enough time has passed
if time.ticks_diff(now, last_toggle) >= interval:
led_state = not led_state
led.value(led_state)
last_toggle = now
if led_state:
print("LED ON")
else:
print("LED OFF")
# Main loop stays responsive here
Expected Behavior
- LED toggles every 1 second
- Program remains responsive
- You can later add more tasks into the loop
Why This Is Better
You can expand this pattern to: - read buttons - poll sensors - update displays - manage network connections
7. Theory: Periodic Work in Embedded Systems
A periodic task is something that must happen regularly: - read temperature every 5 seconds - send telemetry every 30 seconds - blink status LED every 500 ms - check a button every 20 ms
Common Design
A typical MicroPython periodic loop contains:
- one or more intervals
- one timestamp per task
- checks using ticks_diff()
- task execution only when due
This approach scales better than filling code with sleep() calls.
8. Hands-On Exercise 3: Two Periodic Tasks in One Loop
Objective
Run two independent timing tasks: - blink LED every 500 ms - print a message every 2 seconds
Code
from machine import Pin
import time
led = Pin("LED", Pin.OUT)
# Task intervals
blink_interval = 500 # ms
message_interval = 2000 # ms
# Last execution times
last_blink = time.ticks_ms()
last_message = time.ticks_ms()
led_state = False
while True:
now = time.ticks_ms()
# Blink task
if time.ticks_diff(now, last_blink) >= blink_interval:
led_state = not led_state
led.value(led_state)
last_blink = now
# Message task
if time.ticks_diff(now, last_message) >= message_interval:
print("Two seconds have passed")
last_message = now
Expected Behavior
- LED blinks twice per second
- Console prints a message every 2 seconds
Key Idea
Each task has its own timer. The loop checks both tasks continuously.
9. Working with Sensor or Input Timing
In real applications, timing often combines with hardware input. For example: - read a button every 50 ms - sample a sensor every 1 second - debounce a noisy input using timing
Example: Periodic Button Check
from machine import Pin
import time
button = Pin(14, Pin.IN, Pin.PULL_UP)
last_check = time.ticks_ms()
check_interval = 50 # ms
while True:
now = time.ticks_ms()
if time.ticks_diff(now, last_check) >= check_interval:
last_check = now
if button.value() == 0:
print("Button pressed")
else:
print("Button released")
Notes
- This is a simple periodic poll
- For real buttons, you may need debounce logic
10. Hands-On Exercise 4: Simple LED Task Scheduler
Objective
Create a small scheduler with two tasks: - fast task: LED toggle every 250 ms - slow task: print status every 1 second
Code
from machine import Pin
import time
led = Pin("LED", Pin.OUT)
# Task intervals
FAST_INTERVAL = 250
SLOW_INTERVAL = 1000
# Last run times
last_fast = time.ticks_ms()
last_slow = time.ticks_ms()
led_state = False
counter = 0
while True:
now = time.ticks_ms()
# Fast task: toggle LED
if time.ticks_diff(now, last_fast) >= FAST_INTERVAL:
led_state = not led_state
led.value(led_state)
last_fast = now
# Slow task: print status
if time.ticks_diff(now, last_slow) >= SLOW_INTERVAL:
counter += 1
print("Status update:", counter, "| LED state:", led_state)
last_slow = now
Example Output
Status update: 1 | LED state: False
Status update: 2 | LED state: False
Status update: 3 | LED state: True
11. Best Practices for Timing Code
Do
- use
ticks_ms()for regular periodic checks - use
ticks_diff()instead of raw subtraction - keep loop body short
- assign each task its own timestamp
- use meaningful interval constants
Avoid
- long
sleep()calls in programs that must stay responsive - comparing absolute tick values directly with subtraction
- putting too much work inside a single timing block
- relying on exact timing for precision-critical applications
Good Pattern
if time.ticks_diff(now, last_task) >= interval:
last_task = now
# do work
12. Optional Extension: Reading a Sensor Periodically
If you have a sensor available, use timing to read it at intervals.
Example Structure
from machine import Pin
import time
# Replace this with real sensor code
last_read = time.ticks_ms()
read_interval = 1000
while True:
now = time.ticks_ms()
if time.ticks_diff(now, last_read) >= read_interval:
last_read = now
print("Reading sensor now...")
Use Case
This pattern is the foundation for: - environmental monitoring - IoT dashboards - logging systems
13. Checkpoint Questions
- What is the difference between blocking and non-blocking timing?
- Why is
ticks_diff()preferred over subtracting tick values directly? - What happens to the main loop when
time.sleep(2)is called? - How can you run two periodic tasks in one loop?
- When is a blocking delay acceptable?
14. Practical Summary
You Learned
- how to pause with
sleep - how to build timing-based loops with
ticks_ms - how to schedule multiple periodic tasks
- how to keep MicroPython applications responsive
Core Pattern to Remember
now = time.ticks_ms()
if time.ticks_diff(now, last_time) >= interval:
last_time = now
# run task
15. Mini Challenge
Challenge
Modify the non-blocking blink program so that: - the LED blinks every 200 ms for 5 seconds - then pauses for 2 seconds - then repeats
Hint
Use: - one timer for blink control - one timer for the pause interval - a state variable to switch modes
16. Session Wrap-Up
You now know how to manage time in MicroPython using both simple delays and more advanced periodic scheduling. These techniques are essential for real embedded projects, especially when you need to combine multiple tasks or prepare for asynchronous programming and IoT networking in later sessions.