Implements the firmware-disasm-confirmed Firestorm box protocol in the MatrixPortal replica (ESC X outline / ESC x mode-fill via a BOXOP operand collector, plus the ESC Y region op), updates FIRMWARE.md with the findings, and adds FS-plasma-bench.ps1 -- a simulated MW4 serial stream for benching the display without a pod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
158 lines
7.0 KiB
Markdown
158 lines
7.0 KiB
Markdown
# Plasma display replica — Adafruit Matrix Portal S3 + HUB75
|
||
|
||
A hardware replacement for the failing Babcock **PD01D221** cockpit plasma
|
||
display ([../README.md](../README.md), [../FIRMWARE.md](../FIRMWARE.md)). A
|
||
modern microcontroller reads the same serial command stream the game sends and
|
||
renders it to a modern LED matrix — a drop-in from the host's point of view,
|
||
with none of the plasma physics or high voltage.
|
||
|
||
The firmware's command parser and fonts are **ported verbatim from vRIO's
|
||
vPLASMA emulator** (`src/VPlasma.Core` in the vRIO repo), which is the
|
||
reference oracle. Feed the replica and vPLASMA the same byte stream and they
|
||
produce the same frame.
|
||
|
||
## Bill of materials
|
||
|
||
| Part | Notes |
|
||
|------|-------|
|
||
| **Adafruit Matrix Portal S3** | ESP32-S3 controller; plugs into the HUB75 header; native USB-C = the virtual COM port |
|
||
| **2 × Adafruit 64×32 RGB LED Matrix** (P-pitch to taste; 1/16 scan) | chained → **128×32**, the panel's native resolution |
|
||
| **5 V power supply, ≥ 4 A** | the panels are the load; USB cannot power them |
|
||
| USB-C cable | data (and logic power) from the host PC |
|
||
|
||
An amber-leaning pitch and diffuser best mimic the neon-orange plasma; the
|
||
firmware already renders in orange (`255,96,0` full / `110,40,0` half).
|
||
|
||
## Wiring
|
||
|
||
1. **Chain the panels**: panel A `OUT` → panel B `IN`, left-to-right, so the
|
||
pair reads as one 128-wide canvas (x 0–63 = panel A, 64–127 = panel B). If
|
||
the image comes out swapped, reverse the chain order.
|
||
2. **Mount the Matrix Portal S3** onto panel A's `IN` HUB75 header.
|
||
3. **Power**: 5 V ≥ 4 A into the Matrix Portal's screw terminals; run the
|
||
panels' power pigtails from the same 5 V. USB-C carries data (and powers the
|
||
S3 logic) — do **not** rely on USB for panel current.
|
||
|
||
## Build & flash
|
||
|
||
Arduino IDE (or `arduino-cli`):
|
||
|
||
1. **Boards Manager** → install **esp32** (Espressif). Select board **"Adafruit
|
||
Matrix Portal S3"**. Set **USB CDC On Boot: Enabled** (so `Serial` is the
|
||
USB port the game opens).
|
||
2. **Library Manager** → install **Adafruit Protomatter** (pulls in Adafruit
|
||
GFX / BusIO).
|
||
3. Open `MatrixPortalPlasma/MatrixPortalPlasma.ino` (keep `PlasmaDisplay.*`,
|
||
`plasma_fonts.h`, `demo_screens.h` beside it) and Upload.
|
||
|
||
The HUB75 pin arrays at the top of the `.ino` are Adafruit's published Matrix
|
||
Portal S3 values — except the G and B pins, which are exchanged because these
|
||
panels have the green and blue LEDs swapped (manufacturer-confirmed; symptom
|
||
was orange rendering as purple). If a different panel garbles, verify the
|
||
arrays against your installed Protomatter version.
|
||
|
||
## Using it with the game
|
||
|
||
The Matrix Portal enumerates as a **USB CDC COM port**. In Windows Device
|
||
Manager you can pin it to the COM number the host expects. Point the game's
|
||
plasma output at it — under the DOSBox-X fork:
|
||
|
||
```
|
||
serial2 = directserial realport:COMx
|
||
```
|
||
|
||
Baud is cosmetic over USB CDC (the `9600` line-coding is accepted as a no-op),
|
||
so the display keeps up regardless. The link is one-way (the game writes, the
|
||
display listens), exactly as the cockpit drove the real panel.
|
||
|
||
## No-host testing (onboard buttons)
|
||
|
||
- **UP** — toggle the built-in **firmware demonstration** (the real 10-screen
|
||
PLASMADOT demo, `demo_screens.h`, extracted from the ROM).
|
||
- **DOWN** — toggle the **panel test**: cycles diagnostic patterns (solid,
|
||
border+grid, horizontal/vertical stripes, checkerboard) to expose dead dots
|
||
and addressing faults — the multi-pattern sequence the real firmware runs.
|
||
- **Power-on** briefly lights every dot (confirms both panels), then clears.
|
||
|
||
## Files
|
||
|
||
| File | |
|
||
|------|--|
|
||
| `MatrixPortalPlasma/MatrixPortalPlasma.ino` | sketch: Protomatter init, USB serial, render loop, buttons |
|
||
| `MatrixPortalPlasma/PlasmaDisplay.h/.cpp` | the PD01D221 parser + 128×32 framebuffer — a C++ port of `VPlasmaDevice` |
|
||
| `MatrixPortalPlasma/plasma_fonts.h` | the 8 real ROM fonts (PROGMEM), generated from `tms27pc512.BIN` |
|
||
| `MatrixPortalPlasma/demo_screens.h` | the 10 firmware demo screens (PROGMEM) |
|
||
|
||
## Keeping it faithful — the differential test
|
||
|
||
`PlasmaDisplay` is a line-for-line port of `VPlasmaDevice`; keep the two in
|
||
sync (same commands, fonts, orientation/`plot` mapping). Validate the replica
|
||
by sending **identical byte streams** to the replica, to vPLASMA, and to the
|
||
real panel, then comparing the glass.
|
||
|
||
The **`VPlasma.Wire`** tool (in the vRIO repo: `tools/VPlasma.Wire`,
|
||
`dotnet run --project tools/VPlasma.Wire -- …` from that repo's root)
|
||
drives this:
|
||
|
||
```sh
|
||
# 1) Build a repeatable stream (or capture a real one — see below).
|
||
VPlasma.Wire synth --kind demo --out demo.bin
|
||
|
||
# 2) The vPLASMA golden image (pixel-exact reference PNG).
|
||
VPlasma.Wire render --in demo.bin --out demo-golden.png --scale 8
|
||
|
||
# 3) Replay the SAME bytes to each target, and photograph the glass:
|
||
VPlasma.Wire replay --in demo.bin --port COM3 # the real panel (RS-232)
|
||
VPlasma.Wire replay --in demo.bin --port COM7 # the Matrix Portal (USB-CDC)
|
||
```
|
||
|
||
Compare the two photos against `demo-golden.png`. `synth` kinds: `selftest`,
|
||
`demo`, `banner`, `charset`. `render` takes `--orient v` for vertical.
|
||
|
||
**Capture a real session** to build a test corpus non-intrusively — insert the
|
||
tool in the path (point the game at `COM12`, tool tees on to the real display
|
||
on `COM3`):
|
||
|
||
```sh
|
||
VPlasma.Wire capture --port COM12 --out session.bin --tee COM3
|
||
```
|
||
|
||
Then `render`/`replay` `session.bin` like any other stream.
|
||
|
||
**Auto-compare** a photo (cropped to the glass) against the golden image, or
|
||
two streams against each other — each is reduced to 128×32 and diffed:
|
||
|
||
```sh
|
||
VPlasma.Wire diff --a demo-golden.png --b panel-photo.png --out delta.png
|
||
# prints "differing dots=N/4096 match=XX%", writes a red-on-mismatch image,
|
||
# exits 1 if they differ. Inputs may be .bin (ESC P) / .png / .txt (bitmap=).
|
||
```
|
||
|
||
### Authoring plasma content (from TeslaSuite's plasma tools)
|
||
|
||
Generate display content from text or images and push it over the wire:
|
||
|
||
```sh
|
||
# Text in a Windows font, auto-sized to the panel → preview PNG + ESC P + hex:
|
||
VPlasma.Wire text --text "ALERT 12" --font "Arial" --png a.png --bin a.bin --hex a.txt
|
||
VPlasma.Wire replay --in a.bin --port COM7 # show it on the replica
|
||
|
||
# An image → the ESC P wire stream (and/or the game's bitmap= encoding):
|
||
VPlasma.Wire encode --in logo.png --bin logo.bin --hex logo.txt
|
||
|
||
# A bitmap= encoding → a preview PNG (and/or ESC P):
|
||
VPlasma.Wire decode --in logo.txt --png logo.png
|
||
```
|
||
|
||
Still deferred (as in vPLASMA, documented in `../FIRMWARE.md`): the 10
|
||
double-buffered pages (`ESC I`/`ESC i` are consumed but single-page) and the
|
||
vector-graphics primitives (`ESC A`–`F`).
|
||
|
||
**Ahead of vPLASMA (2026-07-22):** the box family is implemented here from the
|
||
firmware disassembly — `ESC X l t r b` (box outline) and `ESC x m l t r b`
|
||
(region blank/fill/invert), the commands **BattleTech: Firestorm**'s `CPlasma`
|
||
driver draws its score panel with; `ESC Y` (screen blit) is validated and
|
||
consumed as a no-op. vPLASMA needs the same additions before the differential
|
||
test covers Firestorm streams — until then `../FS-plasma-bench.ps1` is the
|
||
Firestorm exerciser.
|