Files
TeslaRel410/restoration/PlasmaNew/FIRMWARE.md
T
CydandClaude Fable 5 bd082d8c4a PlasmaNew: ESC X/x box commands in the replica + MW4 bench stream
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>
2026-07-24 12:02:37 -05:00

182 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 CPU-invisible** (only A15-high is decoded to the ROM) and
unused — though not quite empty: a stray 1-bpp bitmap-shaped block sits at
file offsets `$0100$02FF` (233 non-zero bytes in 16-byte rows), dead data
left in the image by the EPROM's author.
- **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 |
| **PAOV (pulse-acc overflow)** | **`$9224`** | Display-scan ISR: reloads PACNT=`$E0`, flips the half-frame flag `$B0.0`, writes the scan base to the `$01F8` latch |
| 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. **57 commands populated**
(`q`/`r` share handler `$A5B7`).
- **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/x/Y`) collect into a parameter
block at `$0070``P` via a counting collector, `X/x/Y` by re-entering
themselves through the `$C4` continuation with a sub-state bitmask at `$B5`,
validating each operand on arrival (an out-of-range operand aborts the whole
sequence with nothing drawn). Operand bytes are raw binary and are never
re-tokenized — a coordinate of 27 (`0x1B` = ESC) is fine.
### 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` DC1DC4 | `$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 = 07) |
| `ESC H n` | `$A44C` | **Text attributes**, low 4 bits of `$B1` (intensity/underline/reverse/flash) |
| `ESC K n` | `$A3EA` | **Font select** (n = 09; 8 real fonts) |
| `ESC L` | `$A556` | **Home** cursor (X=0, Y=0) |
| `ESC Q n` | `$A51C` | **Set cursor row Y** (range-checked 031) |
| `ESC R n` | `$A539` | **Set cursor column X** (range-checked 0127) |
| `ESC I n` | `$A473` | **Select DRAW page** 09 (sets `$BE` from `$A4E0` table) |
| `ESC i n` | `$A4A2` | **Select DISPLAY page** 09 (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 l t r b` | `$A748` | **Draw box outline** — 4 raw inclusive coords, validated on arrival (l<128, t<32, r≥l, b≥t). Solid top row; if taller than one row, solid bottom row + single-dot side columns. Pixels OR-set, both page halves. *This is Firestorm's score-panel box (`CPlasma::PlasmaBoxDraw`).* |
| `ESC x m l t r b` | `$A91A` | **Box region op** — mode ≤ 2 then the same 4 coords: **0 = blank, 1 = fill, 2 = invert** (fill worker `$AA07`). *Firestorm's `PlasmaBoxFill` always sends mode 0.* |
| `ESC Y m a b d` | `$A644` | **Screen logic-op blit** — mode ≤ 2, three screen numbers ≤ 9: `dest = srcA AND/OR/XOR srcB` over the whole 1 KB screen (address table `$AADD`, combiner `$AABC`) |
| `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` (the erase-mode twins of `A``F`: same bodies with pen bit `$B6.7`
set), `ESC h l n p q r`. ~29 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 89 are junk pointers, matching the demo's "8 STORED CHARACTER
FONTS"):
| Font | FirstLast | 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`) and have been **extracted** — all
8 fonts are generated into vPLASMA's `PlasmaFonts.cs` and the replica's
`plasma_fonts.h` (row = `uint16`, bit 15 = leftmost pixel).
## 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` 07 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.
**2026-07-22 — the box family decoded (Firestorm unblocked):** `ESC X`/`ESC x`
turned out to be box-outline / box-region commands (the earlier "graphics pen
X/Y" reading was wrong — corrected in the table above), and they are exactly
what **BattleTech: Firestorm**'s `CPlasma` driver (`mw4\Code\MW4\CRIOMAIN.CPP`)
uses for its rank/score panel, alongside cursor-addressed text. BT/RP 4.10 by
contrast drive the panel purely with `ESC P` full-line redraws (`L4PLASMA.CPP`).
Both `ESC X` and `ESC x` (plus `ESC Y` as consume-and-validate, no-op in the
single-page model) are now implemented in the
[hardware replica](replica/MatrixPortalPlasma/PlasmaDisplay.cpp); vPLASMA still
needs the same additions to stay the oracle. `FS-plasma-bench.ps1` replays the
Firestorm sequence for testing.
## 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`.