Skip to content

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

  1. Install Thonny
  2. Connect the Pico 2 W via USB
  3. In Thonny:
  4. Go to Tools → Options → Interpreter
  5. Select MicroPython (Raspberry Pi Pico)
  6. Choose the correct port
  7. Open the Shell panel to interact with the board
  8. Save scripts to the board as:
  9. main.py for automatic execution
  10. boot.py for 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

  • 1 means not pressed
  • 0 means 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_state is 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)

  • Use Pin.PULL_UP or Pin.PULL_DOWN to 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

  1. Why does a button need debouncing?
  2. What does button.value() == 0 mean when using Pin.PULL_UP?
  3. Why is await asyncio.sleep_ms() better than time.sleep_ms() in async code?
  4. 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

  • 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