Replaces the guessed font/cursor model with what the firmware dump proved: - The 8 real Babcock ROM fonts, extracted from tms27pc512.BIN into PlasmaFonts.cs (6x8, 6x10, 7x10, 12x16, 12x20). Drops the public-domain 5x7 stand-in and PlasmaFont.cs. - Pixel-addressed cursor with the real positioning commands: ESC Q (row Y, 0-31) and ESC R (column X, 0-127), matching the firmware's range checks. Cursor motion (BS/HT/LF/VT/CR) now moves by font pixels, not cells. - ESC K selects fonts 0-7 (out-of-range ignored, per firmware); ESC H attributes are the low 4 bits (half/underline/reverse/flash). Deferred (documented in FIRMWARE.md): the 10 double-buffered pages (ESC I/i) and vector-graphics primitives (ESC A-F); vPLASMA keeps a single-page model for now. Verified: 24 unit tests pass; the three self-test pages render the real glyphs (big font 4 banner, full charset in font 0, graphics). Next: begin the modern-parts hardware replica, with this as the reference firmware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
153 lines
7.2 KiB
Markdown
153 lines
7.2 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 script at `$8028+` is literal command usage (pointer table
|
||
at `$8000`). It's how the new `ESC R/I/Q/i/K` commands were first spotted, e.g.
|
||
`1B 47 00` (ESC G 0) `1B 40` (ESC @) `1B 4C` (ESC L) `1B 52 04` (ESC R 4) …
|
||
|
||
## 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. Verified: 24 unit tests +
|
||
the three 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`.
|