Show HN: Esp32sim – 在浏览器中运行 ESP32 固件
摘要
Show HN: esp32sim 是一个用 Rust 编写的 ESP32 模拟器,支持 Xtensa(ESP32、S3)与 RISC-V(C3、C6)四种芯片的指令级仿真,可在浏览器中通过 WebAssembly 直接运行真实固件。
查看缓存全文
缓存时间: 2026/10/17 16:49
mikroverk/esp32sim
来源: https://github.com/mikroverk/esp32sim
esp32sim — 用 Rust 实现的 ESP32 模拟器:
Xtensa(ESP32、S3)与 RISC-V(C3、C6)
对四款 ESP32 SoC 进行指令级模拟,覆盖 乐鑫的两种 CPU 架构。每一款都启动真实的 mask ROM、真实的二级 bootloader 以及未经修改的应用镜像——没有打过补丁的固件、没有挡路的桩(stub)——并对 SoC 建模到足以端到端运行真实项目的程度。
无需云端,无需账号。MIT 许可。经典 ESP32 也会在双 LX6 核心上启动其 ECO3 ROM,并支持 GPIO、UART 和定时器;--board ttgo-tdisplay 会加上 LilyGO TTGO T-Display 的 ST7789 和按键。
| ESP32-S3 | ESP32-C3 | ESP32-C6 | |
|---|---|---|---|
| CPU | 2 × Xtensa LX7 @ 240 MHz | 1 × RISC-V RV32IMC @ 160 MHz | 1 × RISC-V RV32IMAC @ 160 MHz |
| 命令 | esp32sim | esp32sim-c3 | esp32sim-c6 |
| 核心 crate | xtensa-lx7 | riscv-rv32 | riscv-rv32(+ A 扩展) |
| 已报告的全清单解码器校验 | 977 k 条指令,0 处不匹配 | 161 k 条指令,0 处不匹配 | 126 k 条指令,0 处不匹配 |
| 启动路径 | ROM → bootloader → FreeRTOS → 应用 | ROM → bootloader → FreeRTOS → 应用 | ROM → bootloader → FreeRTOS → 应用 |
| 开发板 / 显示屏 | ST7735、ST7701S 480×480 + 触摸、WS2812、摄像头、音频 | 无——仅控制台 | 走 SPI+DMA 的 ST7789 172×320、WS2812、一个 802.15.4 能量检测替代实现 |
| WiFi | 虚拟 AP、WPA2、DHCP/DNS/NTP、NAT 到真实网络、HTTPS | 同一个虚拟 AP 和网络:扫描、WPA2、DHCP、NAT(一个 station) | 同一个虚拟 AP 和网络:扫描、WPA2、DHCP、NAT(一个 station,传统速率) |
| 速度 | 块解释器 + AArch64 JIT,约 150–240 Minsn/s | 普通解释器(尚无块缓存或 JIT)——在 hello_world 上仍远高于实时 | 同一个解释器 |
| 浏览器内运行 | 支持(WebAssembly) | 支持(WebAssembly) | 支持(WebAssembly) |
| 已报告的硬件对比 | JTAG trace,时序循环重同步后 8000 步 | 控制台 diff,208 行中 205 行一致 | 控制台 diff,204 行中 203 行一致 |
| 状态 | 成熟 | 草稿——见 docs/esp32c3.md | 草稿——见 docs/esp32c6.md |
这些全清单和硬件计数来自早期的手动运行。CI 运行的是签入的采样解码器语料库和模拟器 golden 输出;它不包含外部的全清单测试。Xtensa objdump 测试排除了 PIE 操作数比较。硬件 trace 对比会规范化窗口异常并对延时循环重新同步,因此它并非逐周期不间断的一致。参见 测试范围、CI workflow、Xtensa 解码器测试 和 硬件对比器。
SoC 的大部分是共享的:C3 原封不动地复用了 S3 的 UART、USB-Serial/JTAG、systimer、定时器组、GPIO、SPI flash 控制器、GDMA 以及 SHA/AES/RSA 模型,并加上自己的内存映射、缓存控制器和中断矩阵;C6 再次复用这些模型,并配有自己内存映射、PLIC 前端、L1 缓存、PCR 和常开的 LP 模块。只有 CPU crate 是真正独立的。
在浏览器中试用,无需安装:
—— S3 上的 Linux、带 SID 播放器的 Touch-LCD-4B 面板、pocket-tank LLM 水族箱,以及 C3 和 C6 的演示。
esp32sim/
emu-core/ Bus 与 Core trait、Trap、ClockTree、AArch64 编码器——两个核心共享
esp-soc/ Machine:调度器、设备时间、控制台、脚本、Web UI、加载器、开发板——每颗芯片共用一台机器;芯片插入自己的核心、总线和中断路由;虚拟 WiFi 接入点、其背后的网络与 NAT,适用于每颗芯片
esp-periph/ 芯片共享的外设 IP(UART、systimer、TIMG、GPIO、SPI_MEM、GDMA、加密、I2S、RMT、I2C、...)、Device trait,以及挂载它们的 device_set! 表
── ESP32-S3(Xtensa LX7,双核)──
xtensa-lx7/ 解码器(采样的 objdump 校验加可选的全清单)、解释器(窗口寄存器、循环、XEA2 异常/中断、FPU、MAC16、布尔、PIE SIMD)、基本块解释器、AArch64 JIT、兼容 objdump 的反汇编器
esp32s3/ SoC + 开发板:内存映射、缓存 MMU、SPI flash/PSRAM、SHA/AES/RSA、RNG、systimer、定时器组、中断矩阵(每核)、GPIO、USB-CDC、UART、I2C、GDMA + I2S/LCD_CAM、RMT TX、regi2c、RTC WDT、WiFi MAC;board/: atech14 / waveshare-cam / waveshare-lcd4b / waveshare-amoled18-v2 / none
cli/ `esp32sim` 命令行,支持每颗芯片(`--chip`);`esp32sim-c3` / `esp32sim-c6` 是别名二进制
── ESP32-C3 与 ESP32-C6(RISC-V,单核)──
riscv-rv32/ RV32IMAC 解码器(采样的 objdump 校验)、解释器、反汇编器
esp32c3/ C3 SoC:内存映射、中断矩阵、缓存控制器、RNG;IP 相同处与 esp32s3 共享外设模型
esp32c6/ C6 SoC:统一内存映射与 MMU、中断矩阵 + PLIC/INTPRI、L1 缓存、PCR、LP 模块(复位原因、软件复位、RTC 定时器)、ASSIST_DEBUG、802.15.4 与 WiFi MAC
tests/ golden 输出回归测试及其 fixtures(tests/README.md)
── 共享 ──
wasm/ C-ABI crate,把三台 Machine 封装进浏览器的一个模块
web/ 浏览器 UI(开发板绘图、控制台、音频、摄像头)+ emu.js/worker.js(配合 wasm)
hw/ 针对真实开发板的 JTAG 差分测试脚本,采集的 C3 与 C6 控制台
examples/ hello_world(IDF、S3、C3 与 C6)、waveshare-cam(autopling 运行脚本 + 照片)、wifi-station(S3)、c6-wifi-station(为 S3、C3 和 C6 构建;WiFi golden 所用的固件)、c6-radio-dashboard(C6 开发板上的 WiFi + 802.15.4)
tools/ PIE 表生成器(源自 TRM);bench.py(交错 A/B 基准测试);wasm-build.sh(WebAssembly 模块)
开发板(ESP32-S3)
SoC 模型会发出引脚级事件(GPIO 边沿、RMT 符号流、I2S 采样);由 BoardModel(esp-soc/src/board.rs)来解释它们。
--board atech14(默认)是 Atech 14 口开发板,带 ST7735、WS2812 灯环、编码器和按键;
--board none 是裸模块——任何 ESP32-S3 固件,仅控制台;
--board waveshare-lcd4b 是 Waveshare ESP32-S3-Touch-LCD-4B(通过 LCD_CAM RGB 总线的 ST7701S 480×480、GT911 触摸、TCA9554、编解码器),运行 esp32-screen LVGL 面板并支持触摸;
--board waveshare-amoled18-v2 是 Waveshare ESP32-S3-Touch-AMOLED-1.8 V2(QSPI 的 CO5300 368x448、CST820 触摸);
--board waveshare-cam 是 Waveshare ESP32-S3-CAM-OV5640(CH32V003 IO 扩展器、SCCB 上的 OV5640、I2C0 上的 ES8311/ES7210 编解码器、I2S1 上的扬声器、LCD_CAM DVP 端口上的 OV5640)——端到端运行 waveshare-autopling 固件(IDF 5.5,16 MB flash,8 MB 八线 PSRAM:--flash-mb 16 --psram-mb 8):来自 --cam-image 或浏览器(图片上传 / 摄像头)的摄像头帧 → 模拟 PIE SIMD 单元上的 esp-dl 行人检测 → 在 ES8311 上生成 pling。参见 examples/waveshare-cam/。
新增一块开发板 = 实现该 trait(gpio_changes、rmt_frame、i2c_devices、camera_frame 等)。
atech14 演示所运行的 Pocket Synth 固件是另一个项目:atech-firmware(https://github.com/joakimeriksson/atech-firmware)——一个 SID 合成器和 cRSID 播放器的 PlatformIO 构建,用于播放真实的 .sid 曲目。本仓库在其生成的产物中携带这些已构建镜像,位于 web/wasm/fw/public/。
运行 — ESP32-S3
cargo build --release
FW=web/wasm/fw/public # golden 和网页所运行的演示镜像
./target/release/esp32sim --boot rom \
--bootloader $FW/atech-bootloader.bin --ptable $FW/atech-ptable.bin --app $FW/atech-firmware.bin \
--script $FW/atech-script1.txt --wav out.wav --tft-png tft.png --max-seconds 5
mask ROM ELF 会从 ~/.espressif/tools/esp-rom-elfs/*/esp32s3_rev0_rom.elf(随 ESP-IDF 提供)自动选取。若没有 ESP-IDF,tools/fetch-demo-assets.sh --no-linux 会把 S3、C3 和 C6 的 ROM 放进 web/wasm/fw/:传入 --rom web/wasm/fw/esp32s3_rev0_rom.elf,或为测试设置 ESP32SIM_ROM_DIR=web/wasm/fw。
--boot app 会跳过 ROM + bootloader,直接加载应用镜像。
一个普通的 ESP-IDF 项目,例如 examples/hello_world(用 idf.py set-target esp32s3 && idf.py build 构建的 IDF 5.5 get-started 示例):
H=examples/hello_world/build
./target/release/esp32sim --board none --boot rom --console uart0 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --elf $H/bootloader/bootloader.elf --max-seconds 26
会在 UART0 上打印 ROM 横幅、bootloader 和应用日志、Hello world!、倒计时,然后如同真实芯片一样经 RTC 看门狗重启(rst:0xc (RTC_SW_CPU_RST)),速度约为实时的 30 倍。
芯片复位(软件复位、RTC 看门狗)会带上正确的复位原因从 ROM 重新启动机器;--no-reboot 则在第一次复位时停下。
运行 — ESP32-C3
esp32sim-c3 在模拟的 RISC-V 核心上运行未经修改的 ESP-IDF 固件,从真实的 mask ROM 经由真实的 bootloader 进入 FreeRTOS。
与 esp32sim 相同的参数(--trace、--break、--watch、--peek、--disasm、--log-periph),并打印带符号的 RISC-V 助记符。
H=examples/hello_world-c3/build
./target/release/esp32sim-c3 --boot rom --flash-mb 4 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --max-seconds 26
会打印 ROM 横幅、bootloader 日志、Hello world! 和重启,共重复三次。
与一块物理 C3 模块逐行核对:三个启动周期中 208 行控制台输出有 205 行一致——差异在于 ROM 的 Saved PC: 行。--mac、--reset-cause 和 --strap 让一次运行可以采用某块开发板的身份,从而使对比有意义。
WiFi station 模式支持开放与 WPA2 虚拟 AP,带 DHCP 和 NAT。没有开发板;不支持 --boot app。参见 docs/esp32c3.md,了解哪些可用、哪些不可用,以及硬件发现的五个模拟器 bug。
运行 — ESP32-C6
esp32sim-c6 是同一个前端在 ESP32-C6 上的版本:RV32IMAC、PLIC、统一内存映射。
H=examples/hello_world-c6/build
./target/release/esp32sim-c6 --boot rom --flash-mb 4 \
--bootloader $H/bootloader/bootloader.bin --ptable $H/partition_table/partition-table.bin \
--app $H/hello_world.bin --elf $H/hello_world.elf --max-seconds 27
与一块 Waveshare ESP32-C6-LCD-1.47 核对:三个启动周期中 204 行控制台输出有 203 行一致,包括 esp_restart() 之后 ROM 的 Saved PC:;唯一的差异是首次启动时的那一行。
--board waveshare-c6-lcd147 会加上这块开发板——走 SPI2 和 GDMA 的 ST7789、WS2812、BOOT 按键——并且 C6 的 802.15.4 MAC 会响应能量扫描,因此该板的 LVGL 频谱扫描器固件得以端到端运行(examples/waveshare-c6-lcd147/)。该 MAC 还按空口时序收发帧,--cooja 让 C6 成为 Cooja-NG 的外部 mote,通过 NDJSON 以精确同步锁步驱动(一个未经修改的 Contiki-NG-on-IDF 镜像与模拟的 MSP430 节点交换广播)。
WiFi 也能用:--wifi 会挂载与 S3 相同的虚拟接入点(参见下文 WiFi)。
参见 docs/esp32c6.md。
脚本(在模拟时间执行的宿主动作)
1.5 press btn1 150 # GPIO17 拉低 150 ms
2.2 knob cw 2 # CLK5/DT4 上转过两个正交档位
3.4 press btn2 120
4.0 serial {"action":"set_note","value":"9"} # 写入 USB-CDC RX
5.0 stop
WiFi(ESP32-S3、C3、C6 与经典 ESP32)
--wifi ssid=NAME[,psk=PASS,chan=N,bssid=..] 会挂载一个虚拟接入点,未经修改的乐鑫 WiFi blob 会与之关联——扫描、认证、关联,以及有口令时的 WPA2-PSK 四次握手——以及其背后的一个虚拟网络(DHCP、ARP、ICMP、DNS、基于宿主时钟的 SNTP)。
--net nat(默认)通过普通宿主 socket 中继客户机的 TCP 和 UDP,因此固件可以访问真实网络;--net none 则拒绝。无需修改固件,无需 root,无需 tun 设备。
./target/release/esp32sim --board waveshare-lcd4b --boot rom --flash-mb 16 --psram-mb 8 \
--bootloader $P/bootloader/bootloader.bin --ptable $P/partition_table/partition-table.bin \
--app $P/energy_panel.bin --console usb --wifi "ssid=home,psk=secret" --max-seconds 45
会运行 esp32-screen 能量面板:它会加入网络、获取租约、同步时钟、通过 HTTPS 获取两天的电价,并轮询局域网中一个真实的 Home Assistant。
docs/networking-howto.md 是使用指南(参数、调试、限制);docs/wifi-plan.md 和 docs/networking-plan.md 描述 MAC 模型与数据包路径的工作方式;docs/architecture.md 的 Networking 部分描述每颗芯片共享的各层。
ESP32-C6 通过自己的 MAC 模型加入同一个接入点和同一个网络(docs/wifi-c6-plan.md):一个 station、传统速率、无省电。
examples/c6-wifi-station 就是样例——扫描、加入、租约、五次 ping,以及开发板屏幕上的状态——并且每次 C6 无线运行都需要 --stub bb_init=0(PHY 的基带校准需要模拟硬件):
B=examples/c6-wifi-station/build-emu
./target/release/esp32sim-c6 --boot rom --flash-mb 4 --board waveshare-c6-lcd147 --console usb \
--bootloader $B/bootloader/bootloader.bin --ptable $B/partition_table/partition-table.bin \
--app $B/c6_wifi_station.bin --elf $B/c6_wifi_station.elf --stub bb_init=0 \
--wifi ssid=esp32sim,psk=esp32sim-pass --max-seconds 14
ESP32-C3 也加入同一个接入点,通过一个源自 S3 的 MAC 模型。同一个 station 示例为每颗芯片构建(不带显示屏),已提交在 web/wasm/fw/public/ 下,作为 WiFi golden 的输入(cli/tests/goldens.rs 中的 wifi_station_{s3,c3,c6}),因此无需本地构建即可运行:
P=web/wasm/fw/public
./target/release/esp32sim-c3 --boot rom --flash-mb 4 --console usb \
--bootloader $P/c3-wifi-bootloader.bin --ptable $P/c3-wifi-ptable.bin --app $P/c3-wifi_station.bin \
--wifi ssid=esp32sim,psk=esp32sim-pass --max-seconds 14
examples/c6-radio-dashboard 在一个固件里同时运行 WiFi 和 802.15.4 能量扫描,每样一页。
在浏览器中(WebAssembly)
tools/wasm-build.sh # 构建模块 -> web/wasm/esp32sim.wasm
tools/fetch-demo-assets.sh # mask ROM、xterm.js 和 Linux 镜像(--no-linux 会跳过其 16 MB)
python3 -m http.server -d web 8790 # 然后打开 http://127.0.0.1:8790/?wasm&fw=hello
演示固件已提交;mask ROM、xterm.js 和 Linux 镜像未提交,而每个演示都从 ROM 启动,所以请先运行一次 fetch。它获取的文件与 Pages 站点所提供的是同一套。
同一个模拟器编译为 WebAssembly,运行在页面内的 Web Worker 中:选择开发板、从磁盘加载 ROM ELF 和固件(或用 ?wasm&fw= 指向托管的 manifest),按下 Boot 即可。
hello_world、带 SID 播放器的 Touch-LCD-4B 面板、Atech 开发板、ESP32-C3 和 ESP32-C6 都能在 Chrome 中实时运行;没有 NAT(浏览器没有 socket)。S3 拥有自己的 WebAssembly JIT(热块与热区域、WASM SIMD 上的 PIE)。
参见 docs/wasm.md。
在线体验:https://mikroverk.github.io/esp32sim/
—— ESP32-S3 上的 Linux 6.11(svermigo/Linux-on-esp32-S3(https://github.com/svermigo/Linux-on-esp32-S3),登录并在控制台输入)、带 SID 播放器的 Touch-LCD-4B 面板、Atech 开发板、pocket-tank(https://github.com/mediacutlet/pocket-tank,一个 4-bit transformer 在 Touch-AMOLED-1.8 上操纵鱼儿),以及启动 hello_world 的 ESP32-C3 和 C6;也可以从磁盘为任意芯片加载你自己的固件(选择开发板,或 esp32c3 / esp32c6)。
调试
--trace [--trace-from N]、--break ADDR、--watch ADDR(某个字发生变化时停止)、--peek ADDR[,N]、--profile(pc 直方图)、--log-periph(每个未知寄存器的首次触碰)、--stop-after-exceptions N、--gram-png(原始 ST7735 GRAM)、--no-jit(改为解释执行而非运行本地代码——结果必须完全一致)。
环境变量:`ES
相似文章
@knowledgefxg: 趣站推荐:Wokwi 一个在浏览器里就能玩的电子电路模拟器,可以轻松在线搞嵌入式或者物联网开发,你可以直接在线拖拽搭电路、写代码(甚至支持 MicroPython)、点一下就能跑模拟,连 WiFi 网络行为都能模拟。不用花一分钱买硬件,就…
Wokwi 是一个在浏览器中运行的电子电路模拟器,支持 ESP32 等嵌入式系统开发,可在线拖拽搭建电路、编写代码(包括 MicroPython)并模拟 Wi-Fi 行为,无需硬件即可验证项目。
The Rust on ESP Book
《The Rust on ESP Book》是一本全面的指南,用于在 Espressif 产品上进行嵌入式 Rust 开发,涵盖项目生成、工具链和软件栈结构。
ESP32 Bit Pirate 硬件黑客套件:支持所有协议的网页工具
ESP32 Bit Pirate 是一个针对 ESP32-S3 开发板的开源硬件调试生态系统,提供固件、基于浏览器的工具和文档,用于多协议硬件黑客与开发。
Show HN: 在浏览器中编写、仿真和综合 VHDL/Verilog
RisingEdge.pro 是一个基于浏览器的平台,用于通过编写、仿真和综合 VHDL 和 SystemVerilog 代码来学习和练习硬件设计,使用真实的 EDA 工具(如 GHDL、Icarus 和 Yosys),并提供交互式波形和引导式课程。
Show HN: 运行于 WebAssembly 的 Firefox
Firefox 被编译为在 WebAssembly 中运行,采用基于 WebGL 的渲染和实验性的 JS 到 WASM JIT,网页内容通过 Puter 托管的 Wisp 服务器进行代理。