- 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>
161 lines
7.8 KiB
Markdown
161 lines
7.8 KiB
Markdown
# 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](../src/VPlasma.App/)
|
||
and the planned [hardware replica](README.md).
|
||
|
||
Dump: `tms27pc512.BIN`, 65,536 bytes, MD5 `b775427806857f60ca4a4cc501f4b5cc`.
|
||
Analysis tooling: [`hc11dis.py`](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–$FFFF` and 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 `$0F6D` up.
|
||
|
||
## 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
|
||
|
||
1. **SCI RX ISR (`$B85C`)** — on RDRF, reads `SCSR`/`SCDR`, stores the byte to
|
||
a ring buffer at `$0228` (write ptr `$0228`, count `$022C`). No parsing here.
|
||
2. **Main-loop parser (`$B7E0–$B859`)** — pulls buffered bytes and runs an
|
||
`ESC`-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.
|
||
3. **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 I` sets the draw target, `ESC i` sets
|
||
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 letters `0x30`–`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](README.md). 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
|
||
|
||
```sh
|
||
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`.
|