STM32F4 Emulator

Run real STM32F407 firmware in Node or the browser

Overview

stm32f4-emu is an emulator for the STM32F407 (ARM Cortex-M4) microcontroller. It runs real compiled firmware — not a reimplementation — by pairing a CPU emulator with a cycle-accurate-enough model of the on-chip peripherals. You can boot a binary, watch its UART, poke GPIO and registers, and drive it from scripts, a web UI, or an MCP client.

The two halves are connected by MMIO memory hooks: every read/write the firmware makes to a peripheral register is intercepted and answered by the peripheral model, so the guest sees exactly what real silicon would present.

Architecture

Memory-hook protocol: MMIO range → periph_read/write → model → value written back into guest memory.

See docs/architecture.md and docs/peripherals.md for the full design and a per-peripheral implementation matrix.

Featured firmwares

FirmwareWhat it shows
eth_httpDHCP → TCP handshake → HTTP GET/response, driven through a real gVisor network stack (or a canned netsim). The flagship networking demo.
blinkyBare-metal blink + UART banner; toggles PA5 so you can watch the GPIO panel update live.
can_testTwo-node CAN bus: loopback TX, arbitration (lower ID wins), and filter-gated delivery.
freertos_testFreeRTOS scheduler: TIM3 ISR → xSemaphoreGiveFromISR → PendSV context switch to a higher-priority task.
rtc_testDS3231 I2C RTC: set time, read-back, temperature.
oled_test / tft_test / buzzer_test / audio_play_testVirtual peripherals over I2C/SPI/TIM/I2S (SSD1306, ILI9341, PWM, I2S capture).
doomdoomgeneric F407 port — DOOM 1 shareware running in a Web Worker. Open doom.html.

How to use

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/index.html

Pick a firmware preset from the dropdown, click Run, and watch the UART terminal. The console also shows a live GPIO grid, key peripheral registers, device panels (OLED/TFT/buzzer/RTC), and — with a gateway URL — real Ethernet.

Node library API

// 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