Files
darceye 9284a9c480 Bilingual documentation: English default + Chinese .zh-cn.md (0.5.1)
- Every doc (README, AGENT, docs/*) now ships as English under the default
  filename and Chinese as *.zh-cn.md; both versions cross-link in their
  first line, in-document links stay within the same language
- AGENT.md §7 records the keep-both-languages-in-sync rule (same commit,
  no translating later); CHANGELOG kept in both languages
- Docs-only change; version 0.5.1 synced in the usual three places
2026-10-30 21:33:38 +08:00

5.1 KiB
Raw Permalink Blame History

English | 简体中文

esp-emulator browser API summary (wasm v0.51.0)

A reverse-engineered inventory of everything the closed-source esp_emu_bg.wasm exposes to JS. Sources: the official www/pkg/esp_emu.js (wasm-bindgen glue), official docs (esp-emulator README/BROWSER.md), and this project's M0 measurements (tests/m0_probe.mjs, conclusions in FEASIBILITY.md). Re-run the probe and re-check this document after an upstream upgrade.

1. Loading & initialisation

import init, { WasmEmulator, initSync } from './pkg/esp_emu.js';
await init();                     // fetch+instantiate esp_emu_bg.wasm (relative to esp_emu.js)
// or in Node: initSync(fs.readFileSync('esp_emu_bg.wasm'))   // synchronous init
const emu = new WasmEmulator('esp32c3');   // 'esp32c3'|'esp32c5'|'esp32c6'|'esp32h2'|'esp32p4'|'esp32s3'|'esp32s31'

2. Method inventory (all synchronous; no callbacks — polling only)

Lifecycle / loading

method notes
load_firmware(Uint8Array) must be a merged flash image (bootloader@0x0 + partition table@0x8000 + app@0x10000, from idf.py merge-bin); a bare app bin never boots
load_rom_elf(Uint8Array) / load_default_rom() / has_default_rom() ROM; embedded version used by default
load_efuse(Uint8Array) / load_app_elf(Uint8Array) eFuse / app ELF (for BLE symbol interception)
set_boot_from_rom(bool) call before load_firmware
restart() / needs_restart() soft reset (flash preserved) / firmware called esp_restart

Execution (host-driven loop)

method notes
run_batch(n) → string executes ≤n instructions, returns the batch's UART0 output string (the only output channel)
throttle_delay_ms(now) → number drift from wall clock; the host sleeps by this to pace to real time
cycles() / cycles_per_us() cycle counter / clock (C3 = 160)

CPU state

method notes
pc() / hart_pc(h) / num_harts() / is_xtensa() PC, per-hart PC, hart count, ISA
registers() → Uint32Array / get_reg(i) RV32: x0..x31; Xtensa: a0..a15 + PS,SAR,WINDOWBASE,WINDOWSTART,EXCCAUSE,EXCVADDR,EPC1,LBEG,LEND,LCOUNT
read_memory(addr, len) → Uint8Array bus read; MMIO is readable (LEDC/RMT/GPIO matrix etc.), unmapped returns 0, 4 KiB per call

GPIO (this project's main interface)

method notes
gpio_set_input(pin, high) drives a pad input level; firmware reads it and GPIO interrupts fire (M0: 6/6); default level high
gpio_state() → [out_lo, out_hi, enable_lo, enable_hi] mirror of the GPIO0-63 output and output-enable registers; only CPU-direct GPIO writes appear here — signals through the GPIO matrix (LEDC/RMT etc.) do not (M0 measurement)

UART / network

method notes
uart_input(Uint8Array) inject bytes into UART0 RX
set_wifi_config(ssid, pwd) / set_wifi_auth(ssid, pwd, auth, sae_pwe) simulated soft AP
wifi_rx_push(Uint8Array) / wifi_tx_drain() → Uint8Array ethernet frames ↔ WiFi; TX is u32-length-prefixed concatenation, needs an external WebSocket TAP proxy (tools/ws-net-proxy.py)

3. Register-level facts measured in M0 (ESP32-C3)

fact value / notes
LEDC duty registers 0x60019000 + 0x14*n + {0,4,8,C,10} (CONF0/HPOINT/DUTY/CONF1/DUTY_R); reg = duty×16 (bits[18:4]); precisely readable via read_memory (M0 Q2b 53/53)
RMT channel RAM 0x60016400 + 0xC0*n, 48 words/channel; symbols remain after transmission and can be decoded afterwards (M0 Q3b); word format d0:15,l0:1,d1:15,l1:1
GPIO matrix routing GPIO_FUNCn_OUT_SEL = 0x60004000+0x554+4n; LEDC0 signal = 0x2d, RMT ch0 = 0x33 → wiring can be auto-discovered
IO_MUX (C3) GPIO0..11 contiguous at 0x60009000+0x04+4n
Time fast-forward during vTaskDelay cycles() jumps ahead without spending full CPU cycles → pin waveforms inside CPU sleep windows cannot be captured (independent of sample rate)
Misc GPIO_COUNT: C3=22 / C5=29 / C6=31 / H2=28 / P4=55 / S3=49 (no 22-25) / S31=32

4. Official worker message protocol (www/worker.js; this project replicates and extends it)

  • page→worker: init{wasmUrl}, load{chip,firmware,ssid,password,rom?,efuse?}, start, stop, step, reset{chip}, uart_input{data}, gpio_input{pin,high}, read_mem{addr,len}, mem_watch{addr,len|null}, net_connect{url}, net_disconnect, set_batch_size{size}
  • worker→page: ready, loaded{chip,pc,isXtensa,numHarts,cyclesPerUs}, uart_output{data}, status{pc,cycles,mips,now}, snapshot{regs,pcs,gpio,cycles,mem?} (150ms throttle), mem{addr,bytes}, step{output,pc,cycles}, restarted, reset{reloaded,pc?}, net_status{connected}, error{message}

5. Beyond the wasm surface (browser build lacks these; CLI-only)

--uart1-tcp (external serial device), --i2c-dev (dumb-register I2C slave), --rmt-loopback, --control-tcp (flash write/erase), --ble-hci, --thread-sim, GDB stub, snapshot saving. These are exactly the capabilities the M5 roadmap waits on (see ROADMAP.md).