stm32f4-emu 1.1.1 → 1.3.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 CHANGED
@@ -6,6 +6,14 @@
6
6
  [![CI](https://github.com/danish9661/stm32F4-emulator/actions/workflows/ci.yml/badge.svg)](https://github.com/danish9661/stm32F4-emulator/actions/workflows/ci.yml)
7
7
  [![Pages](https://github.com/danish9661/stm32F4-emulator/actions/workflows/pages.yml/badge.svg)](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
18
  combines a **Rust CPU core** (a WASM-native Thumb-2 interpreter with exact
11
19
  Cortex-M exception entry/return, including the VFPv4-SP FPU) with a
@@ -15,7 +23,12 @@ browser tab**, with no SDL, no native deps, no hardware.
15
23
 
16
24
  It ships three real networking firmwares (`eth_http`, `eth_dhcp`, `eth_test`)
17
25
  that do DHCP + TCP + HTTP against a simulated (or a real gVisor-backed)
18
- network, a browser demo, and a publishable npm package.
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.
19
32
 
20
33
  ## Live demo
21
34
 
@@ -24,7 +37,7 @@ The browser demo deploys to GitHub Pages:
24
37
  **https://danish9661.github.io/stm32F4-emulator/**
25
38
 
26
39
  A single console page that starts **idle** — nothing runs until you pick a
27
- firmware: a preset dropdown with 31 bundled binaries (network demos, a
40
+ firmware: a preset dropdown with 223 bundled binaries (network demos, a
28
41
  bare-metal LED blinker, peripheral/crypto/UART/SPI test binaries), custom
29
42
  firmware upload (`.bin`, Intel `.hex`, `.elf` — with loadable RAM segments
30
43
  and symbols — plus `.map` for a symbol table), Run/Stop/Reset, a **gateway
@@ -35,7 +48,7 @@ characters excluded per HTML spec, see AGENTS.md §11), GPIO pin readout
35
48
  for banks A–E, and key peripheral registers. Interrupt-driven firmware
36
49
  (`rx_interrupt_test`, `rx_crypto_test`) gets inline guest-IRQ delivery;
37
50
  polling firmware (the ETH demos) never uses it. For automation, a
38
- preset can auto-boot via the URL: `?fw=eth_http`, `?fw=blinky`, `?fw=crypto_test`, …
51
+ preset can auto-boot via the URL: `console.html?fw=eth_http`, `console.html?fw=blinky`, `console.html?fw=crypto_test`, …
39
52
 
40
53
  ## DOOM (in the browser)
41
54
 
@@ -60,7 +73,7 @@ reloads.
60
73
  - Controls: move W/S/A/D + arrows · strafe Shift · fire Ctrl · use
61
74
  Space · menu Enter/Esc · save F2/F6 · load F3/F9 · F1/F10/F11/F12.
62
75
  - Boot → menu → gameplay verified end-to-end by
63
- `node site/test_doom.mjs` (boot markers, menu navigation to E1M1,
76
+ `node site/test_doom_wasm.mjs` (boot markers, menu navigation to E1M1,
64
77
  palette + framebuffer, W-move + turn, audio, **save → `SAVE ok slot=0`
65
78
  → load handshake**).
66
79
  - `site/doom1.wad` is the 4.2 MB shareware WAD; the firmware never reads
@@ -77,13 +90,13 @@ npm run serve # then open http://127.0.0.1:8123
77
90
  npm test # == node site/test_flow.mjs
78
91
 
79
92
  # websocket bridge: headless Node serves the emulator, browser is a thin UI
80
- npm run bridge -- blinky/blinky.bin --port 8234
81
- # then open http://127.0.0.1:8123?bridge=ws://127.0.0.1:8234
93
+ npm run bridge -- firmware/blinky/blinky.bin --port 8234
94
+ # then open http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234
82
95
 
83
96
  # gateway-backed run: firmware talks to a REAL network stack (gVisor)
84
97
  cd stm32-periph-wasm/pkg
85
- node cli.mjs ../../eth_http/eth_http.bin 10000000 \
86
- --gateway --config=../../eth_http/config.yaml
98
+ node cli.mjs ../../firmware/eth_http/eth_http.bin 10000000 \
99
+ --gateway --config=../../firmware/eth_http/config.yaml
87
100
  # (requires an HTTP server at 127.0.0.1:8092; see AGENTS.md §10)
88
101
  ```
89
102
 
@@ -119,6 +132,11 @@ custom SPI/I2C devices) to pins and buses with the component API —
119
132
  `ext_devices.spiDevices`/`i2cDevices` for your own bus protocol. See
120
133
  [docs/components.md](docs/components.md).
121
134
 
135
+ For platform integration (Wokwi/OpenHW/Velxio) use the `STM32F4` facade —
136
+ factories, GPIO/USART/SPI/I2C objects, polled event callbacks, DMA
137
+ controller, live displays, SWD/JTAG/power/probe helpers. See
138
+ [docs/facade.md](docs/facade.md).
139
+
122
140
  ## Drive it from an AI agent (MCP)
123
141
 
124
142
  `mcp/server.mjs` exposes the emulator over the Model Context Protocol —
@@ -142,17 +160,17 @@ is a thin UI. Zero impact on the existing local WASM path:
142
160
 
143
161
  ```bash
144
162
  # 1. Start the bridge in Node (serves the emulator over WS on port 8234)
145
- node site/ws-bridge.mjs eth_http/eth_http.bin --port 8234
163
+ node site/ws-bridge.mjs firmware/eth_http/eth_http.bin --port 8234
146
164
 
147
165
  # 2. Open the browser console with the bridge URL param
148
- open "http://127.0.0.1:8123/?bridge=ws://127.0.0.1:8234"
166
+ open "http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234"
149
167
  ```
150
168
 
151
169
  The `RemoteEmu` adapter (`site/remote-emu.js`) is a drop-in replacement
152
170
  for the local `emu` object — same `step()`/`drainUart()`/`read32()` API,
153
171
  all proxied over binary WebSocket. Device stubs (OLED/TFT/etc.) run in
154
172
  Node and are not visible to browser JS. Without `?fw=`, the page boots
155
- whatever firmware the bridge was started with; with `?fw=blinky&bridge=ws://…`,
173
+ whatever firmware the bridge was started with; with `console.html?fw=blinky&bridge=ws://…`,
156
174
  the browser sends the firmware image over the bridge.
157
175
 
158
176
  See AGENTS.md §20 for the full binary protocol reference.
@@ -164,8 +182,11 @@ See AGENTS.md §20 for the full binary protocol reference.
164
182
  | `eth_http/` | DHCP + TCP client + HTTP GET + prints the response | `TCP connected`, `=== HTTP <len>b ===` |
165
183
  | `eth_dhcp/` | Loops DHCP Discover/Offer/Request/Ack | `DHCP SUCCESS` |
166
184
  | `eth_test/` | Raw ETH TX/RX self-test | `ETH Test: done` |
185
+ | `eth_feat_test/` | 66-marker Ethernet feature matrix (netsim, F407 + F429) | `FEAT Test: done` (all 66 markers, `FAIL`/`TIMEOUT` anti-markers) |
186
+ | `lwip_demo/` | Real LwIP 2.2.1 (DHCP→DNS→TCP echo→TCP server→UDP echo) | `LWIP DEMO DONE` |
187
+ | `eth_pins_test/` | MII/RMII pin-level mirrors (TX_EN/CRS_DV/RXD/COL/MDIO/MDC) | `PINS ALL PASS` |
167
188
  | `blinky/` | **No ethernet** — LED blinker on GPIOA PA5 + UART tick counter | `tick N LED=ON/OFF` |
168
- | `doom/` | **DOOM 1 shareware** (doomgeneric F407 port, browser page `site/doom.html`) | `node site/test_doom.mjs` (boot + menu + gameplay + save/load) |
189
+ | `doom/` | **DOOM 1 shareware** (doomgeneric F407 port, browser page `site/doom.html`) | `node site/test_doom_wasm.mjs` (boot + menu + gameplay + save/load) |
169
190
 
170
191
  Plus 17 more test binaries (`crypto_test`, `hal_test`, `timer_test`,
171
192
  `periph_test`, `echo_test`, `blink_serial`, `rx_interrupt_test`,
@@ -198,10 +219,13 @@ firmware .bin ──► Rust WASM CPU (Thumb-2 + NVIC/exceptions)
198
219
  (SysTick, ETH, USART, SVC/PendSV) are delivered inline with exact
199
220
  exception stacking — no native deps, no JIT, no hooks.
200
221
  - **Peripherals**: a `wasm-bindgen` crate (`stm32-periph-wasm/`); registers
201
- and bit fields come from the vendor SVD (`monox/stm32f407.svd`).
202
- - **Ethernet**: TX is captured from the DMA descriptors; RX frames are
203
- injected into the RX ring and the firmware's `eth_irq_flag` (SRAM) drives
204
- polling — no interrupts required. Optionally, a Go gateway
222
+ and bit fields come from the vendor SVD (`site/vendor/stm32f407.svd`).
223
+ - **Ethernet**: TX is captured from the DMA descriptors (OWN/FS/LS poll
224
+ demand); RX frames are delivered head-only with FS+LS status, IPHCE/PCE
225
+ checksum status, and PTP snapshots for event messages. The full MAC
226
+ register map is cross-checked against CMSIS `stm32f407xx.h` (DMASR/
227
+ DMAIER/MACFFR/MACPMTCTL positions — see [NETWORKING.md](NETWORKING.md)
228
+ §6 for the audit). Optionally, a Go gateway
205
229
  (`openhw-local-gateway/`) with a gVisor network stack makes the firmware
206
230
  talk to a real network: `node cli.mjs <fw.bin> <inst> --gateway`.
207
231
  - **Browser build** (`site/`): same modules as ESM; `site/emulator.js` is an
@@ -230,13 +254,13 @@ firmware .bin ──► Rust WASM CPU (Thumb-2 + NVIC/exceptions)
230
254
  ├── index.mjs, package.json npm package entry (stm32f4-emu)
231
255
  ├── mcp/ MCP server (drive the emulator from an AI agent)
232
256
  ├── .github/workflows/ CI (Linux/Windows/macOS test matrix) + Pages deploy
233
- ├── tools/make_firmware.mjs Regenerates site/firmware.js from eth_*/.bin
257
+ ├── tools/make_firmware.mjs Regenerates site/firmware.js from firmware/*/.bin
234
258
  ├── stm32-periph-wasm/ Rust peripheral model (WASM build + pkg/)
235
- ├── eth_http/ eth_dhcp/ eth_test/ Sample network firmwares + configs
236
- ├── doom/ DOOM 1 port (doomgeneric f407 target + WAD path)
259
+ ├── firmware/eth_http/ eth_dhcp/ eth_test/ Sample network firmwares + configs
260
+ ├── firmware/doom/ DOOM 1 port (doomgeneric f407 target + WAD path)
237
261
  ├── openhw-local-gateway/ Go gateway (gVisor network stack)
238
262
  ├── scripts/verify_ethernet.sh Regression runner for all three firmwares
239
- ├── src/, monox/, saturn/ Native SDL emulator (upstream heritage)
263
+ ├── hardware/monox/, hardware/saturn/ Upstream printer firmwares (heritage)
240
264
  └── AGENTS.md Full architecture, build steps, runbook
241
265
  ```
242
266
 
@@ -248,10 +272,9 @@ cd stm32-periph-wasm && wasm-pack build --release --target nodejs
248
272
 
249
273
  # firmware (bare-metal Makefiles; toolchain from the Arduino core)
250
274
  TOOLCHAIN="$HOME/.arduino15/packages/STMicroelectronics/tools/xpack-arm-none-eabi-gcc/14.2.1-1.1/bin/arm-none-eabi-" \
251
- make -C eth_http # also eth_dhcp, eth_test
275
+ make -C firmware/eth_http # also eth_dhcp, eth_test (all firmware lives in firmware/)
252
276
 
253
- # native SDL emulator (upstream heritage, not the headless path)
254
- cd stm32-emulator-main && cargo build --release
277
+ # (no native SDL emulator in this tree — the headless WASM path above is the only backend)
255
278
  ```
256
279
 
257
280
  `wasm-pack` writes `site/vendor/.gitignore` containing `*` after a browser
@@ -259,7 +282,10 @@ rebuild — delete it so the vendor assets stay tracked/committed.
259
282
 
260
283
  ## Testing
261
284
 
262
- - `npm test` — flow test (`site/test_flow.mjs`) + blinky test
285
+ - `npm test` — flow test (`site/test_flow.mjs`) + Ethernet mock harnesses
286
+ (`site/test_eth_mock_model.mjs`: 18 checks, fake bindings + real driver;
287
+ `site/test_eth_mock_consumer.mjs`: 44 checks, fake firmware + real model)
288
+ + blinky test
263
289
  (`site/test_blinky.mjs`) + interrupt-UART test (`site/test_rx_interrupt.mjs`)
264
290
  + component-API tests (`site/test_component_{led,button,pwm,i2cregfile}.mjs`,
265
291
  each against real firmware — LED/blinky, Button/exti_test, Pwm/buzzer_test,
@@ -269,7 +295,7 @@ rebuild — delete it so the vendor assets stay tracked/committed.
269
295
  process (see docs/components.md). The same suite runs in CI on
270
296
  `ubuntu-latest`/`windows-latest`/`macos-latest`
271
297
  ([.github/workflows/ci.yml](.github/workflows/ci.yml)) on every push.
272
- - `node site/test_doom.mjs` — DOOM boot → menu → E1M1 gameplay + save/load
298
+ - `node site/test_doom_wasm.mjs` — DOOM boot → menu → E1M1 gameplay + save/load
273
299
  (see the DOOM section above).
274
300
  - `scripts/verify_ethernet.sh [max_inst]` — runs all three firmwares through
275
301
  the gateway, asserts the success markers and 0 `TCP fail`.
@@ -281,7 +307,7 @@ rebuild — delete it so the vendor assets stay tracked/committed.
281
307
  - [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).
282
308
  - [docs/architecture.md](docs/architecture.md) — how the emulator is put
283
309
  together (CPU, peripheral model, drivers, ETH flow, interrupts).
284
- - [docs/peripherals.md](docs/peripherals.md) — all 33 peripherals and the
310
+ - [docs/peripherals.md](docs/peripherals.md) — all 41 peripherals and the
285
311
  level each is implemented to, plus external devices and known gaps.
286
312
  - [docs/usage.md](docs/usage.md) — CLI, browser, and npm-library usage,
287
313
  config files, env vars, building.
package/index.d.ts CHANGED
@@ -18,7 +18,287 @@ export const bindings: any;
18
18
  export const unicornFactory: any;
19
19
  export const svdXml: string;
20
20
 
21
- // Component-attachment API (attach devices to an emulator handle).
21
+ // STM32F4 high-level facade (see site/stm32f4.js). F1-API parity surface:
22
+ // factories, execute/step results, GPIO/USART/SPI/I2C classes, symbol
23
+ // helpers, and engine pass-throughs. TX is a single shared model buffer
24
+ // (only usart1.onData/output fire); event callbacks beyond SPI/I2C taps
25
+ // are absent by design (no core event queue on F4).
26
+ export interface ExecuteResult { instCount: number; stopped: boolean; }
27
+ export interface FacadeStepResult { pc: number; instCount: number; stopped: boolean; }
28
+ export class GPIOPin {
29
+ constructor(mcu: STM32F4, port: string, pin: number);
30
+ on(event: 'change', cb: (high: boolean) => void): () => void;
31
+ addListener(cb: (high: boolean) => void): () => void;
32
+ read(): boolean;
33
+ readInput(): boolean;
34
+ setInput(high: boolean): void;
35
+ setInputValue(high: boolean): void;
36
+ detach(): void;
37
+ }
38
+ export class GPIO {
39
+ constructor(mcu: STM32F4);
40
+ pin(port: string | number, pin: number): GPIOPin;
41
+ }
42
+ export interface ChipRecord {
43
+ key: string;
44
+ svd: string;
45
+ flash_size: number;
46
+ ram_size: number;
47
+ maxClockMHz: number;
48
+ label: string;
49
+ idcode: number;
50
+ usarts: number[];
51
+ timers: number[];
52
+ can: number[];
53
+ dac: boolean;
54
+ ltdc: boolean;
55
+ gpioBanks: number;
56
+ spi: number[];
57
+ i2c: number[];
58
+ uart78: number[];
59
+ i2s: number[];
60
+ sai: number[];
61
+ sdio: boolean;
62
+ dcmi: boolean;
63
+ crc: boolean;
64
+ cryp: boolean;
65
+ hash: boolean;
66
+ rng: boolean;
67
+ fsmc: boolean;
68
+ dma2d: boolean;
69
+ qspi: boolean;
70
+ }
71
+ export declare const CHIPS: Record<string, Omit<ChipRecord, 'key'>>;
72
+ export declare function chipInfo(key?: string): ChipRecord;
73
+ export class USART {
74
+ readonly n: number;
75
+ onData: ((byte: number) => void) | null;
76
+ constructor(mcu: STM32F4, n: number);
77
+ send(data: string | Uint8Array | number[]): void;
78
+ sendData(data: string | Uint8Array | number[]): void;
79
+ get output(): string;
80
+ }
81
+ export class SPI {
82
+ readonly ch: number;
83
+ onTransfer: ((channel: string, tx: number[], rx: number[]) => void) | null;
84
+ constructor(mcu: STM32F4, ch: number);
85
+ injectMiso(bytes: Uint8Array | number[]): void;
86
+ }
87
+ export class I2C {
88
+ readonly ch: number;
89
+ onStart: ((addr: number, isRead: boolean) => void) | null;
90
+ onWrite: ((byte: number) => void) | null;
91
+ onRead: (() => void) | null;
92
+ onStop: (() => void) | null;
93
+ constructor(mcu: STM32F4, ch: number);
94
+ injectRx(bytes: Uint8Array | number[]): void;
95
+ }
96
+ export class DMAStream {
97
+ constructor(mcu: STM32F4, streamIndex: number);
98
+ pendingCount(): number;
99
+ setCompleted(success?: boolean): void;
100
+ tcif(): boolean;
101
+ htif(): boolean;
102
+ cr(): number;
103
+ ndtr(): number;
104
+ }
105
+ export class DMAController {
106
+ constructor(mcu: STM32F4, controller?: 1 | 2);
107
+ lisr(): number;
108
+ hisr(): number;
109
+ tcif(s: number): boolean;
110
+ htif(s: number): boolean;
111
+ cr(s: number): number;
112
+ ndtr(s: number): number;
113
+ par(s: number): number;
114
+ m0ar(s: number): number;
115
+ m1ar(s: number): number;
116
+ fcr(s: number): number;
117
+ stream(s: number): DMAStream;
118
+ }
119
+ export class Display {
120
+ constructor(mcu: STM32F4);
121
+ get oled(): { w: number; h: number; fb: Uint8Array; frame: number } | null;
122
+ get tft(): { w: number; h: number; fb: Uint8Array; frame: () => number } | null;
123
+ ltdc(): { w: number; h: number; pf: string; fb: Uint8Array; addr: number; pitch: number } | null;
124
+ }
125
+ export class STM32F4 {
126
+ readonly gpio: GPIO;
127
+ readonly usart: USART;
128
+ // Slots for absent-silicon USARTs are null (F401/F411: 3,4,5).
129
+ readonly usart1: USART; readonly usart2: USART; readonly usart3: USART | null;
130
+ readonly usart4: USART | null; readonly usart5: USART | null; readonly usart6: USART;
131
+ readonly usart7: USART | null; readonly usart8: USART | null;
132
+ readonly usarts: Record<number, USART>;
133
+ readonly spi1: SPI | null; readonly spi2: SPI; readonly spi3: SPI;
134
+ readonly spi4: SPI | null; readonly spi5: SPI | null; readonly spi6: SPI | null;
135
+ readonly spiBus: Record<number, SPI>;
136
+ readonly i2c1: I2C; readonly i2c2: I2C; readonly i2c3: I2C;
137
+ readonly i2cBus: Record<number, I2C>;
138
+ readonly spi: { specs: any[]; pushMiso(peripheral: string, bytes: Uint8Array | number[]): void };
139
+ readonly i2c: { specs: any[]; pushRx(peripheral: string, bytes: Uint8Array | number[]): void };
140
+ readonly dma: { stream(index: number): DMAStream; controller(ctl: 1 | 2): DMAController };
141
+ readonly display: Display;
142
+ readonly chip: ChipRecord;
143
+ readonly chipSvdXml: string | null;
144
+ // Wokwi/OpenHW/Velxio integration callbacks (polled dispatch, see site/stm32f4.js).
145
+ onExtiEdge: ((line: number) => void) | null;
146
+ onCanTx: ((can: number) => void) | null;
147
+ onCanRx: ((can: number, id: number, len: number, data: number[]) => void) | null;
148
+ onTimUpdate: ((tim: number) => void) | null;
149
+ onTimCapture: ((tim: number, ch: number, val: number) => void) | null;
150
+ onDmaTc: ((controller: number, stream: number) => void) | null;
151
+ onWdogReset: ((which: number) => void) | null;
152
+ onUsbIn: ((ep: number, data: number[]) => void) | null;
153
+ onUsbHsIn: ((ep: number, data: number[]) => void) | null;
154
+ onItmByte: ((port: number, byte: number) => void) | null;
155
+ onFsmcAccess: ((bank: number, off: number, write: boolean, size: number, val: number) => void) | null;
156
+ onAdcDone: ((adc: number, chan: number, value: number) => void) | null;
157
+ onDacWrite: ((chan: number, value: number) => void) | null;
158
+ onCrcResult: ((value: number) => void) | null;
159
+ onRtcAlarm: ((which: number) => void) | null;
160
+ onI2cAlert: ((peripheral: number, asserted: boolean) => void) | null;
161
+ onHostTx: ((...args: any[]) => void) | null;
162
+ onHostRx: ((...args: any[]) => void) | null;
163
+ constructor(emu: any, bindings?: any, chip?: ChipRecord, chipSvdXml?: string | null);
164
+ static create(opts?: any): Promise<STM32F4>;
165
+ static fromELF(buf: Uint8Array | ArrayBuffer, opts?: any): Promise<STM32F4>;
166
+ static fromBin(buf: Uint8Array | ArrayBuffer, opts?: any): Promise<STM32F4>;
167
+ static fromHex(text: string, opts?: any): Promise<STM32F4>;
168
+ loadBin(bytes: Uint8Array | ArrayBuffer, base?: number): void;
169
+ loadHex(text: string): void;
170
+ loadELF(bytes: Uint8Array | ArrayBuffer): void;
171
+ // Live peripheral injection (anytime unless noted).
172
+ setAdcChannel(peripheral: string, channel: number, value: number): void;
173
+ clearAdcChannel(peripheral: string, channel: number): void;
174
+ takeAdcDma(): number[];
175
+ canInject(id: number, dlc: number, data: Uint8Array | number[]): void;
176
+ canInjectFd(id: number, data: Uint8Array | number[], brs?: boolean): void;
177
+ timInjectCapture(timer: number, ch: number): void;
178
+ timPwmPulseUs(timer: number, ch: number, clockHz: number): number;
179
+ timOcMode(timer: number, ch: number): number;
180
+ timMoe(timer: number): boolean;
181
+ timEncoderStep(timer: number, ti: number, rising: boolean): void;
182
+ timBreakInput(timer: number, asserted: boolean): void;
183
+ dacTrigger(ch: number, src: number, dmaStaged?: boolean): void;
184
+ dacUnderrun(ch: number): boolean;
185
+ takeSpeakerSamples(): Float32Array;
186
+ usbInjectSetup(bytes: Uint8Array | number[]): boolean;
187
+ usbInjectOut(ep: number, bytes: Uint8Array | number[]): boolean;
188
+ usbTakeIn(ep: number): number[];
189
+ usbReset(): void;
190
+ usbEnumerated(): void;
191
+ usbHsInjectSetup(bytes: Uint8Array | number[]): boolean;
192
+ usbHsInjectOut(ep: number, bytes: Uint8Array | number[]): boolean;
193
+ usbHsTakeIn(ep: number): number[];
194
+ usbHsReset(): void;
195
+ usbHsEnumerated(): void;
196
+ ethInject(frame: Uint8Array | number[]): void;
197
+ // Fault / model-harness methods (mirror wasm exports 1:1).
198
+ uartSetCts(base: number, asserted: boolean): void;
199
+ uartFaultRx(base: number, fe: boolean, pe: boolean): void;
200
+ uartIdle(base: number): void;
201
+ uartLinBreak(base: number): void;
202
+ uartBreakPending(base: number): boolean;
203
+ uartTxLen(base: number): number;
204
+ uartMuted(base: number): boolean;
205
+ spiFaultCrc(base: number): void;
206
+ spiFaultModf(base: number): void;
207
+ spiSlaveSelect(base: number, asserted: boolean): void;
208
+ spiSlaveClock(base: number, mosi: number): number;
209
+ spiSlaveGate(base: number): boolean;
210
+ i2cArmArbLoss(base: number): void;
211
+ i2cArmSmbusAlert(base: number, addr: number): void;
212
+ i2cSlaveAddress(base: number, addr: number, isRead: boolean): boolean;
213
+ i2cSlaveWrite(base: number, byte: number): void;
214
+ i2cSlaveRead(base: number): number;
215
+ i2cSlaveStop(base: number): void;
216
+ i2cSlaveStatus(base: number): number;
217
+ sdioFaultDataCrc(): void;
218
+ rccInjectFailure(srcMask: number, dead: boolean): void;
219
+ flashRdpLevel(): number;
220
+ flashSetRdp(levelByte: number): void;
221
+ rngSeedEntropy(words: number[] | Uint32Array): void;
222
+ rngEntropyAvail(): number;
223
+ rtcTamperPin(level: boolean): void;
224
+ rtcTimestamp(): void;
225
+ setIntrPending(irq: number): void;
226
+ hasPendingInterrupt(): boolean;
227
+ // Scope probes (read-only model state).
228
+ adcDualLatched(): boolean;
229
+ ethPpsCount(): number;
230
+ ethPpsLevel(): boolean;
231
+ ethLinkUp(): boolean;
232
+ ethSetLink(up: boolean): void;
233
+ ltdcScanline(): number;
234
+ ltdcFrameCount(): number;
235
+ audioRemaining(): number;
236
+ audioClear(): void;
237
+ dcmiSync(vsync: boolean, hsync: boolean, pclkDiv: number): void;
238
+ qspiMmapLive(): boolean;
239
+ qspiMmapRead(offset: number): number;
240
+ sdioBusWidth(): number;
241
+ sdioCardBlocks(): number;
242
+ sdioReadBlock(block: number): number[];
243
+ takeItm(port: number): number[];
244
+ itmPending(port: number): number;
245
+ takeFsmc(bank: number): number[];
246
+ pushFsmc(bank: number, values: number[] | Uint32Array): void;
247
+ regfileGet(peripheral: string, offset: number): number;
248
+ regfileSet(peripheral: string, offset: number, value: number): void;
249
+ get oled(): any;
250
+ get tft(): any;
251
+ get rtc(): any;
252
+ get buzzer(): any;
253
+ get camera(): any;
254
+ execute(cycles?: number): ExecuteResult;
255
+ step(cycles?: number): FacadeStepResult;
256
+ uartRx(byte: number): boolean;
257
+ get uartOutput(): string;
258
+ setSymbols(mapText: string): number;
259
+ resolveSymbol(pc: number): string | null;
260
+ read32(addr: number): number;
261
+ write32(addr: number, val: number): void;
262
+ memRead32(addr: number): number;
263
+ memWriteBytes(addr: number, bytes: Uint8Array | number[]): void;
264
+ periphRead(addr: number, width?: number): number;
265
+ periphWrite(addr: number, width: number, value: number): void;
266
+ addJsPeripheral(base: number, size: number, read: (addr: number, size: number) => number, write: (addr: number, value: number, size: number) => void): boolean;
267
+ pwrMode(): number;
268
+ pwrEstimate(): number;
269
+ swdHalted(): boolean;
270
+ swdHalt(): void;
271
+ swdResume(): void;
272
+ swdStep(): number;
273
+ swdAddWatch(kind: number, addr: number, len: number): number;
274
+ swdRemoveWatch(slot: number): void;
275
+ swdTakeTrip(): number[];
276
+ swdDpRead(addr: number): number;
277
+ swdDpWrite(addr: number, value: number): void;
278
+ swdApRead(bank: number, reg: number): number;
279
+ swdApWrite(bank: number, reg: number, value: number): void;
280
+ swdRegRead(idx: number): number;
281
+ swdRegWrite(idx: number, value: number): void;
282
+ jtagReset(): void;
283
+ jtagIr(ir: number): void;
284
+ jtagIdcode(): number;
285
+ jtagDp(addr: number, rnw: boolean, wdata: number): number;
286
+ jtagAp(bank: number, reg: number, rnw: boolean, wdata: number): number;
287
+ dmaController(ctl?: 1 | 2): DMAController;
288
+ getRegisters(): any;
289
+ getPc(): number;
290
+ getSp(): number;
291
+ faultInfo(): any;
292
+ takeFault(): [number, number] | null;
293
+ stop(): void;
294
+ reset(): void;
295
+ close(): void;
296
+ resetCpu(): void;
297
+ setNrst(asserted: boolean): boolean;
298
+ isNrstAsserted(): boolean;
299
+ bootPreset(image: any): void;
300
+ ledStatus(fwName: string, boardKey: string): any;
301
+ }
22
302
  export class LED {
23
303
  constructor(emu: any, port: string, num: number, opts?: { activeLow?: boolean });
24
304
  on(): void;
@@ -46,3 +326,11 @@ export class Potentiometer {
46
326
  export class I2cRegisterDevice {
47
327
  constructor(emu: any, peripheral: string, opts?: any);
48
328
  }
329
+
330
+ // Component-attachment API (attach devices to an emulator handle).
331
+ // Board LED map + host reset/boot control (see site/boards.js,
332
+ // site/emulator.js, site/stm32f4.js).
333
+ export interface BoardLed { bank: number; pin: number; label: string; }
334
+ export declare const BOARD_LED: Record<string, BoardLed>;
335
+ export declare const BOARD_LED_ALIASES: Record<string, BoardLed>;
336
+ export declare function boardLed(fwName: string, boardKey: string): BoardLed;
package/index.mjs CHANGED
@@ -7,13 +7,17 @@ import { createEmulator } from './site/emulator.js';
7
7
  import { createNetSim } from './site/netsim.js';
8
8
  import { FIRMWARES } from './site/firmware.js';
9
9
  import { LED, Button, Pwm, I2cRegisterDevice, Potentiometer } from './site/components.js';
10
- import { STM32F4, GPIOPin, USART, DMAStream } from './site/stm32f4.js';
10
+ import { STM32F4, GPIOPin, GPIO, USART, SPI, I2C, DMAStream, DMAController, Display, CHIPS, chipInfo } from './site/stm32f4.js';
11
11
 
12
12
  // Node consumers get a one-call setup that injects the bundled assets. The
13
13
  // `firmware` option is optional (defer to loadBin/loadHex/loadELF after
14
14
  // create), matching the rp2040js / avr8js ergonomics.
15
+ // NOTE: svdXml is deliberately NOT injected here. STM32F4._create resolves
16
+ // the SVD per `chip` (F401/F411/F407/F429) itself; injecting the F407 default
17
+ // would shadow the chip map (right sizes + IDCODE, wrong registers). A caller
18
+ // passing svdXml explicitly still wins (override path in _create).
15
19
  STM32F4.create = (opts = {}) => STM32F4._create({
16
- bindings, svdXml, wasmInit: wasmBytes, ...opts,
20
+ bindings, wasmInit: wasmBytes, ...opts,
17
21
  });
18
22
 
19
23
  const svdXml = readFileSync(new URL('./site/vendor/stm32f407.svd', import.meta.url), 'utf8');
@@ -39,4 +43,5 @@ export async function createSTM32F407(opts = {}) {
39
43
  return createEmulator({ ...opts, firmware: bin, bindings, svdXml, wasmInit: wasmBytes });
40
44
  }
41
45
 
42
- export { createEmulator, createNetSim, FIRMWARES, bindings, svdXml, LED, Button, Pwm, I2cRegisterDevice, Potentiometer, STM32F4, GPIOPin, USART, DMAStream };
46
+ export { createEmulator, createNetSim, FIRMWARES, bindings, svdXml, LED, Button, Pwm, I2cRegisterDevice, Potentiometer, STM32F4, GPIOPin, GPIO, USART, SPI, I2C, DMAStream, DMAController, Display, CHIPS, chipInfo };
47
+ 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,6 +1,6 @@
1
1
  {
2
2
  "name": "stm32f4-emu",
3
- "version": "1.1.1",
3
+ "version": "1.3.1",
4
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",
@@ -31,6 +31,7 @@
31
31
  "site/emulator.js",
32
32
  "site/emulator.d.ts",
33
33
  "site/stm32f4.js",
34
+ "site/boards.js",
34
35
  "site/components.js",
35
36
  "site/netsim.js",
36
37
  "site/firmware.js",
@@ -39,6 +40,7 @@
39
40
  "site/ws-bridge.mjs",
40
41
  "site/app.js",
41
42
  "site/index.html",
43
+ "site/console.html",
42
44
  "site/vendor/",
43
45
  "mcp/server.mjs",
44
46
  "tools/make_firmware.mjs"
@@ -66,8 +68,8 @@
66
68
  "node": ">=20"
67
69
  },
68
70
  "scripts": {
69
- "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_fpu.mjs && node site/test_fpu_irq.mjs && node site/test_mpu.mjs && node site/test_usb.mjs && node site/test_qspi_cdp.mjs && node site/test_browser.mjs",
70
- "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",
71
73
  "test:bridge": "node site/test_ws_bridge.mjs",
72
74
  "test:bridge:browser": "node site/test_bridge_cdp.mjs",
73
75
  "test:flow": "node site/test_flow.mjs",
@@ -81,7 +83,9 @@
81
83
  "test:wwdgwin": "node site/test_wwdg_window.mjs",
82
84
  "test:timcap": "node site/test_tim_capture.mjs",
83
85
  "test:lowpower": "node site/test_lowpower.mjs",
86
+ "test:standby": "node site/test_standby.mjs",
84
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",
85
89
  "test:fpu": "node site/test_fpu.mjs",
86
90
  "test:fpuirq": "node site/test_fpu_irq.mjs",
87
91
  "test:mpu": "node site/test_mpu.mjs",
@@ -100,7 +104,7 @@
100
104
  "lint": "eslint site mcp tools index.mjs cli.mjs",
101
105
  "format": "prettier --check .",
102
106
  "format:write": "prettier --write .",
103
- "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'",
107
+ "build:wasm": "wasm-pack build stm32-periph-wasm --target web --out-dir ../site/vendor && rm -f site/vendor/.gitignore && git checkout -- site/vendor/stm32f407.svd site/vendor/stm32f401.svd site/vendor/stm32f411.svd site/vendor/stm32f429.svd 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'",
104
108
  "build:wasm:node": "wasm-pack build stm32-periph-wasm --target nodejs --out-dir pkg",
105
109
  "build:wasm:opt": "wasm-opt -O3 site/vendor/stm32_periph_wasm_bg.wasm -o site/vendor/stm32_periph_wasm_bg.wasm",
106
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"