Skip to main content

chumicro-buttons

Buttons that catch the press even when your loop is busy.

Wire a button to a pin, read button.just_pressed in the loop you already wrote, and the press lands whether your program was idle or halfway through a slow network call. Debouncing, long presses, auto-repeat, and double-click counting come with it, and the same code runs on CircuitPython, MicroPython, and your laptop.


Part of the ChuMicro family: small, focused Python libraries for microcontrollers and laptops. Browse all libraries.

Install

# CircuitPython (after `circup bundle-add ChuMicro/ChuMicro-Bundle`)
circup install chumicro_buttons

# MicroPython
mpremote mip install github:ChuMicro/ChuMicro-Bundle/chumicro_buttons

# CPython
pip install chumicro-buttons

For bundle setup, pre-compiled .mpy bundles, the experimental channel, and details on PyPI naming, see the ChuMicro install guide.

Quick example

Wire a momentary button between a GPIO pin and GND. The internal pull-up is switched on for you, so no extra parts are needed.

import board
import digitalio
from chumicro_buttons import Button
from chumicro_timing import ticks, ticks_ms

button = Button(pin=board.GP14, ticks=ticks)

led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT

while True:
    now = ticks_ms()
    button.check(now)

    if button.just_pressed:              # true only on the tick the press landed
        led.value = not led.value

The loop never pauses, so the rest of your program keeps running between presses. just_pressed is true for exactly one pass, which means you can read it as many times as you like without it firing twice.

In a program with more going on, hand the loop to chumicro-runner and let callbacks do the reading:

from chumicro_runner import Runner

button.on_press = lambda: print("pressed")

runner = Runner()
runner.add(button)

while True:
    now = runner.tick()
    runner.wait(now)

tick() gives every registered service one small step and dispatches your callbacks; wait() parks the CPU until the next event, waking exactly when a long press, a repeat, or a click window is due.

Presses land even when the loop is slow

A tap is short. If your loop stalls on a socket read or a flash write, a naive pin read looks at the wrong moment and the press is gone. This library captures the edge when it happens instead of when you get around to asking.

On CircuitPython that capture is the firmware's own keypad scan, which runs in the background and stamps each edge with the time it happened. On MicroPython the library installs a small interrupt handler and does the same job. Either way the timing you read is the press the person actually performed:

button.check(now)
if button.just_pressed:
    print(button.held_ms)      # measured from the real edge, not from this tick

You never set any of this up. There is no capture mode to pick and no interrupt to write.

What's included

Core

Symbol Description
Button(pin, settle_ms=10, ...) One momentary button or switch on one pin
Buttons(pins, ...) Several keys on one scan, read as buttons[0], buttons[1]
KeyMatrix(row_pins, column_pins, ...) A keypad wired as rows by columns, numbered row-major. Imported from chumicro_buttons.matrix

Readings, refreshed by check(now_ms)

Symbol Description
button.pressed True while the key is down, after debouncing
button.just_pressed True only on the tick the press landed
button.just_released True only on the tick the release landed
button.held_ms Milliseconds the key has been down, measured from the real edge. After a release it holds how long that press lasted
button.click_count Presses in the click series that just closed
button.overflowed True when a signal was too noisy to capture in full

Callbacks, dispatched by handle(now_ms)

Symbol Description
button.on_press Called with no arguments when the key goes down
button.on_release Called with no arguments when the key comes up
button.on_long_press Called once per press, after long_press_ms
button.on_repeat Called on each auto-repeat while the key is held
button.on_click Called with the press count when a click series closes

Testing

Symbol Description
chumicro_buttons.testing.FakeButtonSource Hand-driven edge source, so button logic is testable with no board

Debouncing

A switch is two pieces of metal meeting, and they bounce apart a few times on the way. Left alone, one press reads as several.

settle_ms is the only setting. It defaults to 10, which suits an ordinary tactile switch:

button = Button(pin=board.GP14, ticks=ticks, settle_ms=10)   # the default
button = Button(pin=board.GP14, ticks=ticks, settle_ms=0)    # the signal is already clean

Set it to 0 when the button has debouncing hardware behind it, which buys back the settle delay. The guide has wiring diagrams with real component values for building that, and an honest account of when it is worth the parts. For most projects the default is enough.

Where this fits

Imports nothing. It compares durations using the tick arithmetic you hand it, which is what ticks= is for, and chumicro-timing supplies the usual one. Bring your own and this library never loads it. Used directly in user apps; nothing downstream depends on it.

Pairs with chumicro-runner, which deals out turns to every service in your program:

runner.add(button)      # check() and handle() are the runner's contract

A rotary encoder's push switch is a Button like any other, so an encoder with a click uses this library alongside chumicro-knobs.

Platform support

Works on CPython, MicroPython, and CircuitPython.

CircuitPython reads keys through the firmware's own background scan, which costs nothing in your board's storage because it is compiled into the firmware. MicroPython has no equivalent, so the library installs and owns a capture interrupt there. Either way the API is the same and you set none of it up.

pin= needs real hardware, so on a laptop it raises and points you at the fake below.

Testing your code

The chumicro_buttons.testing module provides FakeButtonSource, a hand-driven stand-in for the hardware, so press logic is an ordinary unit test with no board and no waiting:

from chumicro_buttons import Button
from chumicro_buttons.testing import FakeButtonSource
from chumicro_timing.testing import FakeTicks

source = FakeButtonSource()
button = Button(source=source, ticks=FakeTicks())

source.press(at_ms=100)
button.check(120)

assert button.just_pressed
assert button.held_ms == 20      # measured from the edge, not from the tick

Each queued edge carries its own at_ms, separate from the tick you pass to check(). That gap is what lets a test cover slow-loop behavior on the host.

Examples

Example What it shows
circuitpython_button_toggle.py A button flips the onboard LED on CircuitPython
micropython_button_toggle.py The same button on MicroPython

Contributing

Issues, bug reports, and pull requests are welcome, and so is "I ran it on this board and here's what happened", some of the most useful feedback a hardware project can get. Development happens in the ChuMicro repository, whose contributing guide covers setup and the test workflow.

Docs

📖 Stable docs · Experimental docs

Find this library

License

MIT

Metadata

Release files for chumicro-buttons 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chumicro-buttons 0.1.2
File Size Uploaded
chumicro_buttons-0.1.2.tar.gz 45.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chumicro-buttons 0.1.2
File Interpreter ABI Platform
chumicro_buttons-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 67.1 kB

Release files / chumicro_buttons-0.1.2.tar.gz

Download URL chumicro_buttons-0.1.2.tar.gz
Size 45.1 kB
Tags Source
SHA-256 checksum
How to use checksums
811a89910902515e9a5f1bbfea783a0f9838563f853985102ad15fae0ce5917c
BLAKE2b-256 checksum
How to use checksums
cda53cb9191c5dc19924eaf42366fcecc14f1f62096fa09f6beec7a3464f8df1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release files / chumicro_buttons-0.1.2-py3-none-any.whl

Download URL chumicro_buttons-0.1.2-py3-none-any.whl
Size 22.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
789642229ff08a96ce137604bd61bc27892a76a4b22398761142e7feeb56aa7c
BLAKE2b-256 checksum
How to use checksums
7b05a7607ae2cd0b311dd6396da2cffc9c380f8eba2086bead9a569d72ab21f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page