Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.


PyMCU

PyMCU

Python to bare-metal firmware — no runtime, no interpreter, no VM.
Explore the project »

Report a bug · Request a feature · Sponsor

PyPI version Python versions License Last commit Open issues Sponsor


[!IMPORTANT] Alpha 3 is out — v0.1.0a3 release notes. The language grows generators, async/await, dict/set literals, f-strings as values and type inference; the ARM targets (RP2040 / RP2350) reach feature parity with AVR — exceptions, floats, WiFi; and a brand-new PIC backend joins the family. Core compilation is stable and test-covered, but rough edges remain in error messages and tooling — if you hit a bug, please open an issue, it helps a lot.

Avoid pymcu.hal.* during the alpha — the native HAL API may change between releases. Use the MicroPython or CircuitPython compat API instead; those are stable and community-specified.

PyMCU compiles a statically-typed subset of Python into bare-metal firmware for AVR, ARM (RP2040 / RP2350) and PIC — no runtime, no interpreter, no virtual machine. The same binary you would write in C.

PyMCU demo: MicroPython-flavoured blink compiled to 150 bytes and flashed to an Arduino Uno

A real session: 9 lines of Python → pymcu build150 bytes of flash → running on an Arduino Uno. Then the delay is edited, rebuilt and reflashed — the whole loop takes seconds.


The pitch in one table

LED blink for ATmega328P @ 16 MHz — all variants do the same thing: configure PB5 as output, then loop LED on → wait 500 ms → LED off → wait 500 ms forever.

Source Total flash SRAM
C (avr-gcc -Os) 176 B 0 B
PyMCU (native HAL) 150 B 0 B
PyMCU (MicroPython API) 150 B 0 B
PyMCU (CircuitPython API) 152 B 0 B
Arduino (IDE defaults) 924 B 9 B

PyMCU produces a smaller binary than C here. Why?

Pin("PB5", Pin.OUT) and delay_ms(500) are resolved entirely at compile time — the compiler sees through the Python objects and emits the same raw SBI/CBI port-toggle instructions a C programmer would write by hand. The rest of the difference is the delay: PyMCU emits one calibrated delay subroutine shared by both waits (rcall twice), where avr-libc's _delay_ms is inlined at each call site — and there is no call main / jmp _exit scaffolding around the program. The interrupt vector table and startup stub are identical fixed overhead in both toolchains.

Native HAL and MicroPython API produce byte-for-byte identical firmware — both compile down to the same SBI/CBI toggle and the same delay loop. The API is a zero-cost abstraction. CircuitPython is 2 bytes larger because the Direction.OUTPUT setter clears the PORT register before setting DDR, as the CircuitPython spec requires.

These numbers are for a minimal blink. Real programs that use SRAM (global variables, buffers) will emit a small zeroing loop at startup, just like C does.

For complex drivers (custom protocols, timing-critical bit-bang): expect 2-3x flash vs hand-written C. PyMCU is not competing with C — the goal is to make microcontroller development approachable in Python you already know, without the overhead of Arduino. The output is still 100-1000x smaller than any embedded Python interpreter.


Write code you already know

Pick the API that fits your background. Both compile to the same bare-metal firmware.

CircuitPython

# The exact same code that runs on a Pico under CircuitPython
import board
import digitalio
import time

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

while True:
    led.value = True
    time.sleep(0.5)
    led.value = False
    time.sleep(0.5)

MicroPython

# The exact same code that runs on a Pico under MicroPython
from machine import Pin
from utime import sleep_ms

led = Pin(13, Pin.OUT)

while True:
    led.value(1)
    sleep_ms(500)
    led.value(0)
    sleep_ms(500)
pymcu build   # → dist/firmware.hex  (56 bytes flash, 0 bytes SRAM)
pymcu flash   # → avrdude upload to Arduino Uno

First binary in under 5 minutes

1. Install

pipx install --pip-args=--pre "pymcu-compiler[avr]"    # AVR (ATmega / ATtiny)
pipx install --pip-args=--pre "pymcu-compiler[arm]"    # RP2040 / RP2350 (Pico / Pico 2)
pipx install --pip-args=--pre "pymcu-compiler[pic]"    # PIC16
pipx install --pip-args=--pre "pymcu-compiler[all]"    # everything

Requires Python 3.11+ and pipx. Each extra bundles its full toolchain (compiler backend + assembler/linker binaries) — no system packages needed.

Package name: PyMCU is published as pymcu-compiler on PyPI while a PEP 541 request to reclaim the pymcu name is under review. Once approved, a pymcu metapackage will alias pymcu-compiler — installs and project configs will stay compatible.

