about the emulator
A whole ESP32, simulated honestly.
esp32-emu boots the real ESP32 boot ROM and runs real compiled Arduino / ESP-IDF firmware on a cycle-counted dual-core Xtensa LX6 model. There is no instruction-set paraphrase and no UART spoofing: every byte on the demo terminal was transmitted by emulated silicon.
How it works
Four layers, each doing the job it is best at.
1 · Core engine (Rust to WASM)
Both LX6 cores execute inside a single WebAssembly module compiled from Rust. A batched core_run export advances up to 512 interleaved iterations per host call, with CCOUNT sync per instruction. The core is self-timed: it tracks its own cycle count and fast-forwards idle stretches straight to the next timer alarm.
2 · Native peripherals (same module)
UART, GPIO, SPI, I2C, timers, RMT, TWAI, ADC, touch, SDMMC, EMAC, WiFi MAC and more live in Rust behind a page-table dispatch. A full firmware run issues zero fallback calls into JavaScript for peripheral traffic.
3 · Host bridges (JavaScript)
What cannot live in WASM stays out: the worker-thread host, the SharedArrayBuffer UART ring, the simulation clock source, the WiFi gateway socket, and the compile service that turns Arduino sketches into flash images.
4 · This page
The demo tab spawns the same worker the Node test-suite uses, feeds it a prebuilt 4 MB flash image, and renders UART0 into the terminal. The ESP32-CAM toggle applies the real module preset: 4 MB PSRAM and the AI-Thinker pinout.
Speed, measured not marketed
Throughput is reported in true MIPS: retired instructions per wall-clock second, counted in the WASM cores themselves — not the simulator's step budget. Two numbers matter.
# reproduce a true-MIPS number (Node, repo root; needs :5525 for images) node /tmp/fwex5/measure-inst.mjs gpio # per-demo pass/fail (needs the compile server on :5525) node tests/test-worker-gpio.mjs # … one file per peripheral
Key facts
- Chip
- ESP32 (Xtensa LX6, dual core, 240 MHz model)
- Boards
- ESP32 dev module · ESP32-CAM (AI-Thinker, 4 MB PSRAM)
- Firmware
- Arduino / ESP-IDF 3.3.10, compiled with arduino-cli
- Boot
- Real boot ROM (esp32-v3-rom.bin) + real bootloader
- Engine
- Rust → wasm32-unknown-unknown, batched core_run
- Host
- Node worker_threads · browser Web Worker + SharedArrayBuffer
- UART
- Ring buffer over SAB, 115200 8-N-1 model
- Honest limits
- BLE advertising needs the baseband (not emulated; ADV/GATT marks only via callback injection); WiFi RF needs the gateway