stm32f4-emu 1.0.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -30
- package/cli.mjs +11 -6
- package/index.d.ts +55 -0
- package/index.mjs +7 -11
- package/mcp/server.mjs +23 -0
- package/package.json +35 -14
- package/site/app.js +708 -47
- package/site/boards.js +337 -0
- package/site/components.js +37 -0
- package/site/console.html +1098 -0
- package/site/emulator.d.ts +90 -0
- package/site/emulator.js +1000 -846
- package/site/firmware.js +189 -9
- package/site/index.html +142 -692
- package/site/netsim.js +579 -23
- package/site/remote-emu.js +50 -3
- package/site/stm32f4.js +320 -0
- package/site/vendor/stm32_periph_wasm.d.ts +1000 -19
- package/site/vendor/stm32_periph_wasm.js +2158 -325
- package/site/vendor/stm32_periph_wasm_bg.wasm +0 -0
- package/site/vendor/stm32_periph_wasm_bg.wasm.d.ts +177 -16
- package/site/vendor/stm32f401.svd +24876 -0
- package/site/vendor/stm32f411.svd +24914 -0
- package/site/vendor/stm32f429.svd +62106 -0
- package/site/ws-bridge.mjs +161 -14
- package/tools/make_firmware.mjs +260 -0
- package/site/vendor/unicorn_arm.cjs +0 -0
- package/site/vendor/unicorn_arm.js +0 -0
package/README.md
CHANGED
|
@@ -6,15 +6,29 @@
|
|
|
6
6
|
[](https://github.com/danish9661/stm32F4-emulator/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/danish9661/stm32F4-emulator/actions/workflows/pages.yml)
|
|
8
8
|
|
|
9
|
+
> **Status: emulation complete.** All 41 peripheral modules are Detailed
|
|
10
|
+
> (0 Partial), all five board maps verify green (37/37/72/73/59 presets),
|
|
11
|
+
> and the Ethernet descriptor layer (EDFE, TCH/TER + RCH/RER walks, RDES4,
|
|
12
|
+
> backoff probe) plus the Wireshark-validated pcap capture (`a1b2c3d4`,
|
|
13
|
+
> tcpdump-verified DHCP→TCP→HTTP) are in. What remains is product work,
|
|
14
|
+
> not emulation: npm publish, https gateway endpoint, VS Code packaging
|
|
15
|
+
> (see [docs/progress-and-future.md](docs/progress-and-future.md)).
|
|
16
|
+
|
|
9
17
|
An STM32F407 microcontroller emulator that runs real Cortex-M4 firmware. It
|
|
10
|
-
combines a **
|
|
18
|
+
combines a **Rust CPU core** (a WASM-native Thumb-2 interpreter with exact
|
|
19
|
+
Cortex-M exception entry/return, including the VFPv4-SP FPU) with a
|
|
11
20
|
**Rust peripheral model** (RCC, USART, GPIO, DMA, ETH, TIM, NVIC, ...) also
|
|
12
21
|
compiled to WASM — so the whole machine runs headless in **Node.js or a
|
|
13
22
|
browser tab**, with no SDL, no native deps, no hardware.
|
|
14
23
|
|
|
15
24
|
It ships three real networking firmwares (`eth_http`, `eth_dhcp`, `eth_test`)
|
|
16
25
|
that do DHCP + TCP + HTTP against a simulated (or a real gVisor-backed)
|
|
17
|
-
network, a
|
|
26
|
+
network, a 66-marker Ethernet feature matrix (`eth_feat_test`: PHY/MDIO,
|
|
27
|
+
checksum offload, hash/perfect/SA/DA filtering, VLAN, PTP, WOL, wire
|
|
28
|
+
pacing, deferral/collisions, pause frames, MMC, RBUS — see
|
|
29
|
+
[NETWORKING.md](NETWORKING.md)) plus real LwIP 2.2.1 (`lwip_demo`) and
|
|
30
|
+
MII/RMII pin mirrors (`eth_pins_test`), a browser demo, and a publishable
|
|
31
|
+
npm package.
|
|
18
32
|
|
|
19
33
|
## Live demo
|
|
20
34
|
|
|
@@ -23,7 +37,7 @@ The browser demo deploys to GitHub Pages:
|
|
|
23
37
|
**https://danish9661.github.io/stm32F4-emulator/**
|
|
24
38
|
|
|
25
39
|
A single console page that starts **idle** — nothing runs until you pick a
|
|
26
|
-
firmware: a preset dropdown with
|
|
40
|
+
firmware: a preset dropdown with 223 bundled binaries (network demos, a
|
|
27
41
|
bare-metal LED blinker, peripheral/crypto/UART/SPI test binaries), custom
|
|
28
42
|
firmware upload (`.bin`, Intel `.hex`, `.elf` — with loadable RAM segments
|
|
29
43
|
and symbols — plus `.map` for a symbol table), Run/Stop/Reset, a **gateway
|
|
@@ -32,18 +46,18 @@ scripted network (netsim) as fallback, a live UART terminal (with **UART
|
|
|
32
46
|
RX input** — type into the console and the firmware reads it; newline
|
|
33
47
|
characters excluded per HTML spec, see AGENTS.md §11), GPIO pin readout
|
|
34
48
|
for banks A–E, and key peripheral registers. Interrupt-driven firmware
|
|
35
|
-
(`rx_interrupt_test`, `rx_crypto_test`)
|
|
36
|
-
|
|
37
|
-
preset can auto-boot via the URL:
|
|
49
|
+
(`rx_interrupt_test`, `rx_crypto_test`) gets inline guest-IRQ delivery;
|
|
50
|
+
polling firmware (the ETH demos) never uses it. For automation, a
|
|
51
|
+
preset can auto-boot via the URL: `console.html?fw=eth_http`, `console.html?fw=blinky`, `console.html?fw=crypto_test`, …
|
|
38
52
|
|
|
39
53
|
## DOOM (in the browser)
|
|
40
54
|
|
|
41
55
|
**[`site/doom.html`](site/doom.html)** runs DOOM 1 shareware
|
|
42
|
-
(doomgeneric, ported to the emulated F407) at
|
|
43
|
-
headless-Chrome-verified browser page — playable
|
|
44
|
-
35 fps
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
(doomgeneric, ported to the emulated F407) at the full 35 fps in a
|
|
57
|
+
headless-Chrome-verified browser page — playable: at ~1M guest instructions
|
|
58
|
+
per rendered frame, 35 fps needs ~32 MIPS and the Rust core delivers ~65
|
|
59
|
+
(details and the measurements in AGENTS.md §16/§22). Because the guest mixes
|
|
60
|
+
one frame of audio
|
|
47
61
|
per rendered frame, sound below 35 fps plays slightly slow and pitched-down
|
|
48
62
|
rather than breaking up — the worklet rate-matches instead of inserting
|
|
49
63
|
gaps, and the stats line reports it (`audio 0.72x`). The page: 320×200
|
|
@@ -59,7 +73,7 @@ reloads.
|
|
|
59
73
|
- Controls: move W/S/A/D + arrows · strafe Shift · fire Ctrl · use
|
|
60
74
|
Space · menu Enter/Esc · save F2/F6 · load F3/F9 · F1/F10/F11/F12.
|
|
61
75
|
- Boot → menu → gameplay verified end-to-end by
|
|
62
|
-
`node site/
|
|
76
|
+
`node site/test_doom_wasm.mjs` (boot markers, menu navigation to E1M1,
|
|
63
77
|
palette + framebuffer, W-move + turn, audio, **save → `SAVE ok slot=0`
|
|
64
78
|
→ load handshake**).
|
|
65
79
|
- `site/doom1.wad` is the 4.2 MB shareware WAD; the firmware never reads
|
|
@@ -77,7 +91,7 @@ npm test # == node site/test_flow.mjs
|
|
|
77
91
|
|
|
78
92
|
# websocket bridge: headless Node serves the emulator, browser is a thin UI
|
|
79
93
|
npm run bridge -- blinky/blinky.bin --port 8234
|
|
80
|
-
# then open http://127.0.0.1:8123?bridge=ws://127.0.0.1:8234
|
|
94
|
+
# then open http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234
|
|
81
95
|
|
|
82
96
|
# gateway-backed run: firmware talks to a REAL network stack (gVisor)
|
|
83
97
|
cd stm32-periph-wasm/pkg
|
|
@@ -144,14 +158,14 @@ is a thin UI. Zero impact on the existing local WASM path:
|
|
|
144
158
|
node site/ws-bridge.mjs eth_http/eth_http.bin --port 8234
|
|
145
159
|
|
|
146
160
|
# 2. Open the browser console with the bridge URL param
|
|
147
|
-
open "http://127.0.0.1:8123
|
|
161
|
+
open "http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234"
|
|
148
162
|
```
|
|
149
163
|
|
|
150
164
|
The `RemoteEmu` adapter (`site/remote-emu.js`) is a drop-in replacement
|
|
151
165
|
for the local `emu` object — same `step()`/`drainUart()`/`read32()` API,
|
|
152
166
|
all proxied over binary WebSocket. Device stubs (OLED/TFT/etc.) run in
|
|
153
167
|
Node and are not visible to browser JS. Without `?fw=`, the page boots
|
|
154
|
-
whatever firmware the bridge was started with; with
|
|
168
|
+
whatever firmware the bridge was started with; with `console.html?fw=blinky&bridge=ws://…`,
|
|
155
169
|
the browser sends the firmware image over the bridge.
|
|
156
170
|
|
|
157
171
|
See AGENTS.md §20 for the full binary protocol reference.
|
|
@@ -163,8 +177,11 @@ See AGENTS.md §20 for the full binary protocol reference.
|
|
|
163
177
|
| `eth_http/` | DHCP + TCP client + HTTP GET + prints the response | `TCP connected`, `=== HTTP <len>b ===` |
|
|
164
178
|
| `eth_dhcp/` | Loops DHCP Discover/Offer/Request/Ack | `DHCP SUCCESS` |
|
|
165
179
|
| `eth_test/` | Raw ETH TX/RX self-test | `ETH Test: done` |
|
|
180
|
+
| `eth_feat_test/` | 66-marker Ethernet feature matrix (netsim, F407 + F429) | `FEAT Test: done` (all 66 markers, `FAIL`/`TIMEOUT` anti-markers) |
|
|
181
|
+
| `lwip_demo/` | Real LwIP 2.2.1 (DHCP→DNS→TCP echo→TCP server→UDP echo) | `LWIP DEMO DONE` |
|
|
182
|
+
| `eth_pins_test/` | MII/RMII pin-level mirrors (TX_EN/CRS_DV/RXD/COL/MDIO/MDC) | `PINS ALL PASS` |
|
|
166
183
|
| `blinky/` | **No ethernet** — LED blinker on GPIOA PA5 + UART tick counter | `tick N LED=ON/OFF` |
|
|
167
|
-
| `doom/` | **DOOM 1 shareware** (doomgeneric F407 port, browser page `site/doom.html`) | `node site/
|
|
184
|
+
| `doom/` | **DOOM 1 shareware** (doomgeneric F407 port, browser page `site/doom.html`) | `node site/test_doom_wasm.mjs` (boot + menu + gameplay + save/load) |
|
|
168
185
|
|
|
169
186
|
Plus 17 more test binaries (`crypto_test`, `hal_test`, `timer_test`,
|
|
170
187
|
`periph_test`, `echo_test`, `blink_serial`, `rx_interrupt_test`,
|
|
@@ -182,9 +199,9 @@ watchdog timeouts close to spec) will diverge — see docs/progress-and-future.m
|
|
|
182
199
|
## Architecture
|
|
183
200
|
|
|
184
201
|
```
|
|
185
|
-
firmware .bin ──►
|
|
202
|
+
firmware .bin ──► Rust WASM CPU (Thumb-2 + NVIC/exceptions)
|
|
186
203
|
│
|
|
187
|
-
|
|
204
|
+
peripheral MMIO ──► Rust peripheral model (WASM)
|
|
188
205
|
RCC USART GPIO DMA ETH TIM NVIC
|
|
189
206
|
│
|
|
190
207
|
UART out / ETH TX frames ──► driver (cli.mjs / site/emulator.js)
|
|
@@ -192,14 +209,18 @@ firmware .bin ──► Unicorn WASM CPU ──► memory hooks
|
|
|
192
209
|
RX frames injected (netsim, or real gVisor gateway)
|
|
193
210
|
```
|
|
194
211
|
|
|
195
|
-
- **CPU**:
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
- **CPU**: a pure-Rust Cortex-M4 Thumb-2 interpreter compiled to WASM;
|
|
213
|
+
peripheral accesses call straight into the Rust model, and guest IRQs
|
|
214
|
+
(SysTick, ETH, USART, SVC/PendSV) are delivered inline with exact
|
|
215
|
+
exception stacking — no native deps, no JIT, no hooks.
|
|
198
216
|
- **Peripherals**: a `wasm-bindgen` crate (`stm32-periph-wasm/`); registers
|
|
199
217
|
and bit fields come from the vendor SVD (`monox/stm32f407.svd`).
|
|
200
|
-
- **Ethernet**: TX is captured from the DMA descriptors
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
- **Ethernet**: TX is captured from the DMA descriptors (OWN/FS/LS poll
|
|
219
|
+
demand); RX frames are delivered head-only with FS+LS status, IPHCE/PCE
|
|
220
|
+
checksum status, and PTP snapshots for event messages. The full MAC
|
|
221
|
+
register map is cross-checked against CMSIS `stm32f407xx.h` (DMASR/
|
|
222
|
+
DMAIER/MACFFR/MACPMTCTL positions — see [NETWORKING.md](NETWORKING.md)
|
|
223
|
+
§6 for the audit). Optionally, a Go gateway
|
|
203
224
|
(`openhw-local-gateway/`) with a gVisor network stack makes the firmware
|
|
204
225
|
talk to a real network: `node cli.mjs <fw.bin> <inst> --gateway`.
|
|
205
226
|
- **Browser build** (`site/`): same modules as ESM; `site/emulator.js` is an
|
|
@@ -223,8 +244,8 @@ firmware .bin ──► Unicorn WASM CPU ──► memory hooks
|
|
|
223
244
|
│ ├── test_blinky.mjs Node blinky GPIO test (npm test)
|
|
224
245
|
│ ├── test_rx_interrupt.mjs Node UART-interrupt test (npm test)
|
|
225
246
|
│ ├── test_component_*.mjs Component-API tests, one firmware each (npm test)
|
|
226
|
-
│ ├──
|
|
227
|
-
│ └── vendor/ Browser WASM build, SVD
|
|
247
|
+
│ ├── test_doom_wasm.mjs Node DOOM boot/menu/gameplay/save test
|
|
248
|
+
│ └── vendor/ Browser WASM build (CPU + peripherals), SVD
|
|
228
249
|
├── index.mjs, package.json npm package entry (stm32f4-emu)
|
|
229
250
|
├── mcp/ MCP server (drive the emulator from an AI agent)
|
|
230
251
|
├── .github/workflows/ CI (Linux/Windows/macOS test matrix) + Pages deploy
|
|
@@ -257,7 +278,10 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
257
278
|
|
|
258
279
|
## Testing
|
|
259
280
|
|
|
260
|
-
- `npm test` — flow test (`site/test_flow.mjs`) +
|
|
281
|
+
- `npm test` — flow test (`site/test_flow.mjs`) + Ethernet mock harnesses
|
|
282
|
+
(`site/test_eth_mock_model.mjs`: 18 checks, fake bindings + real driver;
|
|
283
|
+
`site/test_eth_mock_consumer.mjs`: 44 checks, fake firmware + real model)
|
|
284
|
+
+ blinky test
|
|
261
285
|
(`site/test_blinky.mjs`) + interrupt-UART test (`site/test_rx_interrupt.mjs`)
|
|
262
286
|
+ component-API tests (`site/test_component_{led,button,pwm,i2cregfile}.mjs`,
|
|
263
287
|
each against real firmware — LED/blinky, Button/exti_test, Pwm/buzzer_test,
|
|
@@ -267,7 +291,7 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
267
291
|
process (see docs/components.md). The same suite runs in CI on
|
|
268
292
|
`ubuntu-latest`/`windows-latest`/`macos-latest`
|
|
269
293
|
([.github/workflows/ci.yml](.github/workflows/ci.yml)) on every push.
|
|
270
|
-
- `node site/
|
|
294
|
+
- `node site/test_doom_wasm.mjs` — DOOM boot → menu → E1M1 gameplay + save/load
|
|
271
295
|
(see the DOOM section above).
|
|
272
296
|
- `scripts/verify_ethernet.sh [max_inst]` — runs all three firmwares through
|
|
273
297
|
the gateway, asserts the success markers and 0 `TCP fail`.
|
|
@@ -279,7 +303,7 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
279
303
|
- [site/about.html](site/about.html) — in-repo About page: what it is, architecture, featured firmwares, and how to use it (CLI / browser / Node API / MCP).
|
|
280
304
|
- [docs/architecture.md](docs/architecture.md) — how the emulator is put
|
|
281
305
|
together (CPU, peripheral model, drivers, ETH flow, interrupts).
|
|
282
|
-
- [docs/peripherals.md](docs/peripherals.md) — all
|
|
306
|
+
- [docs/peripherals.md](docs/peripherals.md) — all 41 peripherals and the
|
|
283
307
|
level each is implemented to, plus external devices and known gaps.
|
|
284
308
|
- [docs/usage.md](docs/usage.md) — CLI, browser, and npm-library usage,
|
|
285
309
|
config files, env vars, building.
|
|
@@ -296,6 +320,5 @@ rebuild — delete it so the vendor assets stay tracked/committed.
|
|
|
296
320
|
## License & Credits
|
|
297
321
|
|
|
298
322
|
- **License**: GPL-3.0-only. See [LICENSE](LICENSE).
|
|
299
|
-
- **Unicorn CPU Core**: Powered by [Unicorn.js](https://github.com/AlexAltea/unicorn.js) by [Alex Altea](https://github.com/AlexAltea) (WASM/JS port of the [Unicorn Engine](https://www.unicorn-engine.org/) CPU emulator, derived from QEMU, licensed under GPLv2).
|
|
300
323
|
- **Heritage**: Fork and continuation of [nviennot/stm32-emulator](https://github.com/nviennot/stm32-emulator) (native SDL 3D printer emulator by Nicolas Viennot). The headless WASM peripheral model, networking stack, browser demo, virtual components API, MCP server, and npm package are new work built on that base.
|
|
301
324
|
- **DOOM**: Ported using [doomgeneric](https://github.com/ozkl/doomgeneric) by Ozkan Sezgin.
|
package/cli.mjs
CHANGED
|
@@ -10,15 +10,12 @@
|
|
|
10
10
|
// emulator advances the virtual RTC until a wakeup source (e.g. an RTC alarm)
|
|
11
11
|
// fires — so firmware that enters STOP (e.g. deep_sleep_demo.bin) runs.
|
|
12
12
|
import { readFileSync } from 'node:fs';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
13
|
import { fileURLToPath } from 'node:url';
|
|
15
14
|
import { dirname, resolve } from 'node:path';
|
|
16
15
|
import * as bindings from './site/vendor/stm32_periph_wasm.js';
|
|
17
16
|
import { createEmulator } from './site/emulator.js';
|
|
18
17
|
import { parseIntelHex, parseElf } from './site/loaders.js';
|
|
19
18
|
|
|
20
|
-
const require = createRequire(import.meta.url);
|
|
21
|
-
const unicornFactory = require('./site/vendor/unicorn_arm.cjs');
|
|
22
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
20
|
const svdXml = readFileSync(resolve(__dirname, 'site/vendor/stm32f407.svd'), 'utf8');
|
|
24
21
|
const wasmBytes = new Uint8Array(readFileSync(resolve(__dirname, 'site/vendor/stm32_periph_wasm_bg.wasm')));
|
|
@@ -35,7 +32,7 @@ Arguments:
|
|
|
35
32
|
Options:
|
|
36
33
|
-n, --inst <N> instruction budget to run (default 20000000)
|
|
37
34
|
-f, --format <fmt> firmware format: auto|bin|hex|elf (default auto)
|
|
38
|
-
-v, --verbose trace
|
|
35
|
+
-v, --verbose trace guest PCs to stderr (capped per step)
|
|
39
36
|
-l, --lowpower halt on WFI/WFE and advance the virtual RTC until wakeup
|
|
40
37
|
-h, --help show this help
|
|
41
38
|
-V, --version show version
|
|
@@ -119,8 +116,8 @@ async function main() {
|
|
|
119
116
|
let emu;
|
|
120
117
|
try {
|
|
121
118
|
emu = await createEmulator({
|
|
122
|
-
firmware, bindings,
|
|
123
|
-
extra_mem,
|
|
119
|
+
firmware, bindings, svdXml, wasmInit: wasmBytes,
|
|
120
|
+
extra_mem, lowpower,
|
|
124
121
|
});
|
|
125
122
|
} catch (e) {
|
|
126
123
|
fail(`emulator failed to load firmware: ${e.message}`);
|
|
@@ -132,7 +129,15 @@ async function main() {
|
|
|
132
129
|
try {
|
|
133
130
|
while (remaining > 0) {
|
|
134
131
|
const take = Math.min(STEP, remaining);
|
|
132
|
+
if (verbose) { try { emu.traceStart(); } catch {} }
|
|
135
133
|
const r = emu.step(take);
|
|
134
|
+
if (verbose) {
|
|
135
|
+
try {
|
|
136
|
+
const pcs = emu.takeTrace() || [];
|
|
137
|
+
const tail = pcs.slice(-8).map((p) => `0x${(p >>> 0).toString(16)}`).join(' ');
|
|
138
|
+
if (tail) process.stderr.write(`[trace]${tail}\n`);
|
|
139
|
+
} catch {} finally { try { emu.traceStop(); } catch {} }
|
|
140
|
+
}
|
|
136
141
|
remaining -= (r.instCount - lastInst);
|
|
137
142
|
lastInst = r.instCount;
|
|
138
143
|
const u = emu.drainUart();
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
import { createEmulator, CreateEmulatorOpts, EmulatorHandle } from './site/emulator.js';
|
|
4
|
+
|
|
5
|
+
export * from './site/emulator.js';
|
|
6
|
+
|
|
7
|
+
export interface CreateSTM32F407Opts extends CreateEmulatorOpts {
|
|
8
|
+
/** A Uint8Array, or a key into the bundled FIRMWARES table. */
|
|
9
|
+
firmware: Uint8Array | string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createSTM32F407(opts?: CreateSTM32F407Opts): Promise<EmulatorHandle>;
|
|
13
|
+
export function decodeFirmware(key: string): Uint8Array;
|
|
14
|
+
export function createNetSim(opts?: any): any;
|
|
15
|
+
|
|
16
|
+
export const FIRMWARES: Record<string, { bytes: string; [key: string]: unknown }>;
|
|
17
|
+
export const bindings: any;
|
|
18
|
+
export const unicornFactory: any;
|
|
19
|
+
export const svdXml: string;
|
|
20
|
+
|
|
21
|
+
// Component-attachment API (attach devices to an emulator handle).
|
|
22
|
+
export class LED {
|
|
23
|
+
constructor(emu: any, port: string, num: number, opts?: { activeLow?: boolean });
|
|
24
|
+
on(): void;
|
|
25
|
+
off(): void;
|
|
26
|
+
toggle(): void;
|
|
27
|
+
read(): boolean;
|
|
28
|
+
onChange(cb: (on: boolean) => void): void;
|
|
29
|
+
}
|
|
30
|
+
export class Button {
|
|
31
|
+
constructor(emu: any, port: string, num: number, opts?: { activeLow?: boolean });
|
|
32
|
+
press(): void;
|
|
33
|
+
release(): void;
|
|
34
|
+
read(): boolean;
|
|
35
|
+
on(evt: 'down' | 'up', cb: () => void): void;
|
|
36
|
+
}
|
|
37
|
+
export class Pwm {
|
|
38
|
+
constructor(emu: any, timer: string, channel?: number, opts?: { clockHz?: number });
|
|
39
|
+
setDuty(percent: number): void;
|
|
40
|
+
read(): number;
|
|
41
|
+
}
|
|
42
|
+
export class Potentiometer {
|
|
43
|
+
constructor(emu: any, peripheral: string, channel?: number, opts?: { min?: number; max?: number });
|
|
44
|
+
set(value: number): void;
|
|
45
|
+
}
|
|
46
|
+
export class I2cRegisterDevice {
|
|
47
|
+
constructor(emu: any, peripheral: string, opts?: any);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Board LED map + host reset/boot control (see site/boards.js,
|
|
51
|
+
// site/emulator.js, site/stm32f4.js).
|
|
52
|
+
export interface BoardLed { bank: number; pin: number; label: string; }
|
|
53
|
+
export declare const BOARD_LED: Record<string, BoardLed>;
|
|
54
|
+
export declare const BOARD_LED_ALIASES: Record<string, BoardLed>;
|
|
55
|
+
export declare function boardLed(fwName: string, boardKey: string): BoardLed;
|
package/index.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// stm32f4-emu — Node API entry.
|
|
2
2
|
// Wraps the browser/Node-universal emulator.js with the bundled assets
|
|
3
|
-
// (SVD, wasm bindings
|
|
3
|
+
// (SVD, wasm bindings) so Node consumers get a one-call setup.
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
5
|
import * as bindings from './site/vendor/stm32_periph_wasm.js';
|
|
7
6
|
import { createEmulator } from './site/emulator.js';
|
|
8
7
|
import { createNetSim } from './site/netsim.js';
|
|
@@ -14,23 +13,19 @@ import { STM32F4, GPIOPin, USART, DMAStream } from './site/stm32f4.js';
|
|
|
14
13
|
// `firmware` option is optional (defer to loadBin/loadHex/loadELF after
|
|
15
14
|
// create), matching the rp2040js / avr8js ergonomics.
|
|
16
15
|
STM32F4.create = (opts = {}) => STM32F4._create({
|
|
17
|
-
bindings,
|
|
16
|
+
bindings, svdXml, wasmInit: wasmBytes, ...opts,
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
const require = createRequire(import.meta.url);
|
|
21
|
-
const unicornFactory = require('./site/vendor/unicorn_arm.cjs');
|
|
22
|
-
|
|
23
19
|
const svdXml = readFileSync(new URL('./site/vendor/stm32f407.svd', import.meta.url), 'utf8');
|
|
24
20
|
const wasmBytes = new Uint8Array(readFileSync(new URL('./site/vendor/stm32_periph_wasm_bg.wasm', import.meta.url)));
|
|
25
21
|
|
|
26
22
|
// Decode a base64-encoded firmware from FIRMWARES.
|
|
23
|
+
// Uses single-pass Uint8Array.from for fewer allocations than manual loop.
|
|
27
24
|
export function decodeFirmware(key) {
|
|
28
25
|
const fw = FIRMWARES[key];
|
|
29
26
|
if (!fw) throw new Error(`unknown firmware '${key}' (have: ${Object.keys(FIRMWARES).join(', ')})`);
|
|
30
27
|
const bin = atob(fw.bytes);
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
33
|
-
return out;
|
|
28
|
+
return Uint8Array.from(bin, c => c.charCodeAt(0));
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
// Convenience: create an emulator with the bundled STM32F407 assets.
|
|
@@ -41,7 +36,8 @@ export async function createSTM32F407(opts = {}) {
|
|
|
41
36
|
const { firmware } = opts;
|
|
42
37
|
const bin = typeof firmware === 'string' ? decodeFirmware(firmware) : firmware;
|
|
43
38
|
if (!bin) throw new Error('createSTM32F407 requires `firmware` (Uint8Array or a FIRMWARES key)');
|
|
44
|
-
return createEmulator({ ...opts, firmware: bin, bindings,
|
|
39
|
+
return createEmulator({ ...opts, firmware: bin, bindings, svdXml, wasmInit: wasmBytes });
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
export { createEmulator, createNetSim, FIRMWARES, bindings,
|
|
42
|
+
export { createEmulator, createNetSim, FIRMWARES, bindings, svdXml, LED, Button, Pwm, I2cRegisterDevice, Potentiometer, STM32F4, GPIOPin, USART, DMAStream };
|
|
43
|
+
export { boardLed, BOARD_LED, BOARD_LED_ALIASES } from './site/boards.js';
|
package/mcp/server.mjs
CHANGED
|
@@ -252,4 +252,27 @@ server.registerTool('reset', {
|
|
|
252
252
|
return text(`closed session (${was})`);
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
+
server.registerTool('reset_cpu', {
|
|
256
|
+
description: 'Host reset button: CPU back to the vector-table SP/PC, peripherals keep state (like a real NRST pulse without the hold). Prefer over load_firmware when re-running the same image.',
|
|
257
|
+
inputSchema: {},
|
|
258
|
+
}, async () => {
|
|
259
|
+
const { emu } = requireSession();
|
|
260
|
+
if (typeof emu.resetCpu === 'function') emu.resetCpu();
|
|
261
|
+
else emu.reset();
|
|
262
|
+
return text('cpu reset to vector table');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
server.registerTool('set_nrst', {
|
|
266
|
+
description: 'Hold (assert=true) or release the NRST line. While held, step() only advances the model clock — the CPU executes nothing, like real hardware in reset. Query with level omitted.',
|
|
267
|
+
inputSchema: { level: z.boolean().optional().describe('true = assert (hold in reset), false = release; omit to query') },
|
|
268
|
+
}, async ({ level }) => {
|
|
269
|
+
const { emu } = requireSession();
|
|
270
|
+
if (level === undefined) {
|
|
271
|
+
const s = typeof emu.isNrstAsserted === 'function' ? emu.isNrstAsserted() : false;
|
|
272
|
+
return text({ asserted: !!s });
|
|
273
|
+
}
|
|
274
|
+
const s = typeof emu.setNrst === 'function' ? emu.setNrst(level) : false;
|
|
275
|
+
return text({ asserted: !!s });
|
|
276
|
+
});
|
|
277
|
+
|
|
255
278
|
await server.connect(new StdioServerTransport());
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stm32f4-emu",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "STM32F407 emulator:
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "STM32F407 emulator: Rust CPU + peripheral model (WASM). Runs real Cortex-M4 firmware in Node or the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"stm32f4-mcp": "./mcp/server.mjs",
|
|
10
10
|
"stm32f4-bridge": "./site/ws-bridge.mjs"
|
|
11
11
|
},
|
|
12
|
+
"types": "index.d.ts",
|
|
12
13
|
"exports": {
|
|
13
14
|
".": "./index.mjs",
|
|
14
15
|
"./components": "./site/components.js",
|
|
@@ -18,26 +19,31 @@
|
|
|
18
19
|
"./loaders": "./site/loaders.js",
|
|
19
20
|
"./stm32f4": "./site/stm32f4.js",
|
|
20
21
|
"./vendor": "./site/vendor/stm32_periph_wasm.js",
|
|
21
|
-
"./vendor/unicorn": "./site/vendor/unicorn_arm.cjs",
|
|
22
22
|
"./remote": "./site/remote-emu.js",
|
|
23
|
-
"./ws-bridge": "./site/ws-bridge.mjs"
|
|
24
|
-
"./site": "./site/index.html"
|
|
23
|
+
"./ws-bridge": "./site/ws-bridge.mjs"
|
|
25
24
|
},
|
|
26
25
|
"files": [
|
|
27
26
|
"index.mjs",
|
|
27
|
+
"index.d.ts",
|
|
28
28
|
"cli.mjs",
|
|
29
29
|
"README.md",
|
|
30
|
+
"LICENSE",
|
|
30
31
|
"site/emulator.js",
|
|
32
|
+
"site/emulator.d.ts",
|
|
33
|
+
"site/stm32f4.js",
|
|
34
|
+
"site/boards.js",
|
|
31
35
|
"site/components.js",
|
|
32
|
-
"mcp/server.mjs",
|
|
33
36
|
"site/netsim.js",
|
|
34
37
|
"site/firmware.js",
|
|
35
38
|
"site/loaders.js",
|
|
36
|
-
"site/index.html",
|
|
37
|
-
"site/app.js",
|
|
38
39
|
"site/remote-emu.js",
|
|
39
40
|
"site/ws-bridge.mjs",
|
|
40
|
-
"site/
|
|
41
|
+
"site/app.js",
|
|
42
|
+
"site/index.html",
|
|
43
|
+
"site/console.html",
|
|
44
|
+
"site/vendor/",
|
|
45
|
+
"mcp/server.mjs",
|
|
46
|
+
"tools/make_firmware.mjs"
|
|
41
47
|
],
|
|
42
48
|
"keywords": [
|
|
43
49
|
"stm32",
|
|
@@ -45,7 +51,6 @@
|
|
|
45
51
|
"emulator",
|
|
46
52
|
"arm",
|
|
47
53
|
"cortex-m4",
|
|
48
|
-
"unicorn",
|
|
49
54
|
"wasm",
|
|
50
55
|
"embedded",
|
|
51
56
|
"firmware"
|
|
@@ -63,8 +68,8 @@
|
|
|
63
68
|
"node": ">=20"
|
|
64
69
|
},
|
|
65
70
|
"scripts": {
|
|
66
|
-
"test": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs && node site/test_qspi_cdp.mjs && node site/test_browser.mjs",
|
|
67
|
-
"test:unit": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs",
|
|
71
|
+
"test": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_eth_mock_model.mjs && node site/test_eth_mock_consumer.mjs && node site/test_periph_mock_consumer.mjs && node site/test_blinky.mjs && node site/test_blinky_f401.mjs && node site/test_blinky_f411.mjs && node site/test_blinky_f407g.mjs && node site/test_blinky_nucleo_f401.mjs && node site/test_blinky_nucleo_f411.mjs && node site/test_blinky_f429.mjs && node site/test_blinky_f407ve.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs && node site/test_fpu.mjs && node site/test_fpu_irq.mjs && node site/test_mpu.mjs && node site/test_usb.mjs && node site/test_arduino_boards.mjs && node site/test_exti.mjs && node site/test_flash.mjs && node site/test_spi_flash.mjs && node site/test_board_matrix.mjs && node site/test_qspi_cdp.mjs && node site/test_browser.mjs",
|
|
72
|
+
"test:unit": "node site/test_stm32f4_api.mjs && node site/test_stm32f4_periph.mjs && node site/test_ws_bridge.mjs && node site/test_flow.mjs && node site/test_blinky.mjs && node site/test_blinky_f401.mjs && node site/test_blinky_f411.mjs && node site/test_blinky_f407g.mjs && node site/test_blinky_nucleo_f401.mjs && node site/test_blinky_nucleo_f411.mjs && node site/test_blinky_f429.mjs && node site/test_blinky_f407ve.mjs && node site/test_rx_interrupt.mjs && node site/test_component_led.mjs && node site/test_component_button.mjs && node site/test_component_pwm.mjs && node site/test_component_i2cregfile.mjs && node site/test_component_adc.mjs && node site/test_multi_instance.mjs && node site/test_fsmc_dcmi.mjs && node site/test_fsmc.mjs && node site/test_dcmi.mjs && node site/probe_freertos.mjs && node site/test_candemo.mjs && node site/test_lowpower.mjs && node site/test_edge_cases.mjs && node site/test_can_inject.mjs && node site/test_watchdog.mjs && node site/test_wwdg.mjs && node site/test_wwdg_window.mjs && node site/test_tim_capture.mjs && node site/test_qspi.mjs",
|
|
68
73
|
"test:bridge": "node site/test_ws_bridge.mjs",
|
|
69
74
|
"test:bridge:browser": "node site/test_bridge_cdp.mjs",
|
|
70
75
|
"test:flow": "node site/test_flow.mjs",
|
|
@@ -78,7 +83,12 @@
|
|
|
78
83
|
"test:wwdgwin": "node site/test_wwdg_window.mjs",
|
|
79
84
|
"test:timcap": "node site/test_tim_capture.mjs",
|
|
80
85
|
"test:lowpower": "node site/test_lowpower.mjs",
|
|
86
|
+
"test:standby": "node site/test_standby.mjs",
|
|
81
87
|
"test:qspi": "node site/test_qspi.mjs",
|
|
88
|
+
"test:eth:mock": "node site/test_eth_mock_model.mjs && node site/test_eth_mock_consumer.mjs && node site/test_periph_mock_consumer.mjs",
|
|
89
|
+
"test:fpu": "node site/test_fpu.mjs",
|
|
90
|
+
"test:fpuirq": "node site/test_fpu_irq.mjs",
|
|
91
|
+
"test:mpu": "node site/test_mpu.mjs",
|
|
82
92
|
"test:qspi:browser": "node site/test_qspi_cdp.mjs",
|
|
83
93
|
"test:browser": "node site/test_browser.mjs",
|
|
84
94
|
"test:edge": "node site/test_edge_cases.mjs",
|
|
@@ -89,7 +99,15 @@
|
|
|
89
99
|
"bridge": "node site/ws-bridge.mjs",
|
|
90
100
|
"firmwares": "node tools/make_firmware.mjs",
|
|
91
101
|
"serve": "python3 -m http.server 8123 --directory site",
|
|
92
|
-
"prepack": "node tools/make_firmware.mjs"
|
|
102
|
+
"prepack": "node tools/make_firmware.mjs",
|
|
103
|
+
"typecheck": "tsc --noEmit",
|
|
104
|
+
"lint": "eslint site mcp tools index.mjs cli.mjs",
|
|
105
|
+
"format": "prettier --check .",
|
|
106
|
+
"format:write": "prettier --write .",
|
|
107
|
+
"build:wasm": "wasm-pack build stm32-periph-wasm --target web --out-dir ../site/vendor && rm -f site/vendor/.gitignore && cp monox/stm32f407.svd site/vendor/ 2>/dev/null || true; command -v wasm-opt >/dev/null 2>&1 && wasm-opt -O3 site/vendor/stm32_periph_wasm_bg.wasm -o site/vendor/stm32_periph_wasm_bg.wasm || echo 'wasm-opt not found, skipping'",
|
|
108
|
+
"build:wasm:node": "wasm-pack build stm32-periph-wasm --target nodejs --out-dir pkg",
|
|
109
|
+
"build:wasm:opt": "wasm-opt -O3 site/vendor/stm32_periph_wasm_bg.wasm -o site/vendor/stm32_periph_wasm_bg.wasm",
|
|
110
|
+
"test:wasm": "node site/test_wasm_cpu.mjs && node site/test_flow_wasm.mjs && node site/test_audio_wasm.mjs && node site/test_wasm_multi.mjs && node site/test_lowpower_wasm.mjs && node site/probe_freertos.mjs && node site/test_doom_wasm.mjs"
|
|
93
111
|
},
|
|
94
112
|
"dependencies": {
|
|
95
113
|
"ws": "^8.21.3"
|
|
@@ -109,6 +127,9 @@
|
|
|
109
127
|
"devDependencies": {
|
|
110
128
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
111
129
|
"playwright": "^1.62.1",
|
|
112
|
-
"zod": "^3.25 || ^4.0"
|
|
130
|
+
"zod": "^3.25 || ^4.0",
|
|
131
|
+
"typescript": "^5.6.0",
|
|
132
|
+
"eslint": "^9.0.0",
|
|
133
|
+
"prettier": "^3.0.0"
|
|
113
134
|
}
|
|
114
135
|
}
|