2. Create a project

pymcu new blink
cd blink

3. Choose your API and write the program

CircuitPython style — add pymcu-circuitpython to dependencies:

# pyproject.toml
[project]
dependencies = ["pymcu-compiler[avr]", "pymcu-circuitpython"]

[tool.pymcu]
board     = "arduino_uno"
frequency = 16000000
# src/main.py
import board
import digitalio
import time

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

while True:
    led.value = True
    time.sleep(0.5)
    led.value = False
    time.sleep(0.5)

MicroPython style — add pymcu-micropython to dependencies:

# pyproject.toml
[project]
dependencies = ["pymcu-compiler[avr]", "pymcu-micropython"]

[tool.pymcu]
board     = "arduino_uno"
frequency = 16000000
# src/main.py
from machine import Pin
from utime import sleep_ms

led = Pin(13, Pin.OUT)

while True:
    led.value(1)
    sleep_ms(500)
    led.value(0)
    sleep_ms(500)

4. Build and flash

pymcu build
# Compiling src/main.py...
# → dist/firmware.hex

pymcu flash --port /dev/cu.usbmodem*
# avrdude: flash verified

Choosing an API

Package API surface Install
pymcu-circuitpython digitalio, analogio, busio, pwmio, time, board, neopixel pip install pymcu-circuitpython
pymcu-micropython machine (Pin/UART/ADC/PWM/SPI/I2C/Timer/WDT), utime pip install pymcu-micropython
pymcu.hal.* Direct register-level HAL — lowest overhead pymcu-stdlib (installed automatically with pymcu-compiler)

Start with MicroPython or CircuitPython — they are stable, community-specified, and backed by real hardware compatibility guarantees. The pymcu.hal.* native HAL is functional but its API may change between alpha releases — avoid it unless you need direct register access not yet covered by the compat layers.


Supported targets

Architecture Chips
AVR (ATmega) ATmega48/88/168/328P, ATmega2560, ATmega32U4
AVR (ATtiny) ATtiny25/45/85, ATtiny24/44/84, ATtiny13/13A, ATtiny2313/4313
ARM (Cortex-M0+ / M33) RP2040 (Pico / Pico W), RP2350 (Pico 2 / Pico 2 W) — incl. PIO and CYW43 WiFi
PIC (mid-range) PIC16F84A, PIC16F877A — new in alpha 3

HAL coverage (ATmega328P / Arduino Uno)

Module Features
pymcu.hal.gpio Pin — high / low / toggle / irq / pulse_in
pymcu.hal.uart UART — write / read / println / RX interrupt
pymcu.hal.adc AnalogPin — poll + interrupt; internal temperature
pymcu.hal.timer Timer(n, prescaler) — CTC mode; millis() / micros()
pymcu.hal.pwm PWM — multi-channel; set_duty / set_freq
pymcu.hal.spi SPI + SoftSPI
pymcu.hal.i2c I2C + SoftI2C
pymcu.hal.eeprom EEPROMwrite(addr, val) / read(addr)
pymcu.hal.watchdog Watchdogenable / disable / feed
pymcu.hal.power sleep_idle / sleep_adc_noise / sleep_power_down / sleep_power_save / sleep_standby / sleep_extended_standby

Drivers: DHT11, DS18B20, HD44780 LCD, SSD1306 OLED, MAX7219 8x8 matrix, BMP280, WS2812 NeoPixel.


What Python features are supported

PyMCU accepts Python syntax but enforces a strict compile-time type system.

Supported:

  • Integer types: uint8, int8, uint16, int16, uint32, int32, float — with type inference for unannotated def parameters and returns
  • Fixed arrays buf: uint8[16], bytearray, heap-bounded lists x: list[uint8] = list()
  • Slices: equal-length assignment (including through __setitem__, so microcontroller.nvm[0:4] = b"..." compiles) and iteration with runtime bounds (for b in buf[0:n])
  • print() of a bytearray or a slice as the CPython repr, and of a float with two rounded decimals; s = "".join([chr(b) for b in buf]) for bytes-to-string
  • for, while, if, match / case, with, class, @inline, lambda
  • Generators (yield), async / await with asyncio.run / gather
  • dict / set literals as closed compile-time lookup tables, plus pymcu.collections.FixedDict for mutable fixed-capacity maps — still no heap
  • f-strings with runtime interpolations and format specs, as stream writes or values
  • try / except / raise / finally with cross-function propagation (AVR and ARM)
  • @interrupt ISR handlers, asm("...") inline assembly (with operands on ARM)
  • CircuitPython and MicroPython compat packages, plus pymcu lint to vet a port

