npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

stm32f4-emu

v1.7.0

Published

STM32F407 emulator: Rust CPU + peripheral model (WASM). Runs real Cortex-M4 firmware in Node or the browser.

Readme

STM32F4 Emulator (stm32f4-emu)

npm version npm downloads License: MIT CI Pages

Status: emulation complete. All 41 peripheral modules are Detailed (0 Partial), all five board maps verify green (37/37/72/73/59 presets), and the Ethernet descriptor layer (EDFE, TCH/TER + RCH/RER walks, RDES4, backoff probe) plus the Wireshark-validated pcap capture (a1b2c3d4, tcpdump-verified DHCP→TCP→HTTP) are in. What remains is product work, not emulation: npm publish, https gateway endpoint, VS Code packaging (see docs/progress-and-future.md).

An STM32F407 microcontroller emulator that runs real Cortex-M4 firmware. It combines a Rust CPU core (a WASM-native Thumb-2 interpreter with exact Cortex-M exception entry/return, including the VFPv4-SP FPU) with a Rust peripheral model (RCC, USART, GPIO, DMA, ETH, TIM, NVIC, ...) also compiled to WASM — so the whole machine runs headless in Node.js or a browser tab, with no SDL, no native deps, no hardware.

It ships three real networking firmwares (eth_http, eth_dhcp, eth_test) that do DHCP + TCP + HTTP against a simulated (or a real gVisor-backed) network, a 66-marker Ethernet feature matrix (eth_feat_test: PHY/MDIO, checksum offload, hash/perfect/SA/DA filtering, VLAN, PTP, WOL, wire pacing, deferral/collisions, pause frames, MMC, RBUS — see NETWORKING.md) plus real LwIP 2.2.1 (lwip_demo) and MII/RMII pin mirrors (eth_pins_test), a browser demo, and a publishable npm package.

Live demo

The browser demo deploys to GitHub Pages:

https://danish9661.github.io/STM32F4-emulator/

A single console page that starts idle — nothing runs until you pick a firmware: a preset dropdown with 223 bundled binaries (network demos, a bare-metal LED blinker, peripheral/crypto/UART/SPI test binaries), custom firmware upload (.bin, Intel .hex, .elf — with loadable RAM segments and symbols — plus .map for a symbol table), Run/Stop/Reset, a gateway URL field to connect a real network stack (openhw-gw + gVisor) with a scripted network (netsim) as fallback, a live UART terminal (with UART RX input — type into the console and the firmware reads it; newline characters excluded per HTML spec, see AGENTS.md §11), GPIO pin readout for banks A–E, and key peripheral registers. Interrupt-driven firmware (rx_interrupt_test, rx_crypto_test) gets inline guest-IRQ delivery; polling firmware (the ETH demos) never uses it. For automation, a preset can auto-boot via the URL: ?fw=eth_http, ?fw=blinky, ?fw=crypto_test, …

DOOM (in the browser)

site/doom.html runs DOOM 1 shareware (doomgeneric, ported to the emulated F407) at the full 35 fps in a headless-Chrome-verified browser page — playable: at ~1M guest instructions per rendered frame, 35 fps needs ~32 MIPS and the Rust core delivers ~65 (details and the measurements in AGENTS.md §16/§22). Because the guest mixes one frame of audio per rendered frame, sound below 35 fps plays slightly slow and pitched-down rather than breaking up — the worklet rate-matches instead of inserting gaps, and the stats line reports it (audio 0.72x). The page: 320×200 CMAP256 framebuffer, WASD + arrows + Ctrl/Space/Shift + F-keys, I2S audio out (mixer → AudioWorklet at 11025 Hz), and a realtime lock that paces the guest to wall time. Save/load works: the firmware stages savegames to an EXTRAM region (2 slots × 256 KB at 0xC0080000) and doom.js mirrors them to localStorage['doom-save-N'], so F6 (quick-save), F2 (save menu), F9 (quick-load, 'y' to confirm) and F3 (load menu) survive page reloads.

  • Controls: move W/S/A/D + arrows · strafe Shift · fire Ctrl · use Space · menu Enter/Esc · save F2/F6 · load F3/F9 · F1/F10/F11/F12.
  • Boot → menu → gameplay verified end-to-end by node site/test_doom_wasm.mjs (boot markers, menu navigation to E1M1, palette + framebuffer, W-move + turn, audio, save → SAVE ok slot=0 → load handshake).
  • site/doom1.wad is the 4.2 MB shareware WAD; the firmware never reads it from flash — the driver loads it into 8 MB of extra_mem (0xB8000000).

