SearchA-ZR › Raspberry Pi Pico

Raspberry Pi Pico

2021 Open source · MIT Online

The real Raspberry Pi Pico, in the browser. The RP2040's two ARM Cortex-M0+ cores run on the vendored rp2040js SoC — a complete Raspberry Pi Pico with XIP flash, 264 KB SRAM, GPIO, timers, UART and a USB device controller. It boots the official MicroPython firmware to an interactive REPL over USB-CDC, and also runs bare-metal firmware, with the on-board LED (GPIO25) and serial console on screen and the whole ARM Cortex-M0+ wired into this site's shared Thumb debugger. Being a bare Pico, it is naturally silent — there is no on-board audio.

Visit the official Raspberry Pi Pico page ↗

Runs on: Web browser

Raspberry Pi Pico Online Emulator

Play Raspberry Pi Pico using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
MicroPython REPLRaspberry Pi PicoRaspberry Pi PicoopenOpen ⛶
Blink (on-board LED)Raspberry Pi PicoRaspberry Pi PicoopenOpen ⛶
Hello UART (serial)Raspberry Pi PicoRaspberry Pi PicoopenOpen ⛶

Chips

Notes

Embedding

The RP2040 SoC is Uri Shaked's rp2040js (MIT), a complete in-browser Raspberry Pi Pico: two Cortex-M0+ cores, XIP flash, 264 KB SRAM, the SIO/GPIO block, timers, UART, a USB device controller with a USB-CDC class, and the PIO. It is a TypeScript/ESM project; it was bundled once to a single file-safe browser global (window.rp2040js) with esbuild and is used unmodified — no core was reinvented.

// esbuild entry.ts --bundle --format=iife --platform=browser  ->  window.rp2040js
var R   = window.rp2040js;
var sim = new R.Simulator();          // owns the RP2040 + a simulation clock
var mcu = sim.rp2040;
mcu.loadBootrom(R.bootromB1);            // the real RP2040 B1 boot ROM
loadUF2(firmwareBytes, mcu);            // decode UF2 blocks straight into mcu.flash
mcu.core.PC = 0x10000000;              // enter the flashed image (XIP)
var cdc = new R.USBCDC(mcu.usbCtrl);    // USB-CDC = the MicroPython REPL serial port
cdc.onSerialData = function(bytes){ /* -> console */ };
mcu.uart[0].onByte  = function(b){    /* UART0 TX -> console */ };

We own the run loop so the debugger can drive it. Each frame runs a time-boxed budget of mcu.core.executeInstruction() (ticking the simulation clock by the returned cycle count); when the core is waiting (WFI/WFE) the clock is advanced to the next alarm, exactly like rp2040js's own Simulator. Everything is plain JavaScript state:

MemberKindWhat it does
mcu.core.executeInstruction()methodExecute one Thumb instruction on core 0, advancing core.PC; returns a cycle estimate. The single-step primitive.
mcu.core.registers (Uint32Array 16)fieldr0-r12, SP(13), LR(14), PC(15). core.xPSR, core.N/Z/C/V, core.PM round out the programmer's model — all readable and writable.
mcu.flash / mcu.sram / mcu.bootromfieldThe real memories as typed arrays — read side-effect-free for the hex + disassembly views.
mcu.writeUint8/16/32(addr,v)methodThe bus write path used by every store — wrapped to implement write-watchpoints.
mcu.gpio[25].valuegetterThe on-board LED pin (High/Low) — polled each frame to draw the LED.
cdc.sendSerialByte(b) / mcu.uart[0].feedByte(b)methodSerial RX in — the keyboard drives the MicroPython REPL through the USB-CDC port.

Debugger integration

The plug-in (pico-debug.js) describes the RP2040 to the shared debugger and nothing more — rp2040js is not patched:

  • Reused decoder. The Cortex-M0+ runs ARMv6-M Thumb, a subset of the existing /debugger/src/cpus/cortex-m7.js disassembler (written for the Playdate-ARM Cortex-M7). It is reused as-is under the display name "ARM Cortex-M0+"; no new decoder was needed.
  • Registers are read live each refresh: r0-r12, SP, LR, PC, xPSR, the APSR flags N Z C V, and PRIMASK / nPRIV. Each has a set() that writes straight back into core.registers / the core's flag fields.
  • Memory is exposed as three chips, all read side-effect-free directly off the typed arrays, each with its real base address so the disassembly lines up with PC: the XIP flash at 0x10000000 (disassembled), the SRAM at 0x20000000, and the 16 KB boot ROM at 0x00000000 (also disassembled — you can read the real Raspberry Pi bootrom).
  • Single step is one executeInstruction(). Breakpoints are host-side checks of core.PC against a set of addresses, run instruction-by-instruction when any are set. Write-watchpoints wrap mcu.writeUint8/16/32 and pause the loop the moment a watched bus address is written.

Architecture

The Raspberry Pi Pico (2021) is Raspberry Pi's first in-house microcontroller board, built around their RP2040:

  • Dual ARM Cortex-M0+ — two 32-bit ARMv6-M cores at up to 133 MHz (modelled here at 125 MHz), Thumb instruction set, sharing the bus through the SIO single-cycle I/O block.
  • Memory — 264 KB on-chip SRAM at 0x20000000; code executes in place (XIP) from external QSPI flash mapped at 0x10000000; a 16 KB mask boot ROM at 0x00000000.
  • On-board LED — a single user LED on GPIO25, driven through the SIO GPIO block. This is the Pico's only on-board indicator; there are no other lights, displays, buttons or audio on the bare board.
  • Serial — the RP2040 exposes two PL011 UARTs plus a full-speed USB 1.1 device controller. Firmware talks to a host either over UART0 or, as MicroPython and CircuitPython do, over a USB-CDC virtual serial port — which is where the MicroPython REPL appears.

The default firmware is the official MicroPython for RP2040 (.uf2) — real, unmodified firmware. It boots through the same path as on hardware and drops you at an interactive >>> prompt on the USB-CDC console. Because it is real ARM machine code on a real SoC model, every instruction single-steps in the debugger, and Python that touches machine.Pin(25) visibly toggles the on-board LED.

Honest limits. rp2040js is cycle-approximate, not cycle-exact; the second core, PIO, DMA and the fuller USB/peripheral surface are present but not all exercised by these firmwares; and the bare Pico has no on-board sound hardware (PWM audio needs an external speaker), so this emulator is naturally, deliberately silent.