STM32F4 Emulator

Run real STM32F407 firmware in Node or the browser

About

stm32f4-emu runs real compiled STM32F407 firmware — not a reimplementation — by pairing a CPU emulator with a model of the on-chip peripherals. Boot a binary, watch its UART, poke GPIO and registers, drive it from scripts, a web UI, or an MCP client.

The emulator console running the blinky firmware: UART terminal printing tick lines, firmware and machine-state panels
The console running blinky — this is what the emulator looks like in use.

How it fits together

Three parts, one width-correct call per register access.

Peripherals modeled
41 / 41
Firmware builds
223
Chips
F401 · F411 · F407 · VE · F429
DOOM
35 fps

Full design: docs/architecture.md. Per-peripheral matrix: docs/peripherals.md.

What runs on it

A few of the 223 builds.

FirmwareWhat it shows
eth_httpDHCP → TCP handshake → HTTP GET/response, through a real gVisor stack or canned netsim.
blinkyBare-metal blink + UART banner; toggles PA5 so the GPIO panel updates live.
can_testTwo-node CAN bus: loopback TX, arbitration (lower ID wins), filter-gated delivery.
freertos_testFreeRTOS scheduler: TIM3 ISR → xSemaphoreGiveFromISR → PendSV context switch.
rtc_testDS3231 I2C RTC: set time, read-back, temperature.
fpu_testVFPv4-SP FPU: real GCC hard-float output with bit-exact result checks.
mpu_testMPU enforcement: regions + MemManage faults with exact MMFSR/MMFAR.
usb_cdc_testUSB OTG FS device: CDC-ACM enumeration + bulk echo via a scripted host.
oled_test / tft_test / buzzer_testVirtual peripherals over I2C/SPI/TIM (SSD1306, ILI9341, PWM).
doomdoomgeneric F407 port — DOOM 1 shareware in a Web Worker. Open doom.html.
DOOM 1 shareware running on the emulated STM32F407: shotgun in a corridor, YOU GOT THE SHOTGUN banner, status bar with ammo health armor
DOOM on the emulated F407 — framebuffer, audio, savegames, all in the tab.

Use it

Three doors.

Headless CLI

# boot a firmware and stream its UART to stdout
node cli.mjs firmware.bin --inst 20000000

# load an ELF, trace peripheral register accesses to stderr
node cli.mjs app.elf --verbose

node cli.mjs --help      # usage + all flags
node cli.mjs --version   # version

Flags: --inst <N> instruction budget, --format auto|bin|hex|elf, --verbose register trace, --help, --version.

Browser console

# from the repo root
python3 -m http.server 8123 --directory site
# then open http://localhost:8123/console.html

Pick a firmware preset, click Run, watch the UART terminal — plus a live GPIO grid, key peripheral registers, device panels, and real Ethernet with a gateway URL.

Node library

// npm install stm32f4-emu  (or use the local checkout)
import { createSTM32F407 } from 'stm32f4-emu';
import { readFileSync } from 'node:fs';

const fw = new Uint8Array(readFileSync('firmware.bin'));
const emu = await createSTM32F407({ firmware: fw });

for (let i = 0; i < 200; i++) {
  emu.step(100000);                 // run up to 100k instructions
  const uart = emu.drainUart();
  if (uart.length) process.stdout.write(uart.toString());
}
console.log('PC=', emu.read32(0x08000000 + 4).toString(16));

Key handle methods: step(n), run(n), drainUart(), read32/write32/read16/write16/read8/write8, close(). TypeScript types ship in index.d.ts.

MCP server

# optional peer dep:
npm install @modelcontextprotocol/sdk zod
npx stm32f4-mcp          # speaks MCP over stdio

Exposes load/step/UART/pins/ADC/registers/memory/component tools to an MCP client (Claude Code, Claude Desktop). See docs/mcp.md.

Learn more