A Rust-based emulator that runs ESP32-C3, ESP32-C5, ESP32-C6, ESP32-H2, ESP32-P4, ESP32-S31 (RISC-V) and ESP32-S3 (Xtensa LX7) firmware binaries — CPU, memory, WiFi, BLE, Thread, Ethernet, crypto, and more. This repo distributes the installer, prebuilt release binaries, user-facing docs, and helper tools.
One-liner (Linux x86_64 / arm64, macOS Apple Silicon):
curl -fsSL https://raw.githubusercontent.com/espressif/esp-emulator/main/install.sh | sh
This downloads the latest binary to $HOME/.local/bin/esp-emu. If ~/.local/bin is not on your PATH, the installer prints the export PATH=... line to add.
Pin a specific version:
curl -fsSL https://raw.githubusercontent.com/espressif/esp-emulator/main/install.sh | sh -s -- --version 0.45.0
Other options: --check (print latest, no install), --bin-dir DIR, --force, --quiet. Full help:
curl -fsSL https://raw.githubusercontent.com/espressif/esp-emulator/main/install.sh | sh -s -- --help
The browser build is published to GitHub Pages on every release: https://espressif.github.io/esp-emulator/. Pick a chip, drop a merged flash image (.bin) on the page and press Run. The emulator runs entirely in your tab, the firmware never leaves your machine, and a live dashboard shows the console, instruction rate, cores, GPIO pads, registers and memory. Networking is not available from the hosted page (it needs a proxy on your machine, see BROWSER.md); use the native binary for that.
After install:
esp-emu update # refresh in place
esp-emu update --check # only print latest version
esp-emu --version # print currently installed version
esp-emu update re-runs the installer against the directory holding the running binary, so it updates wherever you first installed it.
test_apps/ holds the ESP-IDF projects the emulator is tested against.
test_apps/run_smoke.sh builds the smoke app for every chip (docker
espressif/idf:latest by default, or a sourced IDF_PATH) and runs it in
the esp-emu on your PATH; it passes when the app's RESULT_SUMMARY:
reports no failures. The same script runs on every published release against
ESP-IDF master (.github/workflows/release-smoke.yml).
./install.sh
test_apps/run_smoke.sh --chips esp32c3,esp32c6
SENSITIVE PMS monitors on C3 and S3 are enforced for the CPU and DMA, so ESP-TEE, IDF's memprot app and other isolation firmware see the same faults and violation reports as siliconCONFIG_ETH_USE_OPENETH firmware, plus Synopsys DesignWare GMAC for ESP32-P4's built-in EMAChostfwd, restrict mode, ICMP echo), TAP bridge (Linux), vmnet (macOS)ot_cli and ot_br on ESP32-C6 and ESP32-H2. Single-node forms a partition out of the box; two emulator instances form one Thread mesh (Leader + Child, or Border Router + end device) over a localhost UDP bridgeCONFIG_SECURE_FLASH_ENCRYPTION_KEY_SOURCE_KEY_MGRsocket://: a --uart-tcp HOST:PORT bridge plus --strap-mode 0x02 (UART download) lets esptool, espefuse, and idf.py flash drive the running emulator over TCP, matching the QEMU-Espressif workflow. See esptool / espefuse.CONFIG_SPIRAM=y) with per-chip memory maps and interrupt controllers. C5 is a hybrid: a C6-shaped peripheral map driven by a P4-style CLIC on a single core (2.4 GHz WiFi and BLE both work; 5 GHz and 802.15.4 are not modelled). (ESP32-S31 is in early bring-up)--gdb PORT serves a GDB remote stub (QEMU -s -S equivalent) for riscv32-esp-elf-gdb / xtensa-esp32s3-elf-gdb; --trace FILE records a per-task timeline for Perfetto with no guest instrumentation. See Execution Tracing and Profiling.Default ROM ELFs (C3 rev3, C5 rev100, C6 rev0, H2 rev0, P4 rev3, S3 rev0) are embedded in the binary, so --rom is optional for the common case. Pass --rom <path> only to override with a different silicon revision or a custom ROM (e.g. from ~/.espressif/tools/esp-rom-elfs/).
cd esp-idf
cd examples/protocols/https_request
idf.py set-target esp32c3
idf.py merge-bin
esp-emu --chip esp32c3 --firmware build/merged-binary.bin
| Flag | Default | Description |
|---|---|---|
--chip <CHIP> |
(required) | Target chip: esp32c3, esp32c5, esp32c6, esp32h2, esp32p4, esp32s3, or esp32s31 |
--firmware <PATH> |
(required) | Path to merged flash binary |
--rom <PATH> |
embedded | Path to ROM ELF file (overrides the built-in default for --chip) |
--elf <PATH>... |
— | Application ELF(s) for BLE/Thread interception, panic intercepts and symbolised backtraces (e.g. build/project.elf). At app start the emulator reads the ELF SHA-256 the running app's descriptor records and uses the ELF it names, so pass both builds for an OTA test (--elf old.elf new.elf) and the new build's ELF takes over at the reboot. An ELF from some other build stays out, with a warning. |
--no-panic-intercept |
off | With --elf, don't stop at the first firmware panic: the firmware's own panic handler runs and reboots or halts per its sdkconfig. For firmware that panics on purpose (task-watchdog reset tests, deliberate abort()); backtraces and BLE interception stay. |
--efuse <PATH> |
— | Path to eFuse binary (336 bytes, QEMU-compatible). State is always saved back on exit (eFuses are one-time-programmable). |
--timeout <DURATION> |
— | Exit after duration (e.g. 5s, 500ms) |
--exit-on <STRING> |
— | Exit with code 0 when UART output contains this string |
--batch-size <N> |
50000 |
Instructions per iteration |
--skip-bootloader |
off | Load app directly from partition table, skipping 2nd-stage bootloader (firmware-entry path; only meaningful with --skip-rom). Picks the app the IDF bootloader would: the OTA slot otadata selects, else factory, else ota_0, probing the other slots when the chosen one is empty |
--skip-rom |
off | Reset the CPU at the firmware/bootloader entry instead of the real mask ROM. Default executes the ROM, which is required for KEY_SOURCE_KEY_MGR flash-key recovery and other eFuse-driven cold-boot decisions. |
--save-state |
off | Save flash state to disk on exit (overwrites firmware file) |
--inject <DATA> |
— | Payload to inject into UART RX (supports \n escape). Repeatable. |
--inject-on <STRING> |
— | Trigger string in UART output that causes injection (paired with --inject). Repeatable. |
--net <BACKEND> |
tap0 if present on Linux, else user |
Network backend: user (slirp-style, any OS), user,hostfwd=tcp::H-:G,…, user,restrict=yes, user,dns=1.1.1.1, user,mdns-nat=yes (Matter / mDNS service NAT), tap,ifname=tap0 (Linux), vmnet (macOS) |
--wifi-ssid <SSID> |
myssid |
WiFi soft AP SSID broadcast to firmware |
--wifi-password <PASS> |
mypassword |
Passphrase (8-63 chars) for WPA2-PSK and WPA3-SAE. Empty string for open mode |
--wifi-auth <MODE> |
wpa2-wpa3 |
Soft AP security: wpa2-wpa3 (transition), wpa3-sae (WPA3 only, PMF required), wpa2-psk, open, wpa2-enterprise, wpa3-enterprise |
--wifi-radius <HOST:PORT,SECRET> |
— | RADIUS server the enterprise modes relay EAP to |
--wifi-sae-pwe <PWE> |
both |
SAE password element the AP accepts: both, h2e, hunt-and-peck |
--ble-hci <BACKEND> |
— | BLE HCI backend: tcp:host:port for Bumble/virtual controller, hci0 for Linux adapter |
--thread-sim <SPEC> |
— | IEEE 802.15.4 / Thread bridge, e.g. bind:9001,peer:127.0.0.1:9002. Forwards radio frames over localhost UDP to another emulator instance. Requires --elf. |
--uart-tcp <HOST:PORT> |
— | Bridge UART0 to a TCP server (e.g. 127.0.0.1:5555); esptool connects via socket://. Mirrors QEMU's -serial tcp::PORT,server,nowait. While active, UART RX comes from the socket and TX goes to it (stdin/stdout disconnected). |
--uart1-tcp <HOST:PORT> |
— | Bridge UART1 to a TCP server. Side channel for simulating an external serial device (sensor, GPS, modem) with a host-side script — UART0 keeps stdin/stdout and --exit-on/--inject-on. Without a connected client, UART1 TX is disc |
| 日期 | 榜单 | 排名 | 周期新增 | 总 STAR |
|---|