Page 1 of 1

S3-MSX-PC: Bare-metal MSX2+ emulator with VGA output, USB keyboard & stereo audio on ESP32-S3

Posted: Thu Jun 11, 2026 2:19 pm
by IvanSvarkovsky
Hello ESP32 community!

I'm excited to share a project I've been building for the past several months — a deeply optimized MSX2+ emulator that turns a single ESP32-S3 into a full-fledged 1980s home computer.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WHAT IS IT?

S3-MSX-PC emulates the MSX, MSX2, and MSX2+ computer standards on ESP32-S3, outputting to a real VGA monitor with a USB keyboard and stereo PDM audio. No external video chip. No I2S codec. Just the ESP32-S3, a handful of resistors and capacitors, and your childhood memories.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

HARDWARE SETUP (minimalist, no extra ICs)

- ESP32-S3-WROOM-1-N16R8 — main SoC (16 MB Flash, 8 MB PSRAM), $4–12
- Resistors 400Ω 1% (3 pcs) — VGA R-2R ladder high bits
- Resistors 800Ω 1% (3 pcs) — VGA R-2R ladder low bits
- Resistors 330–400Ω (4 pcs) — PDM audio RC filter
- Capacitors 22–40 nF (2 pcs) — PDM audio filter
- Capacitors 47–100 nF (2 pcs) — PDM audio filter
- Capacitors 1–10 µF electrolytic (2 pcs) — DC blocking for audio out
- VGA connector DB15 female — video output
- USB-A connector female — keyboard input

Total BOM cost: under $2 in passives + $4–12 for the ESP32-S3 board.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

VIDEO: VGA 640×480@60Hz VIA LCD_CAM + R-2R

Instead of a dedicated video DAC, I use 6 GPIO pins driving a resistor ladder (2 bits per R/G/B channel = 64 colors). The ESP32-S3's LCD_CAM peripheral DMA-blasts pixels directly to the monitor.

Key trick: lcd_byte_order=1 flag gives zero-CPU-cost byte swapping. The palette is pre-shifted during init to map directly to GPIO pin states — no runtime bit manipulation.

GPIO mapping:
- HSync → GPIO 13
- VSync → GPIO 14
- R0/R1 → GPIO 1 / GPIO 2
- G0/G1 → GPIO 4 / GPIO 5
- B0/B1 → GPIO 6 / GPIO 7

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AUDIO: STEREO PDM WITH MULTI-STAGE RC FILTER

Two GPIO pins (10 and 11) output high-frequency Pulse-Density Modulated (PDM) bitstreams. A 2nd-order RC low-pass filter smooths the PDM signal into a pulse-width equivalent, which after DC blocking capacitors becomes a line-level signal suitable for headphones or an amplifier.

No I2S codec, no external DAC — just GPIO toggling and passive filtering.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

USB HOST: KEYBOARD INPUT

USB-A connector wired to GPIO 19 (D-) and GPIO 20 (D+), with 5V VBUS from the board's own regulator. Plug-and-play USB keyboards, ~2–4 ms input latency (interrupt-driven, software debounce bypassed).

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CORE OPTIMIZATIONS FOR ESP32-S3 (XTENSA LX7)

1. Z80 CPU: COMPUTED GOTOS
The standard switch/case opcode dispatcher destroys the Xtensa branch predictor. I replaced it with threaded code (computed gotos), pinning PC and ICount to physical 32-bit registers. This alone gave a massive performance boost.

2. PSRAM LATENCY ELIMINATION
fMSX uses ~400 KB of lookup tables in PSRAM. I replaced them with 1-cycle integer bit-shifts. The core 11 KB tables are locked into DRAM_ATTR to avoid PSRAM stalls entirely.

3. ZERO-HEAP SAVE STATE COMPRESSION
Standard zlib crashes when compressing 4.4 MB machine states. I wrote a custom streaming LZ77 ("Delta-Stride LZ") that understands MSX VRAM geometry (128/256-byte strides), performs vertical rep-match XOR-delta, and streams directly to SD card using exactly 0 bytes of heap RAM.

4. SMART DISK AUTO-SAVE
Disk writes use a dirty-flag system: the 720 KB DSK image lives in PSRAM, all writes happen in RAM, and the SD card is touched only on clean exit/reset. This minimizes SD card wear significantly.

5. Z80 TURBO MODE
Dynamic 2x overclocking (~8.23 MHz equivalent) for computational tasks, while preserving exact hardware timing for I/O ports and VDP polling loops.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SOFTWARE ARCHITECTURE

- Emulation core: fMSX 6.0 (MSX1 / MSX2 / MSX2+)
- Framework: ESP-IDF v5.4.4
- File manager: Wi-Fi AP (retro-go derived) — upload ROMs/DSKs via browser
- Save states: SRAM + instant snapshot with streaming compression
- VGM tracker: PSG, SCC, OPLL multi-channel playback

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MEDIA COVERAGE

Featured on CNX-Software:
https://www.cnx-software.com/2026/06/11 ... mizations/

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RESOURCES

- GitHub (source, schematic, binaries, technical deep dive):
https://github.com/Svarkovsky/s3-msx-pc

- Demo videos: check the GitHub README — output_.mp4, output2.mp4, output3r.mp4

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WHO IS THIS FOR?

- Retro computing enthusiasts who want real VGA + USB keyboard experience
- Embedded developers interested in ESP32-S3 performance tuning
- Makers looking for a minimalist DIY project with no specialized chips

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ACKNOWLEDGEMENTS

This project was inspired by retro-go by Alex Duchesne (@ducalex). The Wi-Fi file manager concept and launcher architecture served as invaluable starting points. The rendering pipeline, audio subsystem, and input layer were fundamentally redesigned for the desktop MSX experience.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

I'm happy to answer technical questions — especially about LCD_CAM configuration, PSRAM optimization strategies, or the Z80 threaded code implementation.