Skip to main content
Pre-release

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

rp2040py

license pypi version python versions Pre-commit Test MicroPython Releases Test Pi Pico SDK coverage

Raspberry Pi Pico (RP2040) Emulator in Python — a faithful port of rp2040js. It blinks, runs native code, and even the MicroPython REPL!

See docs/PORTING.md for the file-by-file port status against upstream rp2040js.

Installation

pip install rp2040py

or, with uv:

uv add rp2040py       # into a project
uv tool install rp2040py   # as a standalone CLI tool
uvx rp2040py ...           # run without installing at all

Any of these gives you the rp2040py console script (python -m rp2040py works identically), so the emulator is runnable without a git checkout - see Run the demo project below for the checkout-equivalent commands.

Run the demo project

The commands below assume rp2040py is installed (pip install rp2040py / uv add rp2040py / uv tool install rp2040py, or run ad hoc with uvx rp2040py ...). From a checkout of this repo instead, each maps 1:1 onto uv run python demo/*.py (demo/*.py are thin wrappers around the same src/rp2040py/cli code):

rp2040py subcommand Checkout equivalent
rp2040py run ... uv run python demo/emulator_run.py ...
rp2040py micropython ... uv run python demo/micropython_run.py ...
rp2040py bench ... uv run python demo/benchmark.py ...

Native code

You'd need to get hello_uart.hex by building it from the pico-examples repo, then copy it to the rp2040py root directory and run:

rp2040py run
# or, without installing:
uvx rp2040py run

You can also specify the path to the image on the command line and/or load a UF2 image:

rp2040py run --image ./my-pico-project.uf2

A GDB server will be available on port 3333, and the data written to UART0 will be printed to the console.

MicroPython code

No manual download needed: just run

rp2040py micropython
# or, without installing:
uvx rp2040py micropython

and enjoy the MicroPython REPL! Quit the REPL with Ctrl+X. The first run fetches the recommended MicroPython build (1.21.0, currently) from micropython.org into the current directory and reuses that local file afterwards. 1.21 is recommended: it does far less work before dropping to the REPL prompt than newer releases, so it boots dramatically faster in the emulator (see the benchmark below). Newer releases work too, just slower to reach the REPL - e.g. 1.28.0.

A different version, a local UF2 file, or a CircuitPython version (--circuitpython, see below) can be loaded by supplying the --image option - a known version tag (1.28.0), or a path to a UF2 file already on disk:

[!TIP] Booting real firmware means executing millions of Thumb instructions through a pure-Python interpreter, which is dramatically slower than V8 JIT-compiling the equivalent JS in rp2040js. Measured with demo/benchmark.py booting MicroPython 1.28 + littlefs to the REPL:

Interpreter Time
CPython 3.10 221.11s
CPython 3.14 + PYTHON_JIT=1 121.74s (~1.8x)
PyPy 3.10 9.59s (~23x)

For CPU-bound runs, PyPy is the clear winner: uv run --python pypy3.10 --no-dev -- rp2040py micropython ... (or ... -- python demo/micropython_run.py ... from a checkout). See docs/PORTING.md for the full breakdown (including a synthetic instructions/sec benchmark) and CI's python_runtime matrix, which tests all three.

This is also why 1.21 is the recommended version: on the same machine and CPython 3.10, 1.21 reaches the REPL in 6.85s (2,000,000 steps) versus 1.28's 160.35s (65,000,000 steps) - over 20x fewer steps to boot the same emulator. 1.28 works fine too (as does the littlefs image produced by mklittlefs, mounting correctly on both), it's just a much slower REPL to reach interactively.

rp2040py micropython --image 1.28.0
rp2040py micropython --image my_image.uf2

A GDB server on port 3333 can be enabled by specifying the --gdb flag:

rp2040py micropython --gdb

For using the MicroPython demo code in tests, --expect-text can come in handy: it will look for the given text in the serial output and exit with code 0 if found, or 1 if not found. You can find an example in the MicroPython CI test.

For one-shot, non-interactive runs (like micropython's own CLI), pass one of -c <command>, -m <module>, or a script <filename> - mutually exclusive, matching [-c <command> | -m <module> | <filename>]. Instead of dropping into the REPL, rp2040py boots the device, runs it via the raw-REPL protocol, prints its stdout/stderr, and exits with the device's exit status (0 on success, 1 if it raised):

rp2040py micropython -c "print(1 + 1)"
rp2040py micropython -m sys
rp2040py micropython path/to/script.py

Filesystem support

With MicroPython, you can use the filesystem on the Pico. This becomes useful as more than one script file is used in your code. Just put a LittleFS formatted filesystem image called littlefs.img into the rp2040py root directory, and your main.py will be automatically started from there. A different path can be supplied with --littlefs (it's silently skipped, not an error, if the file doesn't exist).

The mklittlefs subcommand builds such an image (requires the optional fs extra: pip install rp2040py[fs] / uv sync --extra fs). The first file becomes main.py; if the output image already exists, it's opened and updated in place rather than reformatted:

rp2040py mklittlefs littlefs.img your_main.py your.py files.py here.py

--disk-version {2.0,2.1} selects the littlefs on-disk format (defaults to 2.0): MicroPython <=1.21's bundled littlefs can only mount 2.0, while 1.28's reads both - see docs/PORTING.md for why.

Currently, the filesystem is not writeable, as the SSI peripheral required for flash writing is not implemented yet.

CircuitPython code

To run the CircuitPython demo, follow the directions above for MicroPython but add --circuitpython:

rp2040py micropython --circuitpython

and start the CircuitPython REPL! As with MicroPython, the firmware (8.0.2 by default) is downloaded automatically on first use; a different version or a local file can be given via --image (e.g. --image 10.2.1 or a path to an already-downloaded UF2). The rest of the experience is the same as the MicroPython demo (Ctrl+X to exit, the --gdb option, etc).

Filesystem support

For CircuitPython, you can create a FAT12 filesystem in Linux using the truncate and mkfs.vfat utilities:

truncate fat12.img -s 1M  # make the image file
mkfs.vfat -F12 -S512 fat12.img  # create the FAT12 filesystem

You can then mount the filesystem image and add files to it:

mkdir fat12  # create the mounting folder if needed
sudo mount -o loop fat12.img fat12/  # mount the filesystem to the folder
sudo cp code.py fat12/  # copy code.py to the filesystem
sudo umount fat12/  # unmount the filesystem

While CircuitPython does not typically use a writeable filesystem, note that this functionality is unavailable (see the MicroPython filesystem support section for more details).

Library API

Everything above is the CLI, but the emulator is also usable programmatically - e.g. to run code against a device and check its output the way Thonny does over a real serial port, from a test suite or another tool. rp2040py.device.MicroPythonDevice boots a UF2 image and lets you run code on it via the same raw-REPL protocol mpremote run/tools/pyboard.py use, interrupting anything already running on the device first (e.g. an auto-run main.py from a littlefs image).

Blocking (exec()/exec_file()) is the simplest form - each call returns once the device finishes, or raises TimeoutError after timeout elapses (30s by default, since unlike the CLI there's no Ctrl+C to fall back on):

from rp2040py.device import MicroPythonDevice

with MicroPythonDevice("RPI_PICO-20231005-v1.21.0.uf2") as device:
    stdout, stderr = device.exec("print(1 + 1)")
    assert stdout == b"2\r\n"

    stdout, stderr = device.exec_file("my_script.py")

Callback style, via exec_async()'s concurrent.futures.Future - no separate API needed, Future.add_done_callback() does this out of the box:

def on_done(future):
    stdout, stderr = future.result()
    print(stdout.decode())


device.exec_async("print(1 + 1)").add_done_callback(on_done)

asyncio, via astart()/aexec()/aexec_file():

async def main():
    async with MicroPythonDevice("RPI_PICO-20231005-v1.21.0.uf2") as device:
        stdout, stderr = await device.aexec("print(1 + 1)")

All of these - blocking, callback, and asyncio - share one ThreadPoolExecutor(max_workers=1) per device: since the device only has a single REPL channel and can't run two exec()s at once, calling exec_async()/aexec() again before a previous call finishes doesn't raise, it just queues behind it and runs once its turn comes. This is exactly what powers the CLI's own micropython -c/-m/<filename> batch mode - it's a caller of this same API, not a separate implementation. start()/start_async()/stop() are available directly if you want more control over the lifecycle than the context manager gives you.

Learn more

  • rp2040js — the upstream TypeScript emulator this project is ported from.
  • docs/PORTING.md — port status, file by file.

License

Released under the MIT license. Copyright (c) 2021, Uri Shaked. Copyright (c) 2026, Dmytro Yaroshenko.

Download files

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

Source Distribution

rp2040py-0.1.0b3.tar.gz (201.4 kB view details)

Uploaded Source

Built Distribution

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

rp2040py-0.1.0b3-py3-none-any.whl (131.3 kB view details)

Uploaded Python 3

File details

Details for the file rp2040py-0.1.0b3.tar.gz.

File metadata

  • Download URL: rp2040py-0.1.0b3.tar.gz
  • Upload date:
  • Size: 201.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for rp2040py-0.1.0b3.tar.gz
Algorithm Hash digest
SHA256 863b22a1c5bfe5b8003732ea17586948f46283984bdb6fde07fdade59b463213
MD5 20a785420ea9c2573e252ec7e03ea30b
BLAKE2b-256 31c0b30097fc1b12e5c38e9d350c998cc60def143c2c56c10eb86c96336cbb64

See more details on using hashes here.

File details

Details for the file rp2040py-0.1.0b3-py3-none-any.whl.

File metadata

  • Download URL: rp2040py-0.1.0b3-py3-none-any.whl
  • Upload date:
  • Size: 131.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for rp2040py-0.1.0b3-py3-none-any.whl
Algorithm Hash digest
SHA256 dea7d9c4b50313d073f3909b836846646b9a6205a253b97448694a6960d6ac53
MD5 b68ae93712ef185ed51b55445c84c5f4
BLAKE2b-256 4f488c37b7381ad3938cba6e0f6bc9a01705361f9f02e714d8808a52c8d66a59

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page