ESP32-S3 · C3 · C6 — Xtensa and RISC-V

Real ESP32 firmware.
In your browser.

esp32sim is an instruction-level emulator for Espressif's ESP32-S3, C3 and C6, written in Rust. It boots the real mask ROM, the real bootloader and an unmodified application image — here in this tab, or on your command line as a hardware test bench that a coding agent can drive.

ROM → bootloader → appno patched firmwareno cloud, no accountsMIT
The energy panel firmware's first page: a price histogram for the day and four tiles
the frame the emulator wrote with --tft-png, 5 s after power-on
▶ Run it
Demos

Pick something to boot.

Every demo is a real firmware image booting through the real ROM inside this tab. Nothing runs on a server. The first load fetches the emulator (about 8 MB); firmware after that is small, except Linux's 16 MB flash.

ESP32-S3LVGL · touch · audio

Touch-LCD-4B energy panel

An LVGL home-energy panel on a 480×480 RGB display with GT911 touch. Swipe between pages, toggle tiles, then enable audio and play a SID tune from the last page.

Run →…with the SID playerfirmware: Joakim's esp32-screen
ESP32-S34-bit LLM · PIE SIMD

Pocket Tank, an LLM aquarium

A 14.3M-parameter transformer decides what each fish does, running from flash on the S3's PIE vector unit while the other core draws the tank. Slower than real time in a browser.

ESP32-S3encoder · LEDs · SID

Pocket Synth on the Atech 14-port board

Arduino firmware with an ST7735 display, a WS2812 ring, an encoder and buttons. Turn the knob, press the buttons, hear the SID voice. The jukebox variant plays real C64 tunes.

ESP32-C6802.15.4 · ST7789 · RISC-V

802.15.4 energy scanner

The Waveshare C6-LCD-1.47 scanning channels 11–26 with energy detect from the emulated radio, LVGL over SPI and DMA, the WS2812 blinking per scan. Press BOOT for single-channel mode.

node 1
UDP over 6LoWPAN · RPL
node 2
[I] RPL: created DAG with instance ID 0, DAG ID fd00::ff:fe00:1, rank 128
[I] RPL: sending a multicast-DIO with rank 128 to ff02::1a
[I] RPL: received a unicast-DIO from fe80::ff:fe00:2, instance_id 0 …
2 × ESP32-C6Contiki-NG · IPv6

A radio network in one tab

Two emulated C6 motes on one 802.15.4 medium: Contiki-NG's rpl-udp server as the DAG root and a client that joins, then sends a UDP request every 10 s and gets a reply. Hardware ACKs included.

For coding agents

A board the agent can run a thousand times.

Agents write embedded code well and test it badly: a board on a desk cannot be scripted, read back or reset from a shell. esp32sim can. It is the same emulator as the demos above, as a command-line tool any agent with shell access already knows how to use.

  1. Build the firmware as usual.ESP-IDF, PlatformIO or Arduino, unmodified. The emulator boots the real ROM and bootloader, so the app is the one that would go on the board.
  2. Run it with a script.Touches, button presses, encoder turns, GPIO levels and serial input at exact emulated times, with a run limit so the command always returns.
  3. Read the evidence.Console text on stdout, the display as PNG, audio as WAV, GPIO and interrupts as VCD, block coverage, profiles, interrupt latency — files a program can check.
  4. Fix, run again.The same input gives the same run, down to the instruction count, so a failing check stays failing until the fix is real. The project's own golden tests pin console text, WAV hashes and instruction counts.
the loop, from a shell
# build as always
$ idf.py build

# run it 8 emulated seconds with a script of touches, keep the display
$ esp32sim --board waveshare-lcd4b --boot rom \
    --bootloader build/bootloader/bootloader.bin \
    --ptable build/partition_table/partition-table.bin \
    --app build/app.bin --elf build/app.elf \
    --script test/swipe.txt --max-seconds 8 \
    --tft-png panel.png --vcd gpio.vcd --coverage-file cov.txt

# test/swipe.txt — one action per line, at emulated seconds
3.0  touch 450 240 1      # press near the right edge
3.1  touch 300 240 1      # drag left …
3.2  touch 100 240 1
3.3  touch 100 240 0      # release: the panel swipes to its next page
8.0  stop

# what the agent reads back (this run, verbatim)
I (725) main: up
[emu] stop: Halted — core0 231572900 + core1 66167543 insns in 3.0s wall
      = 100.5 Minsn/s; emulated 8.000s (1920000000 cycles)
[emu] wrote panel.png        # the second page, as the firmware drew it

Point an agent at docs/cli.md and a board name. Runs are single-process with no daemon and no device, so they parallelise like unit tests, and --no-jit is the oracle when a result looks wrong.

What's inside

Software that behaves like the chip.

Three SoCs across both of Espressif's CPU architectures. Most of the SoC model is shared; only the CPU crates are separate. The numbers are from the README and its tests.

ESP32-S3 mature

CPU
2 × Xtensa LX7 @ 240 MHz, PIE SIMD
Boots
ROM → bootloader → FreeRTOS → app
Boards
ST7735, ST7701S 480×480 + touch, AMOLED 1.8, WS2812, camera, I²S audio
Wi-Fi
virtual AP, WPA2, DHCP/DNS/NTP, NAT to the real network, HTTPS
Speed
block interpreter + AArch64 JIT, ~150–240 Minsn/s
vs silicon
JTAG lock-step, 8000 steps, 0 PC divergences

ESP32-C3 draft

CPU
1 × RISC-V RV32IMC @ 160 MHz
Boots
ROM → bootloader → FreeRTOS → app
Boards
console only
Wi-Fi
the same virtual AP: scan, WPA2, DHCP, NAT — one station
Decoder
161 k instructions vs objdump, 0 mismatches
vs silicon
console diff, 205/208 lines identical

ESP32-C6 draft

CPU
1 × RISC-V RV32IMAC @ 160 MHz
Boots
ROM → bootloader → FreeRTOS → app
Boards
ST7789 172×320 over SPI + DMA, WS2812, an 802.15.4 MAC with a shared medium
Wi-Fi
the same virtual AP: scan, WPA2, DHCP, NAT — one station
Decoder
126 k instructions vs objdump, 0 mismatches
vs silicon
console diff, 203/204 lines identical
Your own firmware

Boot your build in this tab.

Choose the chip and board, pick your bootloader, partition table and app (or a whole flash image), add ELF symbols and a script if you like, and boot. Files stay in the browser; nothing is uploaded.

Configure a machine →