Session 3: Managing Multiple I/O Activities at Once
Synopsis
Combines outputs and inputs into small multitasking programs, such as status indicators, user interaction loops, and sensor checks running concurrently.
Session Content
Session 3: Managing Multiple I/O Activities at Once
Session Overview
Duration: ~45 minutes
Topic: Managing Multiple I/O Activities at Once
Focus: Using asynchronous programming in MicroPython to handle multiple hardware activities without blocking the main loop.
By the end of this session, learners will be able to:
- Explain why blocking code is a problem in embedded applications.
- Use uasyncio in MicroPython to coordinate multiple tasks.
- Manage simultaneous I/O activities such as LED blinking, button monitoring, and sensor polling.
- Build a simple multi-task Pico 2 W application that remains responsive.
Prerequisites
- Raspberry Pi Pico 2 W
- Micro-USB cable
- Thonny IDE installed
- MicroPython firmware installed on the Pico 2 W
- Breadboard, jumper wires
- 1 LED
- 1 resistor (220Ω to 330Ω)
- 1 push button
- Optional: DHT11 or another simple sensor
Development Environment Setup
Thonny Setup
- Connect the Raspberry Pi Pico 2 W to your computer using USB.
- Open Thonny.
- Go to Tools > Options > Interpreter.
- Select MicroPython (Raspberry Pi Pico).
- Choose the correct serial port.
- Click OK.
- Verify the REPL shows the MicroPython prompt:
```python
```
File Workflow
- Use
main.pyfor the program that should run automatically on boot. - Use
code.pyor other helper files only if needed. - Save files directly to the Pico filesystem.
Session Agenda
1. Theory: Why Multiple I/O Activities Matter
Embedded systems often need to do more than one thing at a time: - Blink an LED - Read a button state - Poll a sensor - Send data over Wi-Fi
If each task uses sleep() or long loops, the device becomes unresponsive.
Asynchronous programming allows tasks to cooperate by yielding control frequently.
2. Theory: Cooperative Multitasking with uasyncio
MicroPython provides uasyncio, a lightweight asynchronous framework.
Key ideas:
- Coroutine: a function defined with async def
- Await: pauses a coroutine without blocking other tasks
- Task: a scheduled coroutine managed by the event loop
- Event loop: coordinates all active tasks
Hands-On Exercise 1: Non-Blocking LED Blink
This first example shows how to blink an LED without freezing other work.
Wiring
- LED anode to GP15 through a 220Ω resistor
- LED cathode to GND
Code: main.py
import uasyncio as asyncio
from machine import Pin
# Built-in LED on many Pico boards is often on GP25.
# Here we use an external LED on GP15 for clarity.
led = Pin(15, Pin.OUT)
async def blink_led():
while True:
led.value(1)
print("LED ON")
await asyncio.sleep(0.5)
led.value(0)
print("LED OFF")
await asyncio.sleep(0.5)
async def main():
await blink_led()
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
LED ON
LED OFF
LED ON
LED OFF
What to Observe
- The program does not use a blocking
while Truewithsleep(). await asyncio.sleep()lets the system remain responsive.
Hands-On Exercise 2: Blink LED and Monitor a Button
Now add a second activity: reading a button while blinking continues.
Wiring
- Button one side to GP14
- Button other side to GND
- Use the internal pull-up resistor
Code: main.py
import uasyncio as asyncio
from machine import Pin
led = Pin(15, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)
async def blink_led():
while True:
led.toggle()
print("LED state:", led.value())
await asyncio.sleep(0.5)
async def monitor_button():
last_state = button.value()
while True:
current_state = button.value()
if current_state != last_state:
if current_state == 0:
print("Button pressed")
else:
print("Button released")
last_state = current_state
await asyncio.sleep_ms(50)
async def main():
task1 = asyncio.create_task(blink_led())
task2 = asyncio.create_task(monitor_button())
await asyncio.gather(task1, task2)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
LED state: 1
LED state: 0
Button pressed
Button released
LED state: 1
What to Observe
- The LED continues blinking even while the button is being monitored.
- Button changes are detected without interrupting the LED task.
Hands-On Exercise 3: Three Concurrent Activities
Add a third task to simulate sensor polling.
Code: main.py
import uasyncio as asyncio
from machine import Pin
import random
led = Pin(15, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)
async def blink_led():
while True:
led.on()
print("LED ON")
await asyncio.sleep(1)
led.off()
print("LED OFF")
await asyncio.sleep(1)
async def monitor_button():
last_state = button.value()
while True:
current_state = button.value()
if current_state != last_state:
if current_state == 0:
print("Button pressed")
else:
print("Button released")
last_state = current_state
await asyncio.sleep_ms(25)
async def poll_sensor():
while True:
# Simulated sensor value
reading = random.randint(20, 30)
print("Sensor reading:", reading)
await asyncio.sleep(2)
async def main():
tasks = [
asyncio.create_task(blink_led()),
asyncio.create_task(monitor_button()),
asyncio.create_task(poll_sensor())
]
await asyncio.gather(*tasks)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
LED ON
Sensor reading: 24
LED OFF
Button pressed
Sensor reading: 27
LED ON
What to Observe
- All tasks run in parallel cooperatively.
- No task prevents the others from executing.
Theory: Best Practices for Multiple Async Tasks
- Keep each task short and responsive.
- Use
await asyncio.sleep()frequently to yield control. - Use small polling intervals for buttons and sensors.
- Avoid long blocking loops inside coroutines.
- Use descriptive names for tasks.
- Clean up tasks when your program ends or resets.
Hands-On Exercise 4: Event-Style Task Coordination
This example uses a shared variable to coordinate actions.
Goal
- Blink an LED continuously.
- Press the button to change the blink speed.
Code: main.py
import uasyncio as asyncio
from machine import Pin
led = Pin(15, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)
blink_delay = 0.5
async def blink_led():
global blink_delay
while True:
led.toggle()
print("Blink delay:", blink_delay)
await asyncio.sleep(blink_delay)
async def button_controller():
global blink_delay
last_state = button.value()
while True:
current_state = button.value()
if current_state != last_state:
if current_state == 0:
if blink_delay == 0.5:
blink_delay = 0.1
print("Fast blink mode")
else:
blink_delay = 0.5
print("Normal blink mode")
last_state = current_state
await asyncio.sleep_ms(50)
async def main():
await asyncio.gather(
blink_led(),
button_controller()
)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
Blink delay: 0.5
Blink delay: 0.5
Fast blink mode
Blink delay: 0.1
Blink delay: 0.1
Normal blink mode
What to Observe
- One task changes shared state.
- Another task reacts to the shared state.
- This is a simple pattern for user input controlling output.
Common Mistakes
- Using
sleep()instead ofawait asyncio.sleep() - Creating tasks but forgetting to keep the event loop running
- Polling too slowly and missing button presses
- Accessing shared variables without thinking about task interaction
- Writing tasks that never yield control
Mini Challenge
Extend the program so that: - The LED blinks normally by default. - A button press switches the LED to fast blink mode. - A second button press switches it back to normal. - A sensor value is printed every 3 seconds.
Knowledge Check
- What is the difference between blocking and non-blocking code?
- Why is
await asyncio.sleep()preferred in async tasks? - What does
asyncio.create_task()do? - Why is it important for every task to yield control?
- How can shared variables be used between async tasks?
Summary
In this session, learners practiced managing multiple I/O activities using uasyncio in MicroPython. They learned how to:
- Replace blocking delays with async pauses
- Run multiple tasks cooperatively
- Monitor buttons while blinking LEDs
- Simulate and coordinate multiple concurrent activities
Next Session Preview
Session 4: Using Async Tasks with Real Hardware Inputs and Outputs
We will extend these patterns to real sensors and actuators, including more robust task coordination and practical IoT-style device behavior.
Back to Chapter | Back to Master Plan | Previous Session | Next Session