- Connection: drop the hardwired COM12; replicate vRIO's endpoint picker — a combo of pipe:vplasma (the DOSBox-X namedpipe backend) + the COM ports, with Rescan and Open/Close. Nothing opens automatically; baud straps drive a COM open. - Demo (jumper 6) now runs the REAL firmware demonstration, not the vPLASMA self-test. The 10 demo screens are extracted verbatim from the ROM demo pointer table ($8000) into PlasmaFirmwareDemo.cs and looped through the parser. Added ESC I / ESC i (draw/display page select) as 1-operand commands — consumed but not acted on in the single-page model — so the demo's page commands don't desync the stream. - Orientation (jumper 4 / PD5) polarity fixed: unstrapped = horizontal 128x32 (the normal cockpit setup, now the default), installed = vertical. Verified: 29 unit tests pass (2 new: demo replay, page-select operand); the real demo screens render with the correct text/positioning/fonts, and the picker lists pipe:vplasma + COM ports with no auto-open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.8 KiB
PD01D221 firmware analysis (tms27pc512.BIN)
Reverse-engineering notes for the dumped controller firmware — the 64 KB TI TMS27PC512 EPROM (U3) from the Babcock PD01D221. This is the authoritative source for the display's command set, and it feeds both vPLASMA and the planned hardware replica.
Dump: tms27pc512.BIN, 65,536 bytes, MD5 b775427806857f60ca4a4cc501f4b5cc.
Analysis tooling: hc11dis.py (a purpose-built 68HC11
disassembler — the toolchain has no m68hc11 target).
Memory map
- CPU $8000–$FFFF = EPROM upper 32 KB, 1:1 (the HC11 vector table lands at
ROM offset
$FFC0–$FFFFand is valid, which pins the mapping). The EPROM's lower 32 KB is unused (all$00) — only A15-high is decoded to the ROM. - Code:
$9000–$B8xx. Data/tables:$8000–$8FFF(demo),$98AC+(dispatch tables),$BC03+(font descriptors),$C000–$DFFF(glyph bitmaps + graphics). - RAM (Mosel MS62256, 32 KB) at low addresses: HC11 registers on page 0
(
$00–$3F; SCSR=$2E, SCDR=$2F), zero-page variables$40–$FF, RX ring buffer at$0228, and ten 128×32 screen buffers from$0F6Dup.
Vectors
| Vector | Target | Notes |
|---|---|---|
| RESET | $9059 |
Init: registers, stack $0227, then main loop |
| SCI (serial rx) | $B85C |
Interrupt-driven receive → ring buffer |
| COP watchdog | $905E |
(re-inits) |
| others | $9059 |
default → reset |
Architecture
- SCI RX ISR (
$B85C) — on RDRF, readsSCSR/SCDR, stores the byte to a ring buffer at$0228(write ptr$0228, count$022C). No parsing here. - Main-loop parser (
$B7E0–$B859) — pulls buffered bytes and runs anESC-state machine (flag$AF: bit$10= ESC seen, bit$20= operand pending). Dispatch is table-driven (below). Printable chars in the current font's[first,last]range ($62/$63) go to the character renderer ($9648), which enqueues a glyph to a deferred rasterizer. - Ten double-buffered screens — descriptor table at
$A4E0(10 × 6 bytes): each screen has a draw pointer ($BE) and a display pointer ($BC) into SRAM, 1 KB apart.ESC Isets the draw target,ESC isets what's scanned to the glass → page-flipping / double-buffering.
Command dispatch
Two jump tables, indexed by byte:
ESC+ letter → command table at$98AC. Valid letters0x30–0x7E; index =letter − 0x30; null entry = ignored. 58 commands populated.- Control bytes
0x08–0x14→ table at$994C. index =byte − 0x08.
Most command handlers share a prologue: first sighting of the letter sets the
"operand pending" flag and returns; the next byte is the 1-byte operand
(in $C6). Multi-operand commands (ESC P/X/Y) collect into a parameter
block at $0070.
Control characters ($994C)
| Byte | Handler | Meaning |
|---|---|---|
0x08 BS |
$99AF |
cursor left |
0x09 HT |
$9966 |
tab |
0x0A LF |
$99F3 |
line feed |
0x0B VT |
$9A30 |
vertical tab |
0x0D CR |
$9A55 |
carriage return |
0x11–0x14 DC1–DC4 |
$9A5C/$9B34/$9C09/$9CFC |
device controls (TBD) |
0x0C FF, 0x0E–0x10 |
— | no handler |
ESC commands ($98AC) — confirmed semantics
| Cmd | Handler | Meaning |
|---|---|---|
ESC @ |
$9F26 |
Clear the active draw buffer (512 bytes = 128×32÷8) |
ESC G n |
$A42B |
Cursor mode, low nibble of $B4 (n = 0–7) |
ESC H n |
$A44C |
Text attributes, low 4 bits of $B1 (intensity/underline/reverse/flash) |
ESC K n |
$A3EA |
Font select (n = 0–9; 8 real fonts) |
ESC L |
$A556 |
Home cursor (X=0, Y=0) |
ESC Q n |
$A51C |
Set cursor row Y (range-checked 0–31) |
ESC R n |
$A539 |
Set cursor column X (range-checked 0–127) |
ESC I n |
$A473 |
Select DRAW page 0–9 (sets $BE from $A4E0 table) |
ESC i n |
$A4A2 |
Select DISPLAY page 0–9 (page-flip; sets $BC) |
ESC P … |
$AAF1 |
Graphics bitmap write (multi-operand: screen,y,x,w,h,data) |
ESC A–ESC F |
$9FB6–$A13C |
Vector/graphics primitives (line/point/move; pen state $B6, coords $58/$59, line routine $A16C) |
ESC X n |
$A748 |
Set graphics pen X (multi-op, 0–127) |
ESC Y n |
$A644 |
Set graphics pen Y (multi-op) |
ESC J |
$A4D4 |
Toggle mode bit $B7.7 (orientation/display — TBD) |
ESC commands — populated but not yet decoded
ESC 0–9 ($9E27+, set continuations — likely custom-char / numeric entry),
ESC : ; = , ESC < > W w _ (cluster $AEBA–$AF00), ESC B C D E F variants,
ESC M N O ($A5BD/$A5C5/$A5E0), ESC Z ^ z ~ (cluster $AC73–$ACA1),
ESC a–f, ESC h l n p q r x. ~30 handlers remain to label — full list with
addresses is dumped by the tooling below.
Fonts
Font-pointer table at $BC03 (10 slots) → 12-byte descriptors. 8 real
fonts (slots 8–9 are junk pointers, matching the demo's "8 STORED CHARACTER
FONTS"):
| Font | First–Last | W×H | Notes |
|---|---|---|---|
| 0 | 0x20–0xFF |
6×8 | base font, full range |
| 1 | 0x40–0x7F |
6×8 | uppercase-only |
| 2 | 0x20–0xFF |
6×10 | |
| 3 | 0x40–0x7F |
6×10 | |
| 4 | 0x20–0x7F |
12×16 | large |
| 5 | 0x20–0x7F |
12×20 | largest |
| 6 | 0x20–0xFF |
7×10 | |
| 7 | 0x40–0x7F |
7×10 |
Glyph bitmaps live in ROM (~$C000–$DFFF). Exact glyph base + encoding
pending — the renderer at $9648 enqueues to a deferred rasterizer; tracing
that (or brute-forcing the 'A' pattern at the known stride) will extract the
real glyphs to replace vPLASMA's public-domain 5×7 stand-in.
Demo program
Enabled by jumper 6 (PD3) — confirms the JP1 map. A 10-screen
scripted demo; the pointer table at $8000 (10 × 4-byte entries) points to
each screen, and every screen is [2-byte count][command stream]. The player
at $BB60/$BBA4 loops the screens, feeding each byte through the command
parser. Extracted verbatim into src/VPlasma.Core/Device/PlasmaFirmwareDemo.cs
(all 10 screens as raw wire bytes); the standalone app replays it on jumper 6.
Commands used: @ G I K L Q R Z i + text. ESC I/ESC i (draw/display page)
are consumed by vPLASMA but not acted on (single-page); ESC Z (a rarely-used
animation command, one all-zero use in screen 9) is left unimplemented.
What this means
For vPLASMA (folded in 2026-07-16): the recovered spec replaced the
guessed behavior. vPLASMA now uses the 8 real ROM fonts (extracted to
src/VPlasma.Core/Device/PlasmaFonts.cs), a pixel-addressed cursor with
the real ESC Q (row) / ESC R (column) positioning, ESC K 0–7 font
select, and ESC H attributes as the low 4 bits. The standalone app also
implements the functional JP1 jumpers — baud (1+2), orientation (4:
horizontal 128×32 / vertical 32×128), display test (5: all-dot pattern),
and demo (6). Verified: 27 unit tests + the self-test pages render the real
glyphs. Still deferred (documented, single-page model retained): the 10
double-buffered pages (ESC I/ESC i) and the vector-graphics primitives
(ESC A–F).
For the replica: this is the spec. The firmware confirms a clean model — a byte-stream command parser, a 512-byte-per-page frame buffer, 10 pages with page-flip, 8 fonts, text attributes as 4 flags, plus vector graphics. All of it ports directly onto a modern MCU. The one artifact still to extract is the glyph bitmaps.
Reproduce
python hc11dis.py <hexaddr> <count> # disassemble from a CPU address
# e.g. python hc11dis.py B7E0 70 # the command parser
Command/control tables are at $98AC / $994C; font table $BC03; screen
table $A4E0.