# rp2040js runs Pico firmware in Node, with a GDB server on port 3333

> rp2040js is a JavaScript emulator of the Raspberry Pi Pico RP2040 used by the Wokwi simulation platform. It runs native hex images, MicroPython and CircuitPython in a terminal, and it is a poor fit for anything that needs a real flash controller.

**wokwi/rp2040js** — A Raspberry Pi Pico Emulator in JavaScript

- Repository: https://github.com/wokwi/rp2040js
- Stars: 531 · Forks: 76
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/wokwi-rp2040js

## An RP2040 emulator that lives inside Node

rp2040js is a Raspberry Pi Pico emulator written in TypeScript. The README describes it as the emulator behind the Wokwi Simulation Platform, and its pitch is short: it blinks, it runs Arduino code, and it even gives you the MicroPython REPL.

The interesting part is where it runs. There is no separate simulator binary to install and no GUI. The package exposes a Node API, ships a CLI through npm scripts, and is built around two executables in the demo folder, emulator-run.ts and micropython-run.ts. That means a Pico can be booted inside a Node process, which is what makes it usable from a test runner.

The package is published as rp2040js with dual module and CommonJS entry points, and the exports map also exposes a separate ./gdb-tcp-server subpath, so the debugger component can be imported on its own. Node 18 or newer is required. The repository carries demo scripts for loading flash, reading Intel HEX files and emulating a bootrom sequence, and the CI workflows include dedicated jobs for MicroPython and for the Pico SDK.

## Running native firmware needs a hex image you build yourself

The native code path does not ship a prebuilt firmware blob. You are expected to build hello_uart from the raspberrypi/pico-examples repository, copy the resulting hello_uart.hex into the rp2040js root directory, and then start the emulator:

```bash
npm install
npm start
```

The npm start script runs the demo through tsx, so the demo is executed straight from the TypeScript sources rather than from a build artifact.

If you already have your own image, the path can be given on the command line, and UF2 files are supported alongside the Intel HEX format:

```bash
npm run start -- --image ./my-pico-project.uf2
```

Once it is running, a GDB server is available on port 3333, and anything written to UART0 is printed to the console. That pairing is the point of the project for firmware developers: a debug server that speaks the protocol your GDB client already knows, in a process you can start from a script and tear down afterwards.

## MicroPython and CircuitPython both boot from a UF2 file

For the interpreter path, the emulator loads a full MicroPython UF2 image rather than a small program. The README names a specific firmware build, RPI_PICO-20230426-v1.20.0.uf2, to download and place in the rp2040js root directory, then start:

```bash
npm install
npm run start:micropython
```

You land in the MicroPython REPL and leave it with Ctrl+X. A different image can be loaded with the --image option, and the GDB server can be turned on with a flag:

```bash
npm run start:micropython -- --image=my_image.uf2
```

```bash
npm run start:micropython -- --gdb
```

CircuitPython uses the same machinery. You follow the MicroPython steps but download adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2 instead, and the start script differs:

```bash
npm install
npm run start:circuitpython
```

The start:circuitpython script is the micropython runner with a --circuitpython argument added, which is a reasonable place to look if you want to understand how the two interpreters are distinguished. The experience after the REPL appears is the same, including Ctrl+X to exit and the --image and --gdb options.

## The expect-text flag turns a REPL session into a CI assertion

The most practical feature for automated use is --expect-text. When it is given, the emulator looks for the specified text in the serial output and exits with code 0 if the text was found, or 1 if it was not. That converts an interactive REPL into something a test job can assert on, without a human typing at a prompt.

The README points at the MicroPython CI test in the repository's own workflow file as the worked example, which is the place to look for the exact invocation pattern. The repository also has a dedicated script for the MicroPython SPI test, run through tsx rather than through the test runner, which shows that not every check fits the standard vitest flow.

Together with the GDB server on port 3333, this gives you two quite different testing styles in one package. --expect-text is a black box check that the firmware reached a certain state, while the debugger is a white box tool for stepping through code. The emulator is a library first and a demo script second, which is the reverse of how most hardware projects are packaged.

## A LittleFS image can be attached, but writing to it is not implemented

The MicroPython mode supports a filesystem, and this is where the emulator's limits show. The mechanism is to place a LittleFS formatted image called littlefs.img in the rp2040js root directory, and main.py is then started automatically from that filesystem. The README links to a script for building such an image with littlefs-python:

```python
from littlefs import LittleFS
files = ['your.py', 'files.py', 'here.py', 'main.py']
output_image = 'output/littlefs.img'  # symlinked/copied to rp2040js root directory
lfs = LittleFS(block_size=4096, block_count=352, prog_size=256)
for filename in files:
    with open(filename, 'rb') as src_file, lfs.open(filename, 'w') as lfs_file:
        lfs_file.write(src_file.read())
with open(output_image, 'wb') as fh:
    fh.write(lfs.context.buffer)
```

