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

Ecosystem Partner
Adafruit


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 146 bytes and flashed to an Arduino Uno

A real session: 9 lines of Python → pymcu build → 146 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) 146 B 0 B
PyMCU (MicroPython API) 146 B 0 B
PyMCU (CircuitPython API) 148 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  (146 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)   -- beta
pipx install --pip-args=--pre "pymcu-compiler[arm]"    # RP2040 / RP2350 (Pico)  -- alpha
pipx install --pip-args=--pre "pymcu-compiler[pic]"    # PIC16                   -- alpha
pipx install --pip-args=--pre "pymcu-compiler[all]"    # all of the above

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

The extras are not equally mature. [avr] is beta; [arm] and [pic] are alpha, and [all] installs alpha backends alongside the beta one. See Supported targets for what alpha means here.

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 --board arduino_uno --stdlib micropython
cd blink

That is the whole setup. pymcu new scaffolds a project that already builds: a pyproject.toml carrying the board, frequency, toolchain and dependencies, a src/main.py with a working blink, plus requirements.txt, a Makefile and VS Code tasks. Pass --stdlib circuitpython for the other API, or run it with no flags and it asks for what you left out.

3. The program it wrote for you

MicroPython style (--stdlib micropython):

# src/main.py
from machine import Pin
from time import sleep_ms

led = Pin(13, Pin.OUT)
while True:
    led.value(1)
    sleep_ms(500)
    led.value(0)
    sleep_ms(500)

CircuitPython style (--stdlib circuitpython):

# 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)

Both compile to bare-metal firmware for the same chip. The Quick Start walks the same path in more detail, including what each scaffolded file is for.

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)

Build against a compatibility layer, not against pymcu.hal.*.

The beta label covers the compiler frontend and the AVR backend. It is not a promise about every API those two can reach, and the difference between the three rows above matters:

  • pymcu-circuitpython and pymcu-micropython are the surfaces designed to hold still, because they do not define their own shape: they track digitalio / board and machine / utime as specified by CircuitPython and MicroPython. That is a reason you can check, not a guarantee we have earned yet, and both packages are still pre-1.0.
  • pymcu.hal.* is deliberately low-level, it is the layer being actively reshaped, and it may change between releases without a deprecation cycle. Reach for it when you need direct register access, @interrupt, asm() or @extern, and expect to revisit that code.

All three are deepest on the ATmega parts. board.* in particular is defined for the Arduino boards but not for ATtiny or for the RP2040 / RP2350, where you pass the pin number instead (digitalio.DigitalInOut(25)). On the alpha backends a compat call may resolve to a HAL module that does not implement it yet; the compiler says so at build time rather than emitting silently wrong code.


Supported targets

Not every backend is at the same maturity. The compiler frontend and the AVR backend are beta as of 0.1.0b1. ARM, PIC and RISC-V remain alpha.

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

What the labels mean.

  • beta: the language surface below is implemented and test-covered on this backend, and it is validated on real silicon (Arduino Uno, logic analyzer). The label covers the compiler frontend and the AVR backend, not the stability of every API you can reach through them: see Choosing an API for which surface to build against.
  • alpha: it builds and it runs, but parts of the language surface below are missing on this backend, it does not carry AVR's continuous silicon validation, and the API may change between releases. Specifically today: list[T] is AVR-only; try / except is unavailable on PIC; float, f-strings, generators and @interrupt are unavailable on PIC16 (PIC18 has float and generators); and PIC16F84A / PIC16F877A builds emit no configuration word, so the resulting image will not boot on real hardware until you program the fuses yourself. See Language Limitations.

How far each backend has been validated on real hardware. This is the part that most separates beta from alpha, so it is worth stating per backend rather than in one sentence:

Backend Hardware record
AVR Continuously validated on silicon. A logic-analyzer harness on an Arduino Uno decodes the board's UART and diffs it against CPython running the same source, so a semantic divergence is caught rather than argued about. Of the Adafruit libraries, only adafruit_ssd1306 and adafruit_hcsr04 have run on silicon for beta 1; the rest are compile and emulator measurements.
ARM Confirmed running on real Raspberry Pi silicon (Pico, Pico 2): blink, native f-strings, and the Python RTOS doing preemptive multitasking on the Cortex-M33, with clock and timer timing verified on a logic analyzer to better than 0.01%. What it does not yet have is the continuous differential harness AVR runs.
PIC Partially exercised on silicon (PIC18 GPIO, delay_ms, UART TX); most testing is on the PicSharp emulator. For the PIC16 parts listed above the build emits no configuration word, so the image does not boot until you program the fuses yourself.
RISC-V Emulation only, in qemu. It has never been run on a physical CH32V003.

RISC-V (CH32V003/V203) has a working backend in-tree but is not published on PyPI and has no install extra, so a pip/pipx install never provides it.


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
pymcu.hal.softspi SoftSPI — bit-bang, any GPIO
pymcu.hal.i2c I2C
pymcu.hal.softi2c SoftI2C — bit-bang, any GPIO
pymcu.hal.eeprom EEPROM — write(addr, val) / read(addr)
pymcu.hal.watchdog Watchdog — enable / 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.

The list below describes the AVR backend, which is the beta one. Items marked (AVR only) or with an explicit backend list are not available everywhere. See Supported targets.

