What all the wires are for

Rusty Nail is a fantasy console built on an STM32 Nucleo board: it runs PICO-8 carts natively on the microcontroller, puts a game library on an HDMI monitor, takes a Bluetooth gamepad, plays music through a proper DAC, and loads games from physical cartridges. Previous posts covered single battles - the HDMI link bring-up, this morning's crisp-pixels rework - but I have never actually shown the thing around. This is the tour: what is on the screen, what is on the bench, and why the cartridge can never be plugged into a real Game Boy.

Every console screenshot below is a pixel-perfect capture pulled straight off the running hardware over the ST-LINK debug cable (the capture tool from this morning's post), not a photo of the monitor.

The little OS

The console boots into a cart library served off the LAN: a manifest plus box-art thumbnails, fetched by the WiFi bridge. The FAVES view is home:

Capture: the FAVES list - 307 carts in the library, box art on the right, 59fps in the corner

The full library is the same list unfiltered, 307 carts deep at the moment:

Capture: the full cart library, mid-scroll at 233 of 307

Search is a PICO-8-style on-screen keyboard with live results - three letters narrow 307 carts to 21:

Capture: search with CEL typed, 21 matches

The status strip top-right tells the hardware story at a glance: the cart badge lights up with a tiny colour-bar screen when a cartridge is docked, and the gamepad icon lights when the controller connects. Undock the cart and the badge drops to an outline:

Capture crop: cart badge and gamepad icon, both connected

Capture crop: the cart badge greyed after undocking

Undocking also gets a toast, because a console should say goodbye:

Capture: the library with a CART REMOVED toast

And as of this afternoon, docking a cartridge the library recognises shows the game's box art right on the insert dialog - the title is matched against the manifest (with a retry, because at boot the cart is recognised at 2.7 seconds and the manifest only lands at 3.8) and the art streams in through the same path the library grid uses:

Capture: the game cart connected dialog with Cattle Crisis box art

The machine room

None of that runs on a finished product; it runs on a Nucleo-H753ZI and four breakout boards, each with one job:

  • The Nucleo is the computer. A 480MHz Cortex-M7 running the whole console: the Lua VM executing the cart, the compositor, the audio synth, and every link below. No operating system, no allocator drama, one big Rust firmware.
  • An Adafruit Feather RP2040 DVI is the graphics card. The H7 streams the 128x128 framebuffer over SPI at 59fps; the RP2040 scans it out as real HDMI. That link - its ground-return lessons, its clock ceiling, this morning's palette-encode rework - has generated more posts than any other wire on the bench.
  • An ESP32 DevKitC is the network card and the gamepad receiver. WiFi for the manifest and box art, Bluetooth for the SN30 Pro, both multiplexed over one UART to the H7.
  • A PCM5102A is the sound card. I2S from the H7's SAI peripheral, headphone-quality output, and famously allergic to switching noise on neighbouring pins - it once vetoed an entire SPI port.

Wiring four breakouts to one Zio header field over jumpers was chaos, so the consolidation plan went through a home-made planner - an HTML grid of the actual perfboard, with every net drawn as a wire you can hover:

The protoboard planner: SAI1 audio, USART2 gamepad and SPI5 DVI link nets drawn across the CN9/CN10 grid

That plan became a KiCad board - a passive carrier that docks onto the Nucleo's headers and sockets all four modules, no soldering cleverness required:

KiCad: the daughterboard layout - Feather DVI and ESP32 sockets over the CN8/CN9/CN10 dock

And the end-state study goes further: one board with the bare chips - RP2040 plus its flash and crystal, ESP32-WROOM module, the DAC plus a headphone jack and even optical out - no breakouts at all. It is deliberately unrouted and unfabricated for now (TMDS and USB differential pairs want a four-layer stackup and more respect than a first spin would give them), but it is the picture the bench is slowly converging toward:

Render: the Rusty Nail Fantasy Console I/O Board rev A study - ESP32, RP2040, DAC, HDMI, USB-C, optical

For scale, this is what the console looks like from the couch - a bench photo for honesty, mid-development, glyph debug box and all:

Photo: Cattle Crisis title screen on the monitor

The cartridge

My favourite part of the whole machine. A Rusty Nail cartridge is a Game Boy cartridge shell containing no Game Boy whatsoever: inside is a W25Q128 SPI NOR flash - 16 MiB, which at PICO-8's 32 KiB ceiling is room for about five hundred games on one cart, absurd on purpose (the headroom is really for bigger cartridge formats later).

The board that makes it a cartridge rather than a chip on wires is Gekkio's GB-BRK-CART v4.0 - a passive breakout shaped exactly like a Game Boy cart PCB, bringing the 32-pin card edge out to a header. The Game Boy's parallel bus pins get remapped to SPI roles:

GB edge pinGB nameRusty Nail signal
1VCC3V3 (never 5V)
2PHI/CLKSPI SCK
3/WRSPI MOSI
4/RDSPI MISO
5/CSchip select
6A0reserved for the v4 save chip
7-31A1-A15, D0-D7, ...tied to ground on the slot side

Grounding the two dozen unused lines is not laziness - it is the whole trick. SPI over a connector lives or dies on its ground return, and an edge full of grounded spares gives every signal a return path next door. The shell needed its own engineering too - the breakout plus the flash module had to sit at the right height inside a real cart shell, pilot holes and all, before the 3D-printed holder could be trusted:

Fit-check plot: the W25Q128 module inside the cart shell outline, pilot holes ringed

Could the cart run on a real Game Boy? No, and not just as a disclaimer: the interfaces do not even intersect. A DMG expects a byte on a parallel bus a few hundred nanoseconds after presenting an address, every machine cycle; the W25Q128 says nothing until a master clocks a command and a 24-bit address into it serially. The breakout itself is fully passive - no mapper, no glue, no ROM. A real Game Boy sees grounded address lines and sulks; nothing is harmed, nothing boots. The rail is also 3V3 permanently (the flash is a 3.3V part), which is the other reason the slot and a real DMG must never exchange cartridges.

The console is its own cartridge writer. Pick any game in the library, choose FLASH, and it downloads the cart and burns it - erase, write, verify - straight from the couch:

Capture: search results with PLAY / FLASH / BACK on the footer

Capture: the loading screen fetching a cart for burning

Capture: the overwrite confirmation naming both games

The burn itself finishes faster than a screenshot can catch it - I tried three times today, four snapshots per attempt, and every frame landed either side of the write. On a 128-square-pixel screen the erase-write-verify of a 35 KB game is simply over before the next capture window opens. I have decided to be proud of that instead of annoyed.

What is next

The cartridge roadmap has one entry left: a small FRAM chip on the cart for save data - byte-writable, no sector erase, so a game can save without ceremony - plus maybe a cart identity for the dialog even when the game is not in the library. An SD slot for a purely offline library is sketched. And the 3D-printed cartridge holder just hit its first physical revision, which deserves photos of its own once it has earned them.

The console in this post is a breadboard and four breakouts pretending very hard to be a product. Some evenings, from the couch, with the pad in hand and a cart clicking into a printed shell, the pretence is completely convincing.