Not supported:

  • Open-ended dict / set mutation beyond FixedDict's fixed capacity (no heap hash tables)
  • Closures capturing mutable variables — use explicit parameters
  • *args / **kwargs, reflection (getattr / setattr / eval)
  • Anything whose size is only known at runtime: a slice read bound to a name (b = buf[0:n]), a runtime tuple, a comprehension filtered on a runtime condition

The compiler rejects unsupported features with a clear error at compile time — including the ones the hardware cannot honour, such as a runtime pin number, an image larger than the chip's flash, or static data that does not fit in SRAM. See the Language Limitations page for the full list.


CLI reference

Command Description
pymcu new <name> Scaffold a new project
pymcu build Compile src/dist/firmware.hex
pymcu flash Upload via avrdude
pymcu clean Remove build artifacts

Sustainability

Post-alpha development will be slower and community-driven. If PyMCU saves you time, consider sponsoring the project — the goal is $200-300/month to cover the AI tooling costs that made this first release possible and keep active development going.

Sponsor on GitHub


License

All components are licensed under the MIT License. Your compiled firmware output is entirely yours — no runtime license, no attribution required.


Contributing

See CONTRIBUTING.md and LANGUAGE_ROADMAP.md.

Credits

Special thanks to Richard Wardlow, creator of the original pyMCU project (2012). See CREDITS.md for the full acknowledgement.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pymcu_compiler-0.1.0a10-py3-none-win_arm64.whl (2.7 MB view details)

Uploaded Python 3Windows ARM64

pymcu_compiler-0.1.0a10-py3-none-win_amd64.whl (2.9 MB view details)

Uploaded Python 3Windows x86-64

pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_x86_64.whl (3.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_aarch64.whl (2.9 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

pymcu_compiler-0.1.0a10-py3-none-macosx_14_0_arm64.whl (2.8 MB view details)

Uploaded Python 3macOS 14.0+ ARM64

pymcu_compiler-0.1.0a10-py3-none-macosx_13_0_x86_64.whl (3.0 MB view details)

Uploaded Python 3macOS 13.0+ x86-64

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-win_arm64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 67b8eb2b101742da16b8f3643d86c4b3636cb983a2dd5a475347050b850d02c6
MD5 687fb40462757d86eef4a49d0ffa06c7
BLAKE2b-256 a4be8c13849262cd429961be530bb02fcb3e5ab6c3662c7e8303f0666bd3c837

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-win_arm64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 f5aa487f62ec180a0c58ff7f1c9856dddb8ec396cd3f7340ab4e52cdbae2ea69
MD5 6b8677cd6acaa64e6e97ef89185075e4
BLAKE2b-256 8d1d78329e8904ec799ce46dccfe12a403d34ee244cff36189fae7d5b2372143

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-win_amd64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 bdb7a71838bf712d23b8acb7cb6b6748510b9af791063c42351ed456ee62df4c
MD5 31c6c90c88c676ec5168faf4dd46221d
BLAKE2b-256 d82171e52b867c58ede82f5c144691ef60133760ae638f9c989ec153449b2586

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_x86_64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 cac0e4fa6cf6c483fb970e0179fdf9e1190b64507317f56687e1299e3909d257
MD5 38eca2bf3fc047ca5293395142dea509
BLAKE2b-256 f35c2742f03a4e7703ab968688be48e39c6528d475aba2a664b2584e21f06d71

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-manylinux_2_17_aarch64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 d3c5da8a0f208bc32afc9dea959da025e7c8ebf78f9b232acc923a1045921943
MD5 0beeb6d2ee848c11f58d96fc38890c4d
BLAKE2b-256 127d3905ec28191dd9df3e6f7f5d4ecd5658217527e9a3d1b11d735eb5e9fa90

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-macosx_14_0_arm64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymcu_compiler-0.1.0a10-py3-none-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for pymcu_compiler-0.1.0a10-py3-none-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 61072f1c557ed75fc8ea87e4f909d2de870997dfdbdba0da78a09ddb70104749
MD5 5ca554605840849ddc0780b37cb6192c
BLAKE2b-256 0c1c8f0876f8fb645f562cdfa56e041b7fb89307709839fa326dad2c160bd62b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymcu_compiler-0.1.0a10-py3-none-macosx_13_0_x86_64.whl:

Publisher: publish.yml on PyMCU/PyMCU

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.
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