Skip to content

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(), and sleep_us() correctly
  • Measure elapsed time with time.ticks_ms() and time.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

  1. Open Thonny
  2. Connect the Pico 2 W via USB
  3. Select:
  4. Run > Select interpreter
  5. Interpreter: MicroPython (Raspberry Pi Pico)
  6. Confirm the correct serial port is selected
  7. 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")

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

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

  1. What is the difference between blocking and non-blocking timing?
  2. Why is ticks_diff() preferred over subtracting tick values directly?
  3. What happens to the main loop when time.sleep(2) is called?
  4. How can you run two periodic tasks in one loop?
  5. 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.


Back to Chapter | Back to Master Plan | Previous Session