The limitation is stated in one line and is not a rounding error: the filesystem is not writeable, because the SSI peripheral required for flash writing is not implemented yet. A script that tries to open a file for writing will fail. The README links to a discussion in issue 88 for anyone who wants to work around it.

So a multi file MicroPython project can be booted from flash, and the code in it can be read and executed, but it cannot save state between runs. That is fine for a scripted test and useless for a program that logs to a file.

## CircuitPython takes a FAT12 image built with mkfs.vfat

CircuitPython does not use LittleFS, so the image building steps differ. The README describes creating a FAT12 filesystem on Linux with the truncate and mkfs.vfat utilities:

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

The image is then mounted as a loop device and files are copied into it:

```bash
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
```

These steps need Linux specific tools and root level mount permissions, so the CircuitPython path is less convenient than the MicroPython one on a machine that is not Linux. The README also notes that while CircuitPython does not typically use a writeable filesystem, that functionality is unavailable here for the same reason as the MicroPython case.

One detail worth knowing: the mount step is a host operation on an ordinary file, not something the emulator performs. The emulator consumes the resulting image the same way it consumes a LittleFS image, with main.py started from the filesystem.

## Where the Wokwi Simulator and real hardware sit

The README separates two audiences. If you only want to play with a simulated Pico, it points at the online Wokwi Simulator and lists ready made projects, including a traffic light, an LCD1602 hello world, a MicroPython blink and a MicroPython seven segment counter. Those run in a browser, with no local toolchain at all.

The difference in approach matters. The browser simulator gives you a visual, part by part canvas, where the wiring is a diagram you draw and the code is edited alongside it. rp2040js is the engine underneath, not the drawing surface, and it gives you a terminal, a GDB server and a machine readable exit code. One is a workbench for a human, the other is a component for a script.

Against real hardware the differences are sharper. A physical Pico has a real SSI flash controller, real USB, and real timing on its GPIO, and the emulator has none of those. There is also a separate project linked from the README, the wokwi-pi-pico part documentation and the Pi Pico MicroPython guide, for people who want the part description rather than the emulator.

## Licence, release rhythm and the cost of an upgrade

The project is released under the MIT licence, copyright 2021-2023 Uri Shaked, with the LICENSE file at the repository root. That is a permissive licence and carries no obvious obstacle to embedding the emulator in a commercial test harness, though this is not legal advice and the file itself is the authority.

The repository is not archived and the last push was on 2026-10-18, with v1.4.1 published the same day. Before that came v1.4.0 on 2026-09-25 and v1.3.4 on 2026-09-09, so the version line has moved quickly through the 1.3 and 1.4 series.

An upgrade path exists but is not described in the README, and that is the gap to keep in mind. Because the emulator runs real MicroPython and CircuitPython UF2 images, a firmware update and an emulator update are separate decisions, and a newer interpreter image may expect peripherals the emulator has not implemented. Pinning the image name, as the README does with the dated RPI_PICO-20230426-v1.20.0.uf2 file, is the practice the documentation itself implies, even though it never says so outright.

## Conclusion

rp2040js is the right tool for CI jobs that need a Pico to boot, for developers who want to debug firmware without hardware on the desk, and for anyone who wants the MicroPython or CircuitPython REPL without flashing a board. It is the wrong tool for a project that depends on real GPIO timing, real flash writes, or a real USB stack, because the SSI peripheral that drives flash writing is not implemented and the filesystem is not writeable. Before adopting it, check whether your test only needs a REPL to answer, and confirm that the read only filesystem behaviour in the MicroPython and CircuitPython sections will not break a script that saves state.

## FAQ

### How do I run a MicroPython script on the rp2040js emulator?

Download the RPI_PICO-20230426-v1.20.0.uf2 firmware, place it in the rp2040js root directory, then run npm install followed by npm run start:micropython, which gives you a MicroPython REPL that you leave with Ctrl+X.

### Can rp2040js be used in automated tests?

Yes. The --expect-text option looks for a given string in the serial output and exits with code 0 when it is found and code 1 when it is not, and the repository's own MicroPython CI workflow shows the pattern.

### Does the rp2040js filesystem support writing files?

No. The README states the filesystem is not writeable because the SSI peripheral required for flash writing is not implemented yet, and points to issue 88 for a possible workaround.

### How do I debug firmware running in rp2040js?

A GDB server is available on port 3333, and for the MicroPython and CircuitPython demos it can be enabled with the --gdb flag, for example npm run start:micropython -- --gdb.

### Can rp2040js run CircuitPython?

Yes, the start:circuitpython script runs the same micropython-run.ts demo with a --circuitpython argument, and the README notes the REPL experience, the --image and --gdb options work the same way once it starts.

## Sources

- [Issues](https://github.com/wokwi/rp2040js/issues)
- [License: MIT](https://github.com/wokwi/rp2040js/blob/main/LICENSE)
- [README](https://github.com/wokwi/rp2040js/blob/main/README.md)
- [Releases](https://github.com/wokwi/rp2040js/releases)
- [wokwi/rp2040js on GitHub](https://github.com/wokwi/rp2040js)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wokwi-rp2040js
