Download js/worker.js from HelloSun/esp32emu: direct link, hf CLI and curl.
- Browser
- Download file 4.75 kB
-
https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/worker.js
- Command line
-
hf download hf://spaces/HelloSun/esp32emu/js/worker.js
-
curl -L -o worker.js https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/worker.js
4.75 kB
| // Emulator worker: runs the ESP32 SoC model paced against wall-clock time. | |
| import { ESP32, createESP32 } from './soc.js'; | |
| let soc = null, running = false, paused = false, turbo = false; | |
| let wallStart = 0, virtStart = 0, speed = 1.0; | |
| let uartBuf = [[], [], []]; | |
| let lastFrameWall = 0, lastStatus = 0, lastGpio = 0, framesInFlight = 0; | |
| let instrWindow = 0, instrT0 = 0, mips = 0, ratio = 0, virtT0 = 0; | |
| let roms = null, wasmMod = null; | |
| function post(msg, tr) { self.postMessage(msg, tr || []); } | |
| async function boot(cfg) { | |
| if (!roms) { post({ type: 'error', text: 'ROMs not loaded' }); return; } | |
| running = false; soc = null; | |
| const opts = { cpi: cfg.cpi || 1, slice: cfg.slice || 256 }; | |
| soc = (wasmMod && !cfg.js) ? await createESP32({ ...opts, wasmModule: wasmMod }) : new ESP32(opts); | |
| soc.loadRoms(roms.pro, roms.app); | |
| soc.setFlash(new Uint8Array(cfg.flash)); | |
| soc.enablePsram(!!cfg.psram); | |
| soc.onUart = (n, b) => { uartBuf[n].push(b); }; | |
| soc.reset('power'); | |
| wallStart = performance.now(); virtStart = soc.timeNs; running = true; paused = false; | |
| instrT0 = wallStart; instrWindow = 0; virtT0 = soc.timeNs; | |
| post({ type: 'booted', flashSize: soc.flashSize, psram: !!cfg.psram, engine: soc.engine }); | |
| if (!loopScheduled) { loopScheduled = true; setTimeout(loop, 0); } | |
| } | |
| let loopScheduled = false; | |
| function flushUart() { | |
| for (let n = 0; n < 3; n++) if (uartBuf[n].length) { post({ type: 'uart', n, data: new Uint8Array(uartBuf[n]) }); uartBuf[n] = []; } | |
| } | |
| function loop() { | |
| const t = performance.now(); | |
| if (soc && running && !paused) { | |
| // virtual-time target | |
| const target = turbo ? Infinity : virtStart + (t - wallStart) * 1e6 * speed; | |
| soc.idleLimit = turbo ? undefined : target; | |
| soc.idleBlocked = false; | |
| const budgetEnd = t + 12; | |
| let instr = 0; | |
| while (soc.timeNs < target && performance.now() < budgetEnd) { | |
| instr += soc.run(50000); | |
| if (soc.idleBlocked) break; | |
| if (soc.idleForever) { break; } | |
| } | |
| instrWindow += instr; | |
| // lag clamp: if far behind real time, resynchronize (don't try to catch up) | |
| const now = performance.now(); | |
| if (!turbo && soc.timeNs < target - 250e6) { wallStart = now; virtStart = soc.timeNs; } | |
| // outputs | |
| const mon = soc.i2s[1].monitor; | |
| if (mon.newFrame && now - lastFrameWall > 25 && framesInFlight < 2) { | |
| const f = mon.takeFrame(); | |
| if (f) { lastFrameWall = now; framesInFlight++; post({ type: 'frame', w: f.w, h: f.h, data: f.data }, [f.data.buffer]); } | |
| } | |
| flushUart(); | |
| if (now - lastStatus > 500) { | |
| const dt = (now - instrT0) / 1000; | |
| mips = instrWindow / dt / 1e6; ratio = (soc.timeNs - virtT0) / 1e9 / dt; | |
| instrWindow = 0; instrT0 = now; virtT0 = soc.timeNs; lastStatus = now; | |
| post({ type: 'status', engine: soc.engine, virtMs: soc.timeNs / 1e6, mips, ratio, cpuMHz: soc.cpuHz / 1e6, pc0: soc.cpus[0].pc >>> 0, pc1: soc.cpus[1].pc >>> 0, frames: mon.frames, idle: !!(soc.cpus[0].halted && soc.cpus[1].halted || soc.cpus[1].stalled && soc.cpus[0].halted) }); | |
| } | |
| if (now - lastGpio > 120) { | |
| lastGpio = now; | |
| const lv = new Uint8Array(40), drv = new Int8Array(40); | |
| for (let i = 0; i < 40; i++) { lv[i] = soc.gpio.level(i); drv[i] = soc.gpio.driving(i); } | |
| post({ type: 'gpio', lv, drv }); | |
| } | |
| } | |
| setTimeout(loop, (soc && running && !paused && !turbo && soc.idleBlocked) ? 4 : 0); | |
| } | |
| self.onmessage = (e) => { | |
| const m = e.data; | |
| switch (m.type) { | |
| case 'roms': | |
| roms = { pro: new Uint8Array(m.pro), app: new Uint8Array(m.app) }; | |
| (async () => { try { if (m.wasm) wasmMod = await WebAssembly.compile(m.wasm); } catch (err) { wasmMod = null; post({ type: 'error', text: 'wasm 不可用,改用 JS 核心: ' + err.message }); } post({ type: 'ready' }); })(); | |
| break; | |
| case 'boot': framesInFlight = 0; boot(m); break; | |
| case 'frameAck': if (framesInFlight > 0) framesInFlight--; break; | |
| case 'key': if (soc) soc.ps2.kbd.key(m.code, m.down); break; | |
| case 'mouse': if (soc) soc.ps2.mouse.mouse(m.dx, m.dy, m.buttons); break; | |
| case 'uartRx': if (soc) soc.uart[m.n || 0].rxPush(m.data); break; | |
| case 'pause': paused = !!m.value; if (!paused && soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; | |
| case 'turbo': turbo = !!m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; | |
| case 'speed': speed = m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break; | |
| case 'getFlash': if (soc) { const f = soc.getFlash().slice(); post({ type: 'flash', data: f.buffer }, [f.buffer]); } break; | |
| case 'reset': if (soc) { soc.requestReset('power'); } break; | |
| case 'gpioIn': if (soc) soc.gpio.setExt(m.pin, !m.level); break; | |
| } | |
| }; | |