跳到內容

Technology

開源|esp32sim:用 Rust 寫的 ESP32 模擬器,從 mask ROM 開機跑沒改過的韌體,也能在瀏覽器執行

esp32sim 從晶片的 mask ROM 開始,執行真正的 bootloader 與未修改的 ESP-IDF 應用程式,連螢幕、觸控、音訊和 WiFi 都一起模擬,同一份程式碼可以編成 WebAssembly。整理它的 block 直譯器、以直譯器為 oracle 的部分 JIT、裝置表巨集、虛擬 WiFi 網路,以及和實體晶片對拍的方法。

Published: at 10:00 AM
編輯本頁下載圖卡

專案 GitHub:mikroverk/esp32sim

重點

以 2026 年 10 月 20 日從 GitHub API 查到的資料,專案在 2026 年 8 月 25 日 15

(台北時間)建立,有 445 顆星、25 個 fork、21 個開啟中的 issue,授權是 MIT,最新版本是 9 月 14 日(台北時間)發布的 v0.2.0。Hacker News 的 Show HN 拿到 205 分。作者是 RISE 的 Joakim Eriksson、Alice 和 Schematik 的 Mattia Fiumara 等人。README 特別聲明:模擬器根據 ESP-IDF 暫存器標頭檔、Xtensa 核心設定、RISC-V 規格和技術參考手冊撰寫,QEMU 只用來確認指令語意,沒有複製程式碼。

內容根據 README、docs/architecture.md、docs/hardware-testing.md、docs/cli.md,以及 xtensa-lx7/src/block.rs、xtensa-lx7/src/jit/mod.rs、emu-core/src/bus.rs、emu-core/src/jit_a64.rs 等原始碼。

它解決什麼問題

ESP32 韌體開發通常要燒進實體板子才能測。現有的模擬選項大多要改韌體或用替代驅動,例如 Espressif 自己維護的 QEMU 分支,官方文件列出 ESP32、ESP32-C3、ESP32-S3 都不支援 Wi-Fi 和藍牙;要連網得改用 OpenCores 乙太網路 MAC,這是只存在於模擬環境的周邊,韌體必須開啟 CONFIG_ETH_USE_OPENETH 並初始化這個驅動。esp32sim 的目標是讓同一個 .bin 不用改,從開機到連網都跑在電腦或瀏覽器分頁裡。

晶片CPU狀態板子
ESP32-S32 × Xtensa LX7matureAtech 14-port kit、Waveshare Touch-LCD-4B 等
ESP32-C3RISC-V RV32IMCdraft只有 console
ESP32-C6RISC-V RV32IMACdraftWaveshare ESP32-C6-LCD-1.47,含 802.15.4
ESP32(經典)2 × Xtensa LX6earlyLilyGO TTGO T-Display

示範網站上可以直接跑 ESP32-S3 上的 Linux 6.11、SID 音樂播放器、LVGL 觸控面板,以及 C6 上執行 RPL/IPv6 的 Contiki-NG 節點。

怎麼運作

CPU:block 直譯器

xtensa-lx7/ 是 S3 的 CPU 核心。decode.rs 解碼,exec.rs 是單指令直譯器。單指令執行每一步都要檢查中斷、查解碼快取、累計週期,block.rs 的註解說這些開銷量起來佔執行時間三分之一,所以平常改以 block 為單位執行:

自我修改的程式碼

guest 記憶體每 256 bytes 一頁,每頁有一個寫入版本號(VPAGE_SHIFT = 8)。block 記住自己解碼來源那(最多兩)頁的版本,版本變了就重建。MMU 重新映射 flash 或 PSRAM 時會把版本號往上加,用舊映射解出來的指令就不會再被執行。存取記憶體走軟體 TLB,共 512 項,以 64 KiB 為索引單位。

部分 JIT,直譯器當 oracle

在 AArch64 的 macOS 或 Linux 主機上,block 會編成原生機器碼(xtensa-lx7/src/jit/)。jit/mod.rs 開宗明義寫著:編好的 block 跟直譯器跑的是同一段指令,結束規則完全相同,兩者必須產生逐 bit 相同的機器狀態,直譯器就是 oracle。--no-jit 改回直譯,結果必須一模一樣,出問題時就用它來比對。

機器碼由 emu-core/src/jit_a64.rs 產生,這是一個只涵蓋 block 編譯器需要的指令的小型 AArch64 編碼器,每個編碼都用測試和 clang 的組譯器比對。瀏覽器版另有一條 WebAssembly 的 JIT(wasm-jit/、jit/wasm_*.rs)。

周邊與排程

device_set! 巨集讓每顆晶片只寫一張裝置表(區塊、名稱、欄位、中斷來源編號),就能產生暫存器分派、中斷來源掃描和各時脈域的 tick。Machine 只寫一次,所有晶片共用。多核心以 quantum 輪流執行,原生版每次 64 道指令、瀏覽器版 256 道,可以設定在 64 到 4096 之間;核心閒置時會跳過時間。

虛擬 WiFi 與網路

從手冊產生指令表

S3 的 PIE SIMD 指令(ee.*)共 217 種編碼,由 tools/gen_pie_table.py 從技術參考手冊的指令格式轉出 pie_table.rs,再跟 ESP-IDF 5.5 的組譯器交叉比對。

怎麼確認模擬得對

docs/hardware-testing.md 列出的結果:

ESP32-S3ESP32-C3ESP32-C6
方法JTAG lock-stepconsole diffconsole diff
結果(專案自述)計時迴圈重新同步後 8000 步208 行有 205 行相同204 行有 203 行相同
解碼器全清單檢查(專案自述)97.7 萬道指令,0 不符16.1 萬道,0 不符12.6 萬道,0 不符

S3 的做法是透過 JTAG 讀出實體晶片的 efuse 和 strapping 腳位,再讓晶片和模擬器在同一份 flash 內容上逐步執行並比對。文件也寫明比對時會正規化 window 例外、重新同步延遲迴圈,所以不等於逐週期一致;這些數字是之前手動跑的結果,CI 跑的是抽樣的解碼器語料和模擬器的黃金輸出。C3、C6 的比對各找到五個模擬器 bug。

跟常見做法比

Espressif 的 QEMU 分支esp32sim
語言CRust
WiFi不支援,改用 OpenCores 乙太網路(需改韌體設定)虛擬基地台,跑原版 WiFi 函式庫
板子周邊少量螢幕、觸控、音訊、相機等板子模型
瀏覽器否WebAssembly 版

專案文件把 Espressif 的 QEMU 當成行為問題的參考,但不拿它的程式碼。速度方面,README 說 S3 加上 block 快取和 JIT 後,跑 hello_world 約比實機快 30 倍(專案自述)。

限制與成熟度

參考資料

註釋

編輯本頁

Podcast

關於語音摘要

此語音摘要由 Google NotebookLM 提供,不完全代表作者本人對文章的理解。