This release is a pre-release and may not be stable for production use.
PyMCU
Python to bare-metal firmware — no runtime, no interpreter, no VM.
Explore the project »
Report a bug
·
Request a feature
·
Sponsor
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.
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-compileron PyPI while a PEP 541 request to reclaim thepymcuname is under review. Once approved, apymcumetapackage will aliaspymcu-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-circuitpythonandpymcu-micropythonare the surfaces designed to hold still, because they do not define their own shape: they trackdigitalio/boardandmachine/utimeas 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 / exceptis unavailable on PIC;float, f-strings, generators and@interruptare 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 unannotateddefparameters and returns float(IEEE-754 single) on AVR, ARM and PIC18; PIC16 has no floating point at all- Fixed arrays
buf: uint8[16]andbytearrayeverywhere; heap-bounded listsx: list[uint8] = list()(AVR only); on ARM and PIC, use a fixed array - Slices: equal-length assignment (including through
__setitem__, somicrocontroller.nvm[0:4] = b"..."compiles) and iteration with runtime bounds (for b in buf[0:n]) print()of abytearrayor a slice as the CPython repr, and of afloatwith two rounded decimals;s = "".join([chr(b) for b in buf])for bytes-to-stringfor,while,if,match / case,with,class,@inline,lambda- Generators (
yield) on AVR, ARM and PIC18;async/awaitwithasyncio.run/gatheron AVR, ARM and PIC18, both unavailable on PIC16 dict/setliterals as closed compile-time lookup tables, pluspymcu.collections.FixedDictfor 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 / finallywith cross-function propagation (AVR and ARM); on PIC, use return codes, since only theZeroDivisionErrorguard exists there@interruptISR handlers (not on PIC16),asm("...")inline assembly everywhere (with operands on ARM)- CircuitPython and MicroPython compat packages, plus
pymcu lintto vet a port
Not supported:
- Open-ended
dict/setmutation beyondFixedDict'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.
Sponsors
PyMCU is built by one person, and these are the people helping keep it going.
Ecosystem Partner
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)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logSecurity 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.
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 logA 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.