Supported:

  • Integer types: uint8, int8, uint16, int16, uint32, int32 — with type inference for unannotated def parameters and returns
  • float (IEEE-754 single) on AVR, ARM and PIC18; PIC16 has no floating point at all
  • Fixed arrays buf: uint8[16] and bytearray everywhere; heap-bounded lists x: list[uint8] = list() (AVR only); on ARM and PIC, use a fixed array
  • 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) on AVR, ARM and PIC18; async / await with asyncio.run / gather on AVR, ARM and PIC18, both unavailable on PIC16
  • 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 (AVR and ARM only: PIC and RISC-V have no string-building path)
  • try / except / raise / finally with cross-function propagation (AVR and ARM); on PIC, use return codes, since only the ZeroDivisionError guard exists there
  • @interrupt ISR handlers (not on PIC16), asm("...") inline assembly everywhere (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

Written up elsewhere

  • Python on a Classic Uno on Arduino Project Hub — why the board that taught millions of us electronics never got to speak Python, and what changes once the interpreter is gone.

Sustainability

If PyMCU saves you time, consider sponsoring the project. The goal is $300/month, which covers the AI tooling, domains and hosting that keep development at its current pace. Sponsors at $10/month and above are listed in SPONSORS.md.

Sponsor on GitHub

Sponsors

PyMCU is built by one person, and these are the people helping keep it going.

Ecosystem Partner

Adafruit

Adafruit, the home of CircuitPython, sponsors PyMCU on GitHub. Thank you, pt and ladyada.


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.

Metadata

Release files for pymcu-compiler 0.1.0b1

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

Built distributions (wheels)

Table of built distributions (wheels) for pymcu-compiler 0.1.0b1
File
pymcu_compiler-0.1.0b1-py3-none-win_arm64.whl Python 3 none Windows ARM64 Details
pymcu_compiler-0.1.0b1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
pymcu_compiler-0.1.0b1-py3-none-macosx_14_0_arm64.whl Python 3 none macOS 14.0+ ARM64 Details
pymcu_compiler-0.1.0b1-py3-none-macosx_13_0_x86_64.whl Python 3 none macOS 13.0+ x86-64 Details

Total release size: 29.2 MB

Release files / pymcu_compiler-0.1.0b1-py3-none-win_arm64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-win_arm64.whl
Size 4.6 MB
Tags Python 3 Windows ARM64
SHA-256 checksum
How to use checksums
ad8f923914e1f04456292438dd01d0bab27d776635b7eeac91597089f9e43d08
BLAKE2b-256 checksum
How to use checksums
748fc2bb5b13377d0b38dc59df5aa54fd31d830e8c30db20a19a81ba2d1c836b
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 Oct 5, 2026.

Transparency log

Release files / pymcu_compiler-0.1.0b1-py3-none-win_amd64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-win_amd64.whl
Size 4.8 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
91f60001b18e222a757e6d4912a71fa37548c73a328f00862c22dda4f25bd0cb
BLAKE2b-256 checksum
How to use checksums
2cfd59a151c79ab04fb240e5c4f91b31bc36243f63fc28a28b097d4bd93b3709
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 Oct 5, 2026.

Transparency log

Release files / pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_x86_64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_x86_64.whl
Size 5.2 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
cf587161ddadc98b6cdb75e51d1ecc45064c957daa87ca810474b5b426c64d28
BLAKE2b-256 checksum
How to use checksums
10e100de560536115637d41ca2961e5d7dc413a6ec8279ba9a4302bd93c0e171
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 Oct 5, 2026.

Transparency log

Release files / pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_aarch64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-manylinux_2_17_aarch64.whl
Size 4.8 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
4df7ddefd22b3d9b27be49530a0c9ff1d343b9d9e8e60c93eb178e7c425a040a
BLAKE2b-256 checksum
How to use checksums
7596155e5e8f210652a56c64640ae4cf432493ef9b037451cfcbf087f6615b25
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 Oct 5, 2026.

Transparency log

Release files / pymcu_compiler-0.1.0b1-py3-none-macosx_14_0_arm64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-macosx_14_0_arm64.whl
Size 4.7 MB
Tags Python 3 macOS 14.0+ ARM64
SHA-256 checksum
How to use checksums
1e89c02ba84cf48fd6305d6912d93556946ed2d18b5a604384a6db08805b3342
BLAKE2b-256 checksum
How to use checksums
dedfac8435b2a48d336cf9c246a93198634219426e21d2cd2bb38005bf69fbfe
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 Oct 5, 2026.

Transparency log

Release files / pymcu_compiler-0.1.0b1-py3-none-macosx_13_0_x86_64.whl

Download URL pymcu_compiler-0.1.0b1-py3-none-macosx_13_0_x86_64.whl
Size 5.1 MB
Tags Python 3 macOS 13.0+ x86-64
SHA-256 checksum
How to use checksums
fed250ae2b74a91cead4bb0be1002cbc1a0ba05ccf0be8477e7905cb9171fbe3
BLAKE2b-256 checksum
How to use checksums
9e01889d41e8ec52b32ec2b4aac4b2bcec69e7f1a107a9a9462e2c60ba0d0053
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 Oct 5, 2026.

Transparency log

Security for pymcu-compiler 0.1.0b1

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.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for pymcu_compiler-0.1.0b1-py3-none-win_arm64.whl

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 Oct 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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