Quickstart

# browser demo locally
npm run serve                  # then open http://127.0.0.1:8123

# node end-to-end flow test (boot -> DHCP -> TCP -> HTTP, 2 rounds)
npm test                       # == node site/test_flow.mjs

# websocket bridge: headless Node serves the emulator, browser is a thin UI
npm run bridge -- firmware/blinky/blinky.bin --port 8234
# then open http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234

# gateway-backed run: firmware talks to a REAL network stack (gVisor)
cd stm32-periph-wasm/pkg
node cli.mjs ../../firmware/eth_http/eth_http.bin 10000000 \
  --gateway --config=../../firmware/eth_http/config.yaml
# (requires an HTTP server at 127.0.0.1:8092; see AGENTS.md §10)

Use it as a library (npm package)

npm pack produces stm32f4-emu — the full emulator as a library, with all WASM assets, the SVD register map, and the firmware binaries bundled:

import { createSTM32F407, createNetSim, FIRMWARES } from 'stm32f4-emu';

const netsim = createNetSim();                        // canned DHCP/TCP/HTTP peer
const emu = await createSTM32F407({
    firmware: FIRMWARES.eth_http.bytes,               // any STM32F4 firmware blob
    onTx: (frame) => {
        for (const reply of netsim.onTx(frame)) emu.injectFrame(reply);
    },
});

emu.step(100000);              // run up to 100k instructions
const uart = emu.drainUart();  // collect UART output
emu.injectFrame(packetBytes);  // inject an Ethernet frame

See site/test_flow.mjs and the exports in index.mjs for the full API (decodeFirmware, createEmulator, createNetSim re-exports).

Attach virtual hardware (LEDs, buttons, PWM readers, analog sensors, custom SPI/I2C devices) to pins and buses with the component API — emu.pin()/emu.watchPin()/emu.setAdcChannel() plus the LED/Button/Pwm/Potentiometer/I2cRegisterDevice components, or ext_devices.spiDevices/i2cDevices for your own bus protocol. See docs/components.md.

For platform integration (Wokwi/OpenHW/Velxio) use the STM32F4 facade — factories, GPIO/USART/SPI/I2C objects, polled event callbacks, DMA controller, live displays, SWD/JTAG/power/probe helpers. See docs/facade.md.

Drive it from an AI agent (MCP)

mcp/server.mjs exposes the emulator over the Model Context Protocol — boot firmware, step, read UART, poke pins, inject ADC values, and inspect registers as tools an MCP client (Claude Code, Claude Desktop) can call:

npm install && npm run mcp

The MCP SDK is an optional peer dependency, so installing this package as a library stays dependency-free; only MCP users need npm i @modelcontextprotocol/sdk zod. See docs/mcp.md for the tool reference and client config.

WebSocket bridge (headless Node ↔ browser UI)

A binary WebSocket protocol so the browser console can drive the emulator running headlessly in Node — all WASM execution stays in Node, the browser is a thin UI. Zero impact on the existing local WASM path:

# 1. Start the bridge in Node (serves the emulator over WS on port 8234)
node site/ws-bridge.mjs firmware/eth_http/eth_http.bin --port 8234

# 2. Open the browser console with the bridge URL param
open "http://127.0.0.1:8123/console.html?bridge=ws://127.0.0.1:8234"

The RemoteEmu adapter (site/remote-emu.js) is a drop-in replacement for the local emu object — same step()/drainUart()/read32() API, all proxied over binary WebSocket. Device stubs (OLED/TFT/etc.) run in Node and are not visible to browser JS. Without ?fw=, the page boots whatever firmware the bridge was started with; with ?fw=blinky&bridge=ws://…, the browser sends the firmware image over the bridge.

See AGENTS.md §20 for the full binary protocol reference.

Firmwares

| Firmware | What it does | Success marker | |---|---|---| | eth_http/ | DHCP + TCP client + HTTP GET + prints the response | TCP connected, === HTTP <len>b === | | eth_dhcp/ | Loops DHCP Discover/Offer/Request/Ack | DHCP SUCCESS | | eth_test/ | Raw ETH TX/RX self-test | ETH Test: done | | eth_feat_test/ | 66-marker Ethernet feature matrix (netsim, F407 + F429) | FEAT Test: done (all 66 markers, FAIL/TIMEOUT anti-markers) | | lwip_demo/ | Real LwIP 2.2.1 (DHCP→DNS→TCP echo→TCP server→UDP echo) | LWIP DEMO DONE | | eth_pins_test/ | MII/RMII pin-level mirrors (TX_EN/CRS_DV/RXD/COL/MDIO/MDC) | PINS ALL PASS | | blinky/ | No ethernet — LED blinker on GPIOA PA5 + UART tick counter | tick N LED=ON/OFF | | doom/ | DOOM 1 shareware (doomgeneric F407 port, browser page site/doom.html) | node site/test_doom_wasm.mjs (boot + menu + gameplay + save/load) |

Plus 17 more test binaries (crypto_test, hal_test, timer_test, periph_test, echo_test, blink_serial, rx_interrupt_test, spi_tft_test, …) from the *_test/ directories — all boot headless and print a banner over UART (probe: node site/probe_firmwares.mjs).

All are bare-metal (no RTOS), built with the Arduino core's arm-none-eabi-gcc, and driven purely through memory-mapped registers — the same firmware binaries run on the emulator and on real silicon. That equivalence is for logic, not timing: timers/ADC/RNG/RTC/watchdogs are instruction-count driven rather than wall-clock driven, so firmware relying on real-time behavior (PWM frequency matching real hardware, watchdog timeouts close to spec) will diverge — see docs/progress-and-future.md#known-limitations.

Architecture

firmware .bin ──► Rust WASM CPU (Thumb-2 + NVIC/exceptions)
                          │
              peripheral MMIO ──► Rust peripheral model (WASM)
                                          RCC USART GPIO DMA ETH TIM NVIC
                          │
                    UART out / ETH TX frames ──► driver (cli.mjs / site/emulator.js)
                          │
                    RX frames injected (netsim, or real gVisor gateway)
  • CPU: a pure-Rust Cortex-M4 Thumb-2 interpreter compiled to WASM; peripheral accesses call straight into the Rust model, and guest IRQs (SysTick, ETH, USART, SVC/PendSV) are delivered inline with exact exception stacking — no native deps, no JIT, no hooks.
  • Peripherals: a wasm-bindgen crate (stm32-periph-wasm/); registers and bit fields come from the vendor SVD (site/vendor/stm32f407.svd).
  • Ethernet: TX is captured from the DMA descriptors (OWN/FS/LS poll demand); RX frames are delivered head-only with FS+LS status, IPHCE/PCE checksum status, and PTP snapshots for event messages. The full MAC register map is cross-checked against CMSIS stm32f407xx.h (DMASR/ DMAIER/MACFFR/MACPMTCTL positions — see NETWORKING.md §6 for the audit). Optionally, a Go gateway (openhw-local-gateway/) with a gVisor network stack makes the firmware talk to a real network: node cli.mjs <fw.bin> <inst> --gateway.
  • Browser build (site/): same modules as ESM; site/emulator.js is an import-free universal factory; site/netsim.js is a canned network peer; site/loaders.js parses Intel HEX / ELF32 / linker-map files. The demo page's gateway mode uses the exact same protocol as the Node CLI: raw Ethernet frames over a WebSocket (/api/network-gateway), RESET control message on reboot.

Repository layout

├── site/                    Single-page console + universal emulator factory
│   ├── index.html, app.js   Console UI (UART, presets, loaders, gateway, GPIO)
│   ├── doom.html, doom.js   DOOM page (gameplay + save/load via localStorage)
│   ├── emulator.js          Import-free emulator factory (Node + browser)
│   ├── components.js        Virtual components (LED/Button/Pwm/Potentiometer/...)
│   ├── netsim.js            Canned DHCP/TCP/HTTP network peer (fallback)
│   ├── loaders.js           Intel HEX / ELF32 / linker-map parsers
│   ├── test_flow.mjs        Node E2E flow test (npm test)
│   ├── test_blinky.mjs      Node blinky GPIO test (npm test)
│   ├── test_rx_interrupt.mjs Node UART-interrupt test (npm test)
│   ├── test_component_*.mjs Component-API tests, one firmware each (npm test)
│   ├── test_doom_wasm.mjs  Node DOOM boot/menu/gameplay/save test
│   └── vendor/              Browser WASM build (CPU + peripherals), SVD
├── index.mjs, package.json  npm package entry (stm32f4-emu)
├── mcp/                     MCP server (drive the emulator from an AI agent)
├── .github/workflows/       CI (Linux/Windows/macOS test matrix) + Pages deploy
├── tools/make_firmware.mjs  Regenerates site/firmware.js from firmware/*/.bin
├── stm32-periph-wasm/       Rust peripheral model (WASM build + pkg/)
├── firmware/eth_http/ eth_dhcp/ eth_test/   Sample network firmwares + configs
├── firmware/doom/           DOOM 1 port (doomgeneric f407 target + WAD path)
├── openhw-local-gateway/    Go gateway (gVisor network stack)
├── scripts/verify_ethernet.sh   Regression runner for all three firmwares
├── hardware/monox/, hardware/saturn/  Upstream printer firmwares (heritage)
└── AGENTS.md                Full architecture, build steps, runbook

Building from source

# Rust peripheral model (Node target; browser target goes to site/vendor/)
cd stm32-periph-wasm && wasm-pack build --release --target nodejs

# firmware (bare-metal Makefiles; toolchain from the Arduino core)
TOOLCHAIN="$HOME/.arduino15/packages/STMicroelectronics/tools/xpack-arm-none-eabi-gcc/14.2.1-1.1/bin/arm-none-eabi-" \
  make -C firmware/eth_http   # also eth_dhcp, eth_test (all firmware lives in firmware/)

# (no native SDL emulator in this tree — the headless WASM path above is the only backend)

wasm-pack writes site/vendor/.gitignore containing * after a browser rebuild — delete it so the vendor assets stay tracked/committed.

Testing

  • npm test — flow test (site/test_flow.mjs) + Ethernet mock harnesses (site/test_eth_mock_model.mjs: 18 checks, fake bindings + real driver; site/test_eth_mock_consumer.mjs: 44 checks, fake firmware + real model)
    • blinky test (site/test_blinky.mjs) + interrupt-UART test (site/test_rx_interrupt.mjs)
    • component-API tests (site/test_component_{led,button,pwm,i2cregfile}.mjs, each against real firmware — LED/blinky, Button/exti_test, Pwm/buzzer_test, I2cRegisterDevice/rtc_test), exit 0 = all PASS. Each test file boots exactly one firmware in its own node process — createEmulator() instances aren't safe to reuse across different firmware in the same process (see docs/components.md). The same suite runs in CI on ubuntu-latest/windows-latest/macos-latest (.github/workflows/ci.yml) on every push.
  • node site/test_doom_wasm.mjs — DOOM boot → menu → E1M1 gameplay + save/load (see the DOOM section above).
  • scripts/verify_ethernet.sh [max_inst] — runs all three firmwares through the gateway, asserts the success markers and 0 TCP fail.
  • Soak-tested: 200M-instruction gateway runs with 1000+ consecutive TCP rounds, 0 failures (details in AGENTS.md §10).

Documentation

  • site/about.html — in-repo About page: what it is, architecture, featured firmwares, and how to use it (CLI / browser / Node API / MCP).
  • docs/architecture.md — how the emulator is put together (CPU, peripheral model, drivers, ETH flow, interrupts).
  • docs/peripherals.md — all 41 peripherals and the level each is implemented to, plus external devices and known gaps.
  • docs/usage.md — CLI, browser, and npm-library usage, config files, env vars, building.
  • docs/components.md — attach virtual LEDs, buttons, PWM/analog sensors, and custom SPI/I2C devices to pins/buses (rp2040js-style component API).
  • docs/mcp.md — the MCP server: drive the emulator as tools from Claude Code / Claude Desktop or any MCP client.
  • docs/benchmarks.md — throughput numbers, soak results, tunables.
  • docs/progress-and-future.md — status, known limitations, roadmap.

License & Credits

  • License: MIT. See LICENSE.
  • Heritage: clean-room Rust CPU core + peripheral model, JS driver, and npm package written for this project (no upstream emulator code remains in the tree — the legacy native/SDL prototype was removed; see firmware/doom/NOTICE.md for the one GPL-walled demo exception). The networking stack, browser demo, virtual components API, MCP server, and npm package are new work built on that base.
  • DOOM demo (GPL-walled, not in npm): the doom preset + site/doom.* page run a doomgeneric port (GPL-2.0-or-later engine, firmware/doom/engine, by id Software / Simon Howard / Ozkan Sezgin — see doomgeneric); only the F407 glue in firmware/doom/f407 is original. The doom blob + WAD ship in the repo demo only, never in the npm tarball.