Session 2: Polling Buttons and Debouncing in Async Code
Synopsis
Covers button input, switch bounce, periodic polling strategies, and designing responsive input handlers using async loops.
Session Content
Session 2: Polling Buttons and Debouncing in Async Code
Session Overview
Duration: ~45 minutes
Topic: Polling buttons and handling debouncing safely in asynchronous MicroPython code on Raspberry Pi Pico 2 W
Target audience: Python developers with basic programming knowledge
Platform: Raspberry Pi Pico 2 W
IDE: Thonny (or similar MicroPython IDE)
Learning Objectives
By the end of this session, learners will be able to:
- Wire and read a physical pushbutton on the Pico 2 W
- Understand why mechanical buttons bounce
- Implement button polling using
uasyncio - Apply software debouncing techniques
- Trigger actions from button events without blocking the event loop
- Build a small asynchronous button-controlled LED demo
Prerequisites
Learners should already be comfortable with:
- Basic Python syntax
- Uploading and running MicroPython code in Thonny
- Running a simple script on the Pico 2 W
- Using GPIO pins for output from a previous session
Hardware Required
- Raspberry Pi Pico 2 W
- Breadboard
- 1× pushbutton
- 1× LED
- 1× 220Ω resistor
- 1× 10kΩ resistor
- Jumper wires
- USB cable
Development Environment Setup
Thonny Setup
- Install Thonny
- Connect the Pico 2 W via USB
- In Thonny:
- Go to Tools → Options → Interpreter
- Select MicroPython (Raspberry Pi Pico)
- Choose the correct port
- Open the Shell panel to interact with the board
- Save scripts to the board as:
main.pyfor automatic executionboot.pyfor startup configuration
MicroPython Check
Run this in the Thonny shell:
import sys
print(sys.platform)
Expected output:
rp2
Session Outline
1. Theory: Polling vs Interrupts in Async Code (10 minutes)
What is polling?
Polling means checking the state of a button repeatedly in a loop.
Example: - Read pin value - Wait briefly - Read again - Continue forever
Why use polling with uasyncio?
- Simple and predictable
- Works well for many beginner projects
- Easy to combine with other tasks like blinking LEDs or reading sensors
What is button bounce?
When a button is pressed or released, its electrical contacts may rapidly switch on/off for a few milliseconds.
This can cause: - One press appearing as multiple presses - Unstable readings - False triggers
Debouncing approaches
- Time-based debounce: ignore changes for a short delay
- Stable-state debounce: require the same reading for several consecutive samples
In this session, learners will use polling + time-based debounce.
2. Wiring the Button and LED (5 minutes)
LED Wiring
- Pico pin
GP15→ 220Ω resistor → LED anode (+) - LED cathode (−) → GND
Button Wiring
Use internal pull-up resistor:
- One side of button → GP14
- Other side of button → GND
Important Note
With pull-up enabled:
- Button not pressed = 1
- Button pressed = 0
Hands-On Exercise 1: Read a Button State
Goal
Read the button state and print it to the shell.
Code: button_read.py
from machine import Pin
import time
# Button on GP14, using internal pull-up resistor
button = Pin(14, Pin.IN, Pin.PULL_UP)
while True:
state = button.value()
print("Button state:", state)
time.sleep(0.2)
Expected Output
Button state: 1
Button state: 1
Button state: 0
Button state: 0
Button state: 1
Discussion
1means not pressed0means pressed- Rapid changes may occur when pressing/releasing due to bounce
3. Debouncing in Synchronous Code (5 minutes)
A simple debounce strategy is to wait briefly after detecting a press.
Example
from machine import Pin
import time
button = Pin(14, Pin.IN, Pin.PULL_UP)
while True:
if button.value() == 0:
time.sleep_ms(20)
if button.value() == 0:
print("Confirmed press")
while button.value() == 0:
pass
time.sleep_ms(10)
Limitation
This approach blocks the CPU and does not work well when multiple tasks must run concurrently.
4. Asynchronous Polling with Debounce (10 minutes)
Why uasyncio?
uasyncio lets the Pico run multiple tasks cooperatively:
- Poll buttons
- Blink LEDs
- Communicate over Wi-Fi
- Read sensors
Core idea
Use an async task to: 1. Poll the button every few milliseconds 2. Detect a stable press 3. Trigger an action 4. Avoid blocking the event loop
Hands-On Exercise 2: Async Button Polling with Debounce
Goal
Create an async task that detects button presses reliably and prints a message once per press.
Code: async_button_debounce.py
import uasyncio as asyncio
from machine import Pin
# Hardware setup
button = Pin(14, Pin.IN, Pin.PULL_UP)
# Debounce settings
DEBOUNCE_MS = 30
POLL_MS = 10
async def button_task():
"""
Poll the button and detect a debounced press event.
The button is wired with an internal pull-up:
- 1 = released
- 0 = pressed
"""
last_state = button.value()
while True:
current_state = button.value()
# Detect a potential press transition: released -> pressed
if last_state == 1 and current_state == 0:
await asyncio.sleep_ms(DEBOUNCE_MS)
# Check if still pressed after debounce delay
if button.value() == 0:
print("Button pressed!")
# Wait for release to avoid repeated triggers
while button.value() == 0:
await asyncio.sleep_ms(POLL_MS)
last_state = current_state
await asyncio.sleep_ms(POLL_MS)
async def main():
print("Starting async button debounce demo...")
asyncio.create_task(button_task())
# Keep the event loop alive
while True:
await asyncio.sleep(1)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
Starting async button debounce demo...
Button pressed!
Button pressed!
Button pressed!
Discussion
await asyncio.sleep_ms(...)yields control to other tasks- The press is only confirmed after the delay
- Waiting for release prevents repeated triggers while the button remains held down
5. Button-Controlled LED in Async Code (10 minutes)
Now connect button input to an output action.
Behavior
- Each confirmed button press toggles the LED
- The LED state changes without blocking other async tasks
Hands-On Exercise 3: Toggle an LED with a Debounced Button
Goal
Use button presses to toggle an LED asynchronously.
Code: button_led_toggle.py
import uasyncio as asyncio
from machine import Pin
# Hardware setup
button = Pin(14, Pin.IN, Pin.PULL_UP)
led = Pin(15, Pin.OUT)
# Debounce settings
DEBOUNCE_MS = 30
POLL_MS = 10
# Shared LED state
led_state = 0
led.value(led_state)
async def blink_status_task():
"""
Optional status task to show the event loop is alive.
Blinks the onboard LED on GP25 if available on this board.
If GP25 is not available in your setup, you may remove this task.
"""
status_led = Pin("LED", Pin.OUT)
while True:
status_led.toggle()
await asyncio.sleep_ms(500)
async def button_task():
"""
Detect debounced button presses and toggle the LED.
"""
global led_state
last_state = button.value()
while True:
current_state = button.value()
if last_state == 1 and current_state == 0:
await asyncio.sleep_ms(DEBOUNCE_MS)
if button.value() == 0:
led_state = 1 - led_state
led.value(led_state)
print("LED is now:", "ON" if led_state else "OFF")
while button.value() == 0:
await asyncio.sleep_ms(POLL_MS)
last_state = current_state
await asyncio.sleep_ms(POLL_MS)
async def main():
print("Press the button to toggle the LED.")
asyncio.create_task(button_task())
asyncio.create_task(blink_status_task())
while True:
await asyncio.sleep(1)
try:
asyncio.run(main())
finally:
asyncio.new_event_loop()
Expected Output
Press the button to toggle the LED.
LED is now: ON
LED is now: OFF
LED is now: ON
Discussion
led_stateis shared state- Each valid press changes the state once
- The event loop can still run other tasks like status blinking
6. Best Practices for Async Button Handling (5 minutes)
Recommended Practices
- Use
Pin.PULL_UPorPin.PULL_DOWNto avoid floating inputs - Never use long blocking delays inside async tasks
- Keep debounce times short, typically 20–50 ms
- Separate input polling from action logic when projects grow
- Use clear variable names for button state and edge detection
Common Mistakes
- Forgetting a pull resistor
- Treating raw button reads as stable
- Using
time.sleep()inside async code - Triggering an action on every loop while the button is held down
Knowledge Check
- Why does a button need debouncing?
- What does
button.value() == 0mean when usingPin.PULL_UP? - Why is
await asyncio.sleep_ms()better thantime.sleep_ms()in async code? - How does waiting for button release prevent repeated triggers?
Mini Challenge
Modify the LED toggle example so that:
- A short press toggles the LED
- A long press prints Long press detected
- The button still uses debouncing
Optional Hint
Measure how long the button remains pressed using timestamps from time.ticks_ms().
Summary
In this session, learners practiced:
- Reading a button using MicroPython
- Understanding button bounce
- Applying software debouncing
- Polling a button safely in uasyncio
- Triggering LED behavior from button events
This prepares learners for more advanced asynchronous projects such as: - Multiple button inputs - Menu systems - IoT device control - Web-based device interfaces
Suggested Homework
- Add a second button to control a different LED
- Change the LED toggle so the onboard LED mirrors the button state
- Combine the button task with a sensor-reading task using
uasyncio
Reference Links
- MicroPython Wiki: https://github.com/micropython/micropython/wiki
- MicroPython RP2 Quick Reference: https://docs.micropython.org/en/latest/rp2/quickref.html
- Raspberry Pi MicroPython Documentation: https://www.raspberrypi.com/documentation/microcontrollers/micropython.html
- Raspberry Pi Pico 2 Series Documentation: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#pico2
Back to Chapter | Back to Master Plan | Previous Session | Next Session