Skip to content

Session 1: Async Patterns for Sensor Sampling

Synopsis

Covers periodic sensor reads, sampling intervals, filtering considerations, and balancing freshness, accuracy, and CPU usage in async loops.

Session Content

Session 1: Async Patterns for Sensor Sampling

Session Overview

Duration: ~45 minutes
Topic: Async Patterns for Sensor Sampling
Platform: Raspberry Pi Pico 2 W
Language: MicroPython
IDE: Thonny

Learning Objectives

By the end of this session, learners will be able to: - Explain why asynchronous programming is useful for sensor sampling on microcontrollers. - Set up a MicroPython development environment in Thonny for the Pico 2 W. - Use uasyncio to sample multiple sensors without blocking other tasks. - Combine periodic sensor reads with LED status updates and serial logging. - Structure a simple event-driven MicroPython application for IoT use cases.

Prerequisites

  • Basic Python knowledge
  • Raspberry Pi Pico 2 W with MicroPython installed
  • Thonny IDE installed
  • Breadboard, jumper wires
  • 1x LED + 220Ω resistor
  • 1x button
  • 1x potentiometer or analog sensor
  • Optional: DHT11/DHT22 or another digital sensor

1) Session Agenda

0–5 min: Introduction

  • What asynchronous programming solves in embedded systems
  • Blocking vs non-blocking sensor sampling
  • Typical Pico 2 W use cases:
  • periodic sensor monitoring
  • responsive UI/button handling
  • networking and telemetry

5–15 min: Theory

  • The MicroPython event loop
  • Coroutines with async def
  • await and cooperative multitasking
  • Common async patterns:
  • periodic tasks
  • producer/consumer
  • event flags
  • task cancellation and cleanup

15–25 min: Environment Setup

  • Configure Thonny for Pico 2 W
  • Verify MicroPython firmware
  • Run a basic async test
  • Connect a sensor and output device

25–40 min: Hands-on Exercise

  • Build an async sensor sampler:
  • read a digital sensor periodically
  • blink an LED independently
  • print readings to the REPL
  • keep the system responsive

40–45 min: Review and Wrap-up

  • Discuss best practices
  • Review common async mistakes
  • Preview next session

2) Theory: Why Async Matters on Pico 2 W

Microcontrollers have limited CPU, memory, and timing flexibility compared to desktop systems. When code uses blocking delays like time.sleep(), the entire program pauses. That can cause: - missed button presses - delayed sensor updates - sluggish LED or display behavior - networking timeouts

With uasyncio, tasks cooperate by yielding control using await. This allows multiple activities to progress smoothly: - sample a sensor every second - blink an LED every 250 ms - listen for input events - prepare for Wi-Fi messaging

Blocking Example

import time
from machine import Pin

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

while True:
    led.toggle()
    print("LED toggled")
    time.sleep(1)

This works, but nothing else can run during sleep().

Async Equivalent

import uasyncio as asyncio
from machine import Pin

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

async def blink():
    while True:
        led.toggle()
        print("LED toggled")
        await asyncio.sleep(1)

asyncio.run(blink())

This yields control back to the event loop during await asyncio.sleep(1).


3) Development Environment Setup in Thonny

Install and Configure Thonny

  1. Install Thonny from the official website.
  2. Connect the Pico 2 W via USB.
  3. Open Thonny.
  4. Go to:
  5. Run > Select interpreter
  6. Choose MicroPython (Raspberry Pi Pico)
  7. Select the correct port if needed.
  8. Open the Shell pane to verify the REPL.

Verify MicroPython

In the Thonny shell:

import sys
print(sys.implementation)

Example output:

(name='micropython', version=(1, 24, 0), mpy=...)

Confirm uasyncio Availability

import uasyncio as asyncio
print(asyncio)

Example output:

<module 'uasyncio'>

4) Wiring for the Exercise

Required Components

  • Pico 2 W
  • External LED + resistor
  • Button
  • Potentiometer or analog sensor

Suggested Connections

LED

  • LED anode (+) → GPIO 15 through 220Ω resistor
  • LED cathode (−) → GND

Button

  • One side → GPIO 14
  • Other side → GND

Use internal pull-up resistor in code.

Potentiometer

  • One end → 3V3
  • Other end → GND
  • Middle pin → GPIO 26 / ADC0

5) Hands-on Exercise: Async Sensor Sampling

Goal

Create a MicroPython application that: - samples an analog sensor periodically - monitors a button without blocking - blinks the onboard LED independently - prints status updates cleanly


6) Code: Async Sensor Sampler

Save this as main.py on the Pico.

# Async Sensor Sampling Demo for Raspberry Pi Pico 2 W
# - Samples an analog input periodically
# - Monitors a button without blocking
# - Blinks the onboard LED independently
#
# Hardware:
# - Potentiometer middle pin -> GPIO 26 (ADC0)
# - Potentiometer ends -> 3V3 and GND
# - Button -> GPIO 14 and GND
# - LED -> onboard LED

import uasyncio as asyncio
from machine import Pin, ADC

# ----------------------------
# Hardware setup
# ----------------------------

led = Pin("LED", Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)
adc = ADC(26)  # ADC0 on GPIO26

# ----------------------------
# Shared state
# ----------------------------

button_pressed = False


# ----------------------------
# Async tasks
# ----------------------------

async def blink_led():
    """Blink the onboard LED every 0.5 seconds."""
    while True:
        led.toggle()
        await asyncio.sleep(0.5)


async def sample_sensor():
    """Read the ADC periodically and print a scaled value."""
    while True:
        raw = adc.read_u16()  # 0..65535
        voltage_ratio = raw / 65535
        print("ADC raw:", raw, "ratio:", round(voltage_ratio, 3))
        await asyncio.sleep(1)


async def monitor_button():
    """Poll the button and detect presses without blocking."""
    global button_pressed

    last_state = button.value()

    while True:
        current_state = button.value()

        # Detect a press: pull-up means 1 -> 0 is a press
        if last_state == 1 and current_state == 0:
            button_pressed = True
            print("Button pressed!")

        last_state = current_state
        await asyncio.sleep_ms(50)


async def report_status():
    """Report combined system status."""
    global button_pressed

    while True:
        if button_pressed:
            print("Status: button event handled")
            button_pressed = False
        else:
            print("Status: running normally")

        await asyncio.sleep(2)


# ----------------------------
# Main entry point
# ----------------------------

async def main():
    """Run all tasks concurrently."""
    print("Starting async sensor sampling demo...")

    tasks = [
        asyncio.create_task(blink_led()),
        asyncio.create_task(sample_sensor()),
        asyncio.create_task(monitor_button()),
        asyncio.create_task(report_status()),
    ]

    # Keep the program alive
    await asyncio.gather(*tasks)


try:
    asyncio.run(main())
finally:
    asyncio.new_event_loop()

7) Expected Output

When the board is running, the Thonny REPL may show output similar to:

Starting async sensor sampling demo...
Status: running normally
ADC raw: 32890 ratio: 0.502
Button pressed!
Status: button event handled
ADC raw: 41210 ratio: 0.629
Status: running normally

The onboard LED should blink every 0.5 seconds while sensor readings continue in the background.


8) Exercise Steps

Step 1: Upload the Code

  • Open Thonny
  • Paste the code into a new file
  • Save it as main.py on the Pico 2 W

Step 2: Run the Program

  • Click Run
  • Observe LED blinking and serial output

Step 3: Adjust Sampling Rates

Modify these lines: - await asyncio.sleep(1) in sample_sensor() - await asyncio.sleep(0.5) in blink_led() - await asyncio.sleep_ms(50) in monitor_button()

Try: - faster sensor sampling - slower LED blinking - different button polling intervals

Step 4: Observe Responsiveness

Press the button while the LED is blinking and sensor data is printing. Notice that all tasks continue to run without blocking.


9) Guided Extension: Add a Threshold Alarm

Add logic to detect when the analog reading exceeds a threshold.

Modified Sensor Task

async def sample_sensor():
    """Read the ADC periodically and print an alarm if threshold is exceeded."""
    threshold = 40000

    while True:
        raw = adc.read_u16()

        print("ADC raw:", raw)

        if raw > threshold:
            print("ALARM: Sensor value above threshold!")

        await asyncio.sleep(1)

Example Output

ADC raw: 39120
ADC raw: 42111
ALARM: Sensor value above threshold!
ADC raw: 43890
ALARM: Sensor value above threshold!

10) Best Practices for Async MicroPython on Pico 2 W

  • Keep each task short and cooperative.
  • Use await asyncio.sleep() instead of long blocking loops.
  • Prefer sleep_ms() for fine-grained polling.
  • Avoid doing heavy computation inside tasks.
  • Use shared state carefully and keep it simple.
  • Structure code into small, testable coroutines.
  • Add cleanup with try/finally when using asyncio.run().

11) Common Mistakes

Mistake 1: Using time.sleep() in Async Code

This blocks all tasks.

Mistake 2: Forgetting to await

Coroutines must yield control regularly.

Mistake 3: Busy Waiting

A loop with no sleep can starve other tasks.

Mistake 4: Overly Complex Shared State

Keep communication between tasks simple in early projects.


12) Quick Knowledge Check

  1. Why is time.sleep() problematic in an async program?
  2. What does await asyncio.sleep() do?
  3. How does the button task detect a press?
  4. Why is polling acceptable in this example?
  5. Which task would you change to make the LED blink faster?

13) Mini Challenge

Modify the program so that: - the LED blinks faster when the sensor value is high - the button resets the alarm state - sensor readings are displayed as percentages

Hint

Use a shared variable like sensor_value and update the LED task based on it.


14) Session Wrap-up

Key Takeaways

  • Async programming improves responsiveness on microcontrollers.
  • uasyncio enables concurrent tasks on the Pico 2 W.
  • Periodic sensor sampling is a great use case for coroutines.
  • Small, cooperative tasks are easier to manage and extend.

Next Session Preview

  • Using async patterns with Wi-Fi connectivity
  • Sending sensor data to an IoT endpoint
  • Combining timers, network requests, and sensor reads

Back to Chapter | Back to Master Plan | Next Session