← micropython-hardware docs

Board devices contract

Normative end-device surface for board_config — CircuitPython-like discovery, stable role names, and a clear split between eager UI devices and lazy extras.

This is the target contract for MicroPython boards. Board configs and drivers live in micropython-hardware.

CircuitPython (board_configs/cp/) does not use board_devices.py or lazy DEVICES. CP already exposes pins/buses via the native board module. CP board_config.py only constructs display_drv, runtime, and eager UI inputs wired into runtime (touch, keypad, encoder, joystick). Do not from board_config import … inside CP configs.

Specials (always these names)

Symbol Required Notes
display_drv yes Display backend
runtime when display_drv.needs_refresh eventsys.Runtime; may be `None` on display-only MCU boards

Optional end-device roles

Omit the name entirely when the hardware is absent. Canonical symbols:

Role Symbol Wiring
Touch touch Eager in board_config; wire into Runtime; apps use runtime.touch_dev
Keypad keypad All board buttons (not encoder click); apps use runtime.keypad_dev
Encoder encoder Includes click; apps use runtime.encoder_dev
Joystick joystick Separate from keypad; apps use runtime.joystick_dev
Addressable LEDs pixels NeoPixel / DotStar / APA102
Discrete LED led Primary user LED only
Motion accelerometer, gyroscope, magnetometer Separate; omit missing axes
Environment temperature, humidity, pressure Same driver may bind to several names
Audio audio, microphone audio = playback; microphone separate
Storage sdcard Driver object only; no auto-mount
Camera camera
Expansion I2C i2c Dedicated STEMMA/Qwiic/Grove only (not internal-only)
Power battery
Field / PHY can, rs485, ethernet Dedicated board hardware
Wi‑Fi wlan Station/AP handle; leave high-level wifi for add_ons / CP
Bluetooth LE ble Omit when absent
Bluetooth Classic bt BR/EDR; omit when absent
RF co-processor radio AirLift/C6/etc.; may coexist with wlan/ble
Runtime USB device usb_device Non-tooling machine.USBDevice; omit tooling CDC bridge

Out of contract as board_config symbols: high-level wifi / bluetooth modules and tooling USB / UART bridges. Apps may still use those stacks directly.

Discovery

DEVICES is authored in board_devices.DEVICES only. board_config re-exports that frozenset; eager UI names are not listed there.

Module layout (shape to prove)

Keep board_config.py. Sibling board_devices.py holds DEVICES, zero-arg factories, and setup_devices. End of board_config.py:

from board_devices import DEVICES, setup_devices
setup_devices(globals())

Shared boilerplate is boarddev under src/lib/ (name signals devices, not board_config). Typical board_devices.setup_devices is a thin wrapper around boarddev.bind_lazy. A board may replace setup_devices and skip boarddev entirely.

There is no separate board_hardware module.

Bus ownership

Bus shared with… Lives in
UI devices (display_drv, touch, keypad, encoder, joystick, anything wired into runtime) board_config
Only non-UI lazy devices (e.g. SPI for sdcard + radio) board_devices (optional)

Lazy factories import UI-shared buses from board_config when needed (e.g. IMU on the same I2C as touch).

Infrastructure names (for later sharing)

Rename consistently even before lazy devices exist:

Kind Canonical name
Primary shared I2C i2c
Primary shared SPI spi
Extra SPI buses role-qualified: touch_spi, sd_spi, …
Display protocol bus display_bus (SPIBus / I80Bus / FourWire / MIPI Bus / …)
Primary IO expander io_expander

Touch duck-type

board_config.touch is the driver object used when wiring Runtime. Apps interact via runtime.touch_dev / LVGL, not the raw driver.

  1. touch.read_points()() when up, else a sequence of (x, y[, id[, …]]). Never a bare (x, y) from this method (ambiguous with a single 2-tuple point). Single-touch chips return () or a one-element sequence.
  2. Adapters: eventsys.TouchDevice rotates all points, emits primary-finger MOUSE*, exposes touch_dev.points. LVGL display_driver feeds gesture recognizers when those APIs exist. Non-LVGL apps keep using primary MOUSE*.
  3. Board wrappers: do not collapse multi-touch to points[0] in board_config. Keep only sequence-preserving maps (e.g. diagonal rescale).
  4. Wire with touch_read=touch.read_points (or a sequence-preserving wrapper).

See Runtime — touch read contract and Touch drivers.

App usage

import board_config as board
from board_config import display_drv, runtime

display_drv.fill(0)

# Eager UI — discover/use through runtime
if runtime is not None and runtime.touch_dev is not None:
    runtime.touch_dev.subscribe(...)

# Lazy extras — DEVICES only (do not hasattr these)
if "sdcard" in board.DEVICES:
    card = board.sdcard  # constructs now
if "wlan" in board.DEVICES:
    wlan = board.wlan
    wlan.active(True)

Rollout

Board configs and drivers live in PyDevices/micropython-hardware. MicroPython campaign + product boards use the split layout (board_config.py + board_devices.py). CircuitPython twins under cp/ stay single-file (eager UI only).

In micropython-hardware now Still to do
MP split layout for matrix product boards Fill remaining lazy factories (NotImplementedError)
CP eager UI parity (touch / keypad / encoder / joystick) Optional MP Feather DVI config; CP non-UI stays on board
Sequence-preserving touch_read

See also device-matrix.md and the other notes in this docs/ directory.

See also