← pydevices docs

Timer backend internals & platform capabilities

This document explains the internal architecture of multimer: how timer providers are selected, the underlying C-binding and threading capabilities of each Python runtime, and how PyDevices bridges hardware interrupts, OS signals, and SDL2 event pumps.

Importing multimer itself never selects a synchronous timer provider. An application imports a provider explicitly, such as from multimer import librt as timer, or opts into platform selection with from multimer import auto as timer. The final column below describes what multimer.auto normally selects; it is not a package-root default.

For the general user guide and quickstart, see multimer. For display driver integration, see Display backend internals and Runtime.


Platform capabilities matrix

The table below details the underlying system capabilities available to multimer across all supported runtimes:

Runtime / Executable Target Platform FFI / C-Bindings Threading Support SDL2 Provider Signal / Interrupt Timers Normal multimer.auto Provider
CPython (python) Linux Desktop ctypes Full threading + _thread usdl2.py (via ctypes) or pygame POSIX real-time signals (librt) librt (uses_interrupts=True)
MicroPython (micropython) Linux Unix port ffi + uctypes Built-in _thread usdl2.py (via ffi) POSIX real-time signals (librt) librt (uses_interrupts=True)
CircuitPython (circuitpython) Linux port None Built-in _thread displayif (compiled C module) None sdl2 / polling (uses_interrupts=False)
CPython (python.exe) Windows ctypes Full threading + _thread usdl2.py (via ctypes) or pygame-ce Waitable Timer APCs (uwin32.py) win32 (uses_interrupts=True)
MicroPython (micropython.exe) Windows Win32 port ffi + uctypes None displayif (compiled C module) Waitable Timer APCs (uwin32.py) win32 (uses_interrupts=True)
CPython (python) Android ctypes Full threading + _thread pygame / native Android surface None threading (uses_interrupts=False)
MicroPython MCU Boards None / Native C Port-dependent _thread N/A (Direct panel bus) Hardware interrupts (machine.Timer) machine (uses_interrupts=True)
CircuitPython MCU Boards None None N/A (Direct panel bus) None polling (uses_interrupts=False)

| PyScript / Pyodide | Browser / WASM | js / pyodide FFI | None (single-threaded WASM) | HTML5 Canvas | Browser host loop / Web APIs | internal async provider (uses_interrupts=False) |


How SDL2 is bridged (usdl2.py vs displayif)

Hosted desktop and simulation targets often use SDL2 for window management, frame presentation, and input polling. PyDevices provides two distinct mechanisms to connect to SDL2 depending on the host's FFI capabilities:

1. Pure-Python FFI Bridge (usdl2.py)

When running on CPython (Linux/Windows) or MicroPython Unix (Linux), the runtime has access to dynamic foreign function interfaces (ctypes or ffi):

2. Compiled User C Module (displayif / cmods)

When running on runtimes without FFI (such as CircuitPython, or a custom MicroPython build that omits ffi):


Signal & Interrupt Timer Delivery

Providers with uses_interrupts is True deliver callbacks directly to the main thread through interrupts, signals, or equivalent OS delivery. This eliminates the need for an application-level timer pump and enables the Interactive REPL debugging workflow. uses_interrupts is provider metadata, not a Timer class method, because it describes delivery by the provider as a whole and also governs sleep_ms and pump behavior.

1. Linux librt (POSIX Signals)

2. Windows uwin32.py (Alertable APCs)

3. Microcontroller machine.Timer (Hardware Interrupts)


MicroPython & CircuitPython Roadmap Considerations

micropython.exe (Windows)

The PyDevices Windows build includes ffi and uctypes, allowing the shared uwin32.py module to call Win32 directly. multimer.auto therefore selects the win32 provider and uses alertable waitable-timer APCs, matching python.exe. A custom build without ffi cannot import that provider and falls through to sdl2 (when its compiled usdl2 module is present) or polling.

Asyncio remains a build-time option. When a MicroPython build provides none of asyncio, uasyncio, or _asyncio, the backend-neutral tick and synchronous timer APIs still work, while arming AsyncTimer raises ImportError.

CircuitPython

CircuitPython intentionally omits machine.Timer and low-level FFI in favor of high-level board abstractions and cooperative asyncio. Applications running on CircuitPython boards or the Linux port always use multimer.AsyncTimer or active sleep-pump loops.