esp32emu / docs /DEVELOPING.md
HelloSun's picture
ESP32 emulator update
a281c6e verified
|
Raw History Blame Contribute Delete
7.08 kB

ESP32 模擬器 開發 / 部署 / 修改指南 (Developer guide)

給「要部署或改程式的人(含小型 AI 模型)」:照著下面「配方」逐步做即可。所有路徑相對於專案根目錄。

0. 一分鐘總覽

瀏覽器內執行的 ESP32 (Xtensa LX6 雙核) 模擬器。主執行緒 web/app.js 負責 UI;Web Worker web/js/worker.js 跑 web/js/soc.js(SoC + 週邊)。 CPU 有兩套等價實作:WASM(wasm/xtensa.c,預設)與 JS(web/js/xtensa.js,參考/除錯用)。兩者對同一韌體執行的指令數必須逐位相同。

1. 目錄地圖

路徑 內容
web/index.html style.css app.js UI:範例選單、載入二進位表、虛擬鍵盤、序列埠、GPIO 面板、畫面 canvas
web/js/worker.js 模擬迴圈(對齊真實時間、送 frame/uart/gpio/status 給 UI),處理 UI 訊息
web/js/soc.js 記憶體對映、頁表、中斷矩陣、事件佇列、DPORT、UART、TIMG、FRC、RTC_CNTL、eFuse、ROM hook、主迴圈 run()
web/js/xtensa.js JS 版 CPU(參考實作,可下斷點/trace)
wasm/xtensa.c → wasm/xtensa.wasm WASM 版 CPU(與 xtensa.js 逐行對應)
web/js/cpu_wasm.js WASM CPU 的 JS 包裝(狀態陣列索引表 I,必須與 xtensa.c 的 enum 一致)
web/js/spi.js SPI flash 晶片、PSRAM 晶片、SPI 控制器
web/js/gpio.js GPIO 矩陣、腳位電氣模型(開集極/外部拉低/上拉)、IO_MUX
web/js/i2s.js I2S DMA 描述子鏈 + VGA 螢幕模型(VGA32_BOARD 為電阻網路接線)
web/js/ps2.js ulp.js PS/2 鍵盤/滑鼠裝置、ULP 協處理器直譯器 + RTC_IO + SENS
web/js/sha.js SHA-1/256/384/512 硬體引擎
web/assets.js 生成檔(gzip+base64 的 ROM、wasm、範例韌體),勿手改
web/rom/ ESP32 ROM 映像(esp32-v3-rom.bin, esp32-v3-rom-app.bin)
firmware/*/ 範例 Arduino 草稿碼(dosvga32=FreeDOS、vgademo、echo);firmware/dist/ 為編好的 flash
tools/ 除錯/測試腳本(見 §6)
scripts/ setup.sh build.sh deploy.sh pack_source.sh

2. 環境(第一次)

需要 Linux、python3、node>=18、curl。

scripts/setup.sh        # 安裝 zig(編 wasm)、arduino-cli+ESP32 core 2.0.17+FabGL、下載 ROM

不改韌體/wasm 時可略過,只要有 node 與 python3(+ pip install huggingface_hub pillow playwright)。

3. 建置

scripts/build.sh wasm       # 改了 wasm/xtensa.c 後
scripts/build.sh firmware   # 改了 firmware/* 草稿碼後(輸出 firmware/dist/*.flash.bin)
scripts/build.sh assets     # 任何 ROM/wasm/韌體變動後都要重新生成 web/assets.js
scripts/build.sh            # 全部

本機預覽:python3 -m http.server 8000 --directory web → 開 http://localhost:8000

4. 部署到 Hugging Face(Token 只用環境變數,絕不寫進檔案)

export HF_TOKEN=hf_xxxxxxxx            # 需 write 權限
export HF_SPACE=使用者/空間名            # 可省略,預設 HelloSun/esp32emu
scripts/deploy.sh

它會:重生 assets → 壓縮整個專案成 dist/esp32emu-source.tar.gz → 上傳網站、原始碼、文件、腳本、壓縮包(Space 路徑 downloads/esp32emu-source.tar.gz)。 注意:HF 對二進位檔的下載會 302 轉址且無 CORS,所以網站用的 ROM/wasm/韌體一律以 base64 內嵌在 assets.js,不要改成 fetch 二進位檔。

5. 修改配方(改什麼 → 改哪裡 → 怎麼驗證)

  1. 新增內建範例:把草稿碼放 firmware/<name>/<name>.ino → scripts/build.sh firmware(需在 build.sh 的 for 清單加名字)→ 在 tools/build_assets.py 的 examples 加 {'id','title','psram','flash'} → scripts/build.sh assets。
  2. 新增/修正週邊暫存器:在 web/js/soc.js 找對應類別(Uart/TimerGroup/RtcCntl/Efuse/Frc…)或新建類別,於建構子 reg(基底位址, 裝置) 註冊;裝置要有 read(off)/write(off,v)(off 為頁內位元組偏移,32 位元存取)。未實作位址讀 0。中斷用 soc.setIrq(來源編號, 0/1)(編號見 ESP-IDF soc/soc.h 的 ETS_*_SOURCE)。延遲事件用 soc.scheduleIn(奈秒, fn)。
  3. 改 CPU 指令行為:同時改 web/js/xtensa.js 與 wasm/xtensa.c(兩邊結構相同),重建 wasm,然後跑 §6 的「JS/WASM 一致性」。
  4. **ROM 函式加速/替身 (HLE)**:soc.addRomHook(位址, function(a2..a7){return 回傳值}),見 installRomHooks()(目前用於 APLL 的 rom_i2c_*)。
  5. 新增板子接線(VGA 腳位):web/js/i2s.js 的 VGA32_BOARD;PS/2 腳位在 soc.js 建構子 new Ps2Device(this, clk, dat, kind);UI 示意圖在 app.js 的 buildBoard()。
  6. UI 調整:只動 web/index.html style.css app.js;與 worker 的訊息格式見 worker.js 的 self.onmessage 與 app.js 的 worker.onmessage。
  7. 時序/速度:soc.run() 的 cpi(每指令週期,預設 1,VGA 逐行 ISR 需要 ≤1)、slice(切片指令數,預設 256)。雙核同時進中斷時 lockstep 會細切片模擬真實競爭。

6. 測試與除錯工具(node,皆在 tools/)

目的 指令
跑韌體看序列埠 node tools/runw.mjs firmware/dist/echo.flash.bin 4(加 --js 用 JS 核心、--psram)
JS/WASM 一致性 分別 node tools/runw.mjs X 5 --psram 與 ... --js,最後印的 instr 數必須相同
擷取 VGA 畫面成 ppm node tools/vgaw.mjs X 秒 間隔 [--psram] [--keys=3.5:KeyH,...] → /tmp/opencode/frame_N.ppm
例外/崩潰定位 node tools/exc.mjs X 秒 --syms=<nm 輸出>(用 xtensa-esp32-elf-nm -n elf 產生符號檔)
找函式呼叫序列 node tools/bp.mjs X 函式名 --syms=...
取樣剖析 node tools/prof.mjs X 秒 --syms=... --warm=8
瀏覽器整合測試 python3 tools/browser_test.py 秒 截圖.png(playwright;需 playwright install chromium)
效能微基準 node tools/bench.mjs
除錯技巧:用 --js 核心才能 trace/斷點(cpu.enableTrace()、cpu.bpAddr);WASM 核心看不到。

7. 常見陷阱(已踩過)

  • 改 wasm/xtensa.c 的狀態索引 enum 時,必須同步 web/js/cpu_wasm.js 的 I 表。
  • 未對齊的 L32I/S32I 必須真的支援(硬體支援),不能遮罩位址。
  • ULP 的 MOVR 取 sreg、MOVI 取立即值;ULP 觸發的 GPIO 事件時間用 soc.nowNs(已處理 ulpNow)。
  • I2S OUT_EOF_DES_ADDR 要回傳完整 32 位元位址,否則 FabGL 的畫面幀同步錯亂。
  • 雙核同時收到同一中斷時要同時進入 ISR(deliverNow),否則 FabGL 的 RTC 喚醒 ISR 會輸給 core0 的 rtc_isr。
  • WASM 記憶體固定 64MB(與 wasm 匯入記憶體一致),heap 佈局常數在 soc.js 開頭 H_*。
  • 效能上限約 80–110 MIPS/核心(單核直譯),FreeDOS 約真機 25–100%。

8. 授權

GPL-3.0-or-later(範例韌體含 FabGL)。ROM 取自 Espressif QEMU 發行版;FreeDOS 為 GPL。