From b615f319dbb8a6a84eb24631c0e32b5723dd3705 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sun, 26 Jul 2026 22:05:29 -0500 Subject: [PATCH] RIO: decode the pod-bus I/O architecture end to end New IO-ARCHITECTURE.md, derived from the v4.2 image, the U7 GAL decode and schematic sheets 1/3: - $A010 control-latch bit map (CCK / D_OUT / WR_SB / RD_SB / SEL0-2) - the 50-byte script format the firmware replays, and the ROM table map - full port/address population: 9 buttons boards + 2 keypads across all 8 ports; the 0x00-0x6F logical map and its 0x48-0x4F gap - the keypad engine ($CC53/$CC7E): 4-row matrix scan, row-patched RAM scripts, the $DC14 key-code table, message type $8B - lamp readback ($21C2) and the 72-byte lamp-fault mask at $DFA8 -- fault reports are type $03 with the lamp index in $2519 - scan cycle budget: 1.91 ms/pass, 17.3 ms/scan, ~58 Hz; demux is 60% - expansion: one spare buttons board, and nothing further without a protocol revision Corrections to existing docs: - $CC53 was cited as the encoder sweep; it is the keypad-1 scanner. The sweep is $C8CC-$C9A7, driven from $C0CB. - U31 is on sheet 3, not sheet 1, and is completely uncommitted: no address, data, strobe or pod-bus signal reaches it. Not an expansion hook -- populating it does nothing without new wiring. - No spare HCTL-2016 footprints exist. The decode has 3 free selects on U9, but the PCB carries exactly 5 positions and sheet 1 draws 5. More analog axes need hardware, not firmware. Also carries the previously-uncommitted standard-rate (16550) feasibility analysis and baudscan.py, plus a note that the FastRIO "FTDI-class adapter" rule really means arbitrary-rate generation: CP2102N qualifies, classic CP2102 snaps 31250 to 38400. Bench-measured 2026-07-26; on-cockpit latency check still pending. Co-Authored-By: Claude Opus 5 (1M context) --- restoration/rio-firmware/IO-ARCHITECTURE.md | 538 +++++++++++++++++++ restoration/rio-firmware/README.md | 54 ++ restoration/rio-firmware/RIOv4_2-ANALYSIS.md | 146 ++++- restoration/rio-firmware/baudscan.py | 42 ++ restoration/rio-hardware/README.md | 39 +- 5 files changed, 811 insertions(+), 8 deletions(-) create mode 100644 restoration/rio-firmware/IO-ARCHITECTURE.md create mode 100644 restoration/rio-firmware/baudscan.py diff --git a/restoration/rio-firmware/IO-ARCHITECTURE.md b/restoration/rio-firmware/IO-ARCHITECTURE.md new file mode 100644 index 00000000..289dc095 --- /dev/null +++ b/restoration/rio-firmware/IO-ARCHITECTURE.md @@ -0,0 +1,538 @@ +# RIO I/O architecture — the pod bus, decoded + +How the RIO board actually moves buttons, keypads and lamps over its +eight-port "pod bus": the control-latch bit map, the 50-byte script +format the firmware replays, the complete port/address population of a +pod, and what it all costs in CPU cycles. + +Derived 2026-07-26 from [`RIOv4_2.disasm.asm`](RIOv4_2.disasm.asm) plus +the recovered memory map in +[`../rio-hardware/gal/README.md`](../rio-hardware/gal/README.md) and +schematic sheet 3 (`RIO_1407`). Cross-checked against the physical +inventory (9 buttons boards + 2 keypads per pod, operator-confirmed). + +Everything here is read out of the firmware image unless flagged +**INFERRED** or **UNVERIFIED**. + +## The control latch at `$A010` + +The pod bus is bit-banged by rewriting one 74HC574 latch (`BND_SEL`, +write-only, decoded by U7). Every bus edge is one `STAA $A010`. Bit +assignment recovered by diffing all 11 scripts in ROM: + +| Bit | Signal | Behaviour | +|-----|--------|-----------| +| 0 | `CCK` | shift clock — toggles on every write | +| 1 | `D_OUT` / `DD_IN` | serial data into the pod's 74HCT164 | +| 2 | `WR_SB` | write strobe — **used only in write scripts** | +| 3 | `RD_SB` | read strobe — parallel-loads the pod's 74HCT165 | +| 4 | *(unidentified)* | **static 1** in every script | +| 5-7 | `SEL0-2` | port select → 74LS138; all 8 values used | + +`SEL0` = bit 5, `SEL1` = bit 6, `SEL2` = bit 7, so a script's first byte +`$N0` gives its port directly as `N >> 1`. + +## The 50-byte script format + +The firmware does not bit-bang inline. It replays precomputed **50-byte +scripts**, one latch state per byte: + +- **18 bytes — preamble.** Nine `CCK` pulses shifting a walking `1` + down the cable into the board's address 74HCT164; its position picks + the `ADR. SEL.` jumper (`A-0` = shift position 7, `A-1` = 6, + `A-2` = 5). The last pair asserts the strobe. +- **32 bytes — payload.** 16 `CCK` pulses, 2 bytes each. On reads, + `PORTE` is sampled between pulses; on writes, `WR_SB` (bit 2) latches. + +18 + 32 = 50, matching the table stride exactly. + +### The acquisition routine `$CFC0` + +``` +CFC0 PSHA/PSHB/PSHX/PSHY ; prologue +CFC5 LDY #$2152 ; sample destination +CFC9 LDX $2150 ; script pointer +CFCC LDAB #$12 ; 18 preamble bytes +CFCE LDAA $00,X / STAA $A010 / INX / DECB / BNE ; preamble loop +CFD8 LDAB #$10 / STAB $2036 ; 16 bits +CFDD LDAA $100A ; sample PORTE <-- bit loop top +CFE0 PSHX / STY $2031 / LDX $2031 / STAA $00,X / PULX / INY +CFED LDAB #$02 +CFEF LDAA $00,X / STAA $A010 / INX / DECB / BNE ; one CCK pulse +CFF8 DEC $2036 / BNE $CFDD +CFFD NOP / PULY / PULX / PULB / PULA / RTS +``` + +> **Disassembler sync gap.** `RIOv4_2.disasm.asm` renders +> `$CFE0-$D003` as `.byte` — it lost sync on the `18 FF` (`STY` +> extended) prebyte. The listing above is the hand decode; both branch +> displacements (`$CFF6` → `$CFEF`, `$CFFB` → `$CFDD`) land exactly on +> its instruction boundaries, which confirms it. The same gap recurs +> at `$C380-$C38F`. + +`PORTE` (`$100A`) is read at **exactly one site in the whole firmware** +— this one. Every bit of pod input arrives here. + +### Script tables in ROM + +| Region | Size | Contents | +|--------|------|----------| +| `$DBB0-$DC13` | 2 × 50 | **keypad scripts** — SEL 4, SEL 5 | +| `$DC14-$DC23` | 16 B | **keypad key-code table** (see below) | +| `$DC24-$DDE5` | 9 × 50 | **read scripts** — the 9 buttons boards | +| `$DDE6-$DFA7` | 9 × 50 | **write scripts** — lamps (`WR_SB` active) | +| `$DFA8-$DFEF` | **72 B** | **lamp-fault mask** — one byte per lamp (see below) | +| `$DFF0-$FFxx` | 8,144 B | erased — the largest free run in the EPROM | + +The blocks butt against each other with **zero slack**: there is no +reserved tenth script anywhere in the image. + +## Port and address population of a pod + +Scan order is `$CF40`, nine `JSR $CFB6` calls. SEL values across the +scripts are `0,1,2,2,3,3,3,6,7`, matching the `$204E` increment +sequence exactly (`$204E` = SEL + 1; it starts at 1 per `$CF3E`). + +| Port | Board | Addr | Logical range | +|-----:|-------|------|---------------| +| 0 | buttons | A-0 | `0x00-0x07` | +| 1 | buttons | A-0 | `0x08-0x0F` | +| 2 | buttons ×2 | A-0, A-1 | `0x10-0x1F` | +| 3 | buttons ×3 | A-0, A-1, A-2 | `0x20-0x37` | +| 4 | **keypad 1** | A-0 | `0x50-0x5F` | +| 5 | **keypad 2** | A-0 | `0x60-0x6F` | +| 6 | buttons | A-0 | `0x38-0x3F` | +| 7 | buttons | A-0 | `0x40-0x47` | + +All eight ports are populated. The button scan's `INC $204E` ×3 at +`$CF8B` skips ports 4-5 because those are keypads, driven from the +separate script pair at `$DBB0`/`$DBE2` (referenced `LDX #$DBB0` at +`$C350`). + +**Port→logical assignment within port 3 and the port-6/7 ordering is +INFERRED** from scan order (`$2037` increments monotonically, 8 per +script); the ROM does not label them. + +## The keypads — a second, separate engine + +The keypads do **not** ride the button scan. They have their own +scripts, their own bit-bang engine, their own RAM, and their own +message type. This is why they never appear in the 72-entry button +arrays. + +### Templates are copied ROM → RAM so rows can be patched + +`$C34C` copies **100 bytes** (`LDAB #$64`) from `$DBB0` to **`$2050`** — +both keypad scripts. `$C369` does the same for the 9 write scripts +(`$DDE6` → `$220A`, `#$09` × `#$32`). Scripts live in RAM because the +firmware **rewrites bits inside them between replays**. + +| | Keypad 1 | Keypad 2 | +|---|---|---| +| Entry | `$CC53` | `$CC7E` | +| Port (SEL) | 4 | 5 | +| `$204E` | 5 | 6 | +| RAM script | `$2050` | `$2082` (= `$2050 + $32`) | +| Raw samples | `$20B6` (32 B) | `$20D6` (32 B) | +| Key states | `$20FC` (16 B) | `$210C` (16 B) | +| Key code out | `$204A` | `$204B` | + +`$2082` never appears as a literal — `$CC7E` reaches it with +`LDAB #$32 / ABX`, and likewise offsets the two buffers by `$20`/`$10`. + +`$204E` = 5 and 6 are **exactly the two indices the button scan skips** +with its `INC $204E` ×3 at `$CF8B`. The two engines share one index +space and partition it cleanly. + +### The script shape differs from a buttons board + +``` +buttons: 7 pulses data=0, 1 pulse data=1, strobe, 16 read pulses +keypad : 15 pulses data=0, 1 pulse data=1, strobe, 8 read pulses +``` + +The keypad shifts **16 bits, not 8** — the 1402 carries *two* chained +74HCT164s (8 bits board address + 8 bits matrix row drive, through the +74LS05 open-collector inverters), so the walking `1` has to travel +twice as far. It then reads back only 8 bits, because the single +CD74HCT165 sees 4 columns plus `BRD_DT*` and spares. + +### The scan engine `$CCB2` — four rows + +``` +LDY $20F6 ; result ptr LDAB #$04 / STAB $2034 ; 4 ROWS +CCBC: LDX $20B4 ; reload script base each row + dispatch on row: $CD1E (4) / $CD4F (3) / $CD80 (2) / $CDB1 (1) + ; ^ patch the row bit in the RAM script +CCE3: LDAB #$22 ; 34 bytes = 17 CCK pulses + strobe + LDAA $00,X / STAA $A010 / INX / DECB / BNE +CCF2: LDAB #$08 / STAB $2035 ; 8 columns +CCF7: LDAA $100A ; sample PORTE (the 2nd of only 2 such sites) + ... store, INY, 2 latch writes = one CCK pulse ... +CD12: DEC $2035 / BNE $CCF7 ; 8 bits +CD17: DEC $2034 / BNE $CCBC ; 4 rows +``` + +The four row patchers are near-identical; `$CD1E` is +`LDAA $08,X / ORAA #$02 / STAA $08,X` (etc.) — setting and clearing +**bit 1 (`D_OUT`)** at specific script offsets, which walks the row +`1` to a different shift position for each row. + +### Demux `$CDE2` and the key-code table + +Per row it does `INX` ×4 — **discarding the first 4 sampled bits** — +then takes 4. That is 4 columns × 4 rows = **16 keys**, indexed by +`$20FB` running 0-15. The discarded bits are the `165`'s status half; +`$CEFA` picks two of them (`$20B6+$03` bit 4, `$20D6+$03` bit 5) into +the fault array at `$24ED+9`/`+$0A` — **keypad board-detect**. + +`$CE1F`/`$CE72` load **`LDY #$DC14`**, a 16-byte scan-code → legend +table occupying the gap between the keypad scripts and the button read +block. Contents are the stock hex-pad layout: + +| | c0 | c1 | c2 | c3 | +|---|---|---|---|---| +| **r0** | 1 | 2 | 3 | C | +| **r1** | 4 | 5 | 6 | D | +| **r2** | 7 | 8 | 9 | E | +| **r3** | A | 0 | B | F | + +`$CE72` is the press path (state → `$01`), `$CE1F` the release path +(state → `$00`); a pressed key reads **0**, being pulled low against +the 2.7K column pull-ups. Both enqueue message type **`$8B`** — *not* +the `$88`/`$89` button press/release pair. + +**So `0x50`/`0x60` are host-side logical IDs, formed as a per-pad base +plus the 0-F key code; the wire carries a distinct message type and a +nibble.** That is why the pads consume none of the 112-entry button +map internally. (**INFERRED** — the base-plus-code composition is the +reading that fits both the firmware and the observed map; `PROTOCOL.md` +in the RIOJoy repo is the authority.) + +### Cadence asymmetry + +The main loop at `$C0B0` is: + +``` +C0B0 JSR $C579 ; protocol service +C0B3 JSR $CF31 ; button scan (9 boards) +C0B6 JSR $C579 +C0B9 JSR $CC7E ; keypad 2 ONLY +C0BC JSR $C579 +C0BF JSR $C1CB +C0C2 JSR $C579 +C0C5 JSR $C0EB +C0C8 JMP $C0B0 +``` + +**Both pads are polled every pass, but by different routes.** Keypad 2 +is scanned directly at `$C0B9`. Keypad 1 goes through `$C33B` +(`JSR $CC53 / LDAA $204A / RTS` — scan and return the key), which is +called by the **A-9-E chord detector `$C1CB`** at `$C0BF`; `$CC53` is +also reached from the `CheckRequest` self-test at `$C5C9`. So keypad 1 +feeds the test-mode entry sequence while keypad 2 feeds the normal +event stream — a functional split, not a polling gap. + +## I/O inventory — one pod + +| Inputs | Count | Address | +|--------|------:|---------| +| Buttons (9 × 1401 × 8) | **72** | `0x00-0x47` | +| Keypad 1 (1402 hex pad) | **16** | `0x50-0x5F` | +| Keypad 2 (1402 hex pad) | **16** | `0x60-0x6F` | +| **Total digital controls** | **104** | of a 112 map | +| Analog axes (HCTL-2016, 512 cpr) | 5 | `$A020-$A03F` | +| Panel switches (RESET, ABORT→`IRQ*`) | 2 | — | + +| Outputs | Count | +|---------|------:| +| Button lamps (9 × 8, UCN5821A) | **72** | +| Global brightness PWM (`BRT_CTR`) | 1 | +| Display digits (ICM7228B) | 8 | +| Status LEDs | 6 | + +### Logical address map + +| Range | Count | Contents | +|-------|------:|----------| +| `0x00-0x47` | 72 | buttons — **fully populated** | +| `0x48-0x4F` | 8 | **gap** — one unused board slot | +| `0x50-0x5F` | 16 | keypad 1 | +| `0x60-0x6F` | 16 | keypad 2 | + +`0x48` = 72 is the `#$48` constant that sizes five separate arrays — +the button space and the gap boundary are the same number. + +## RAM layout (packed solid) + +| Base | Size | Contents | +|------|-----:|----------| +| `$2050` | 2 × 50 | keypad scripts, copied from `$DBB0`, row-patched in place | +| `$20B6` / `$20D6` | 32 B ea | keypad raw samples (4 rows × 8 bits) | +| `$20FC` / `$210C` | 16 B ea | keypad key states | +| `$2152` | 16 B | raw `PORTE` samples, bit-major | +| `$2162` | `$60` = 96 | input state, logical `0x00-0x5F` | +| `$21C2` | `$48` = 72 | second demux path (`$D144`, index `$2442`) | +| `$220A` | 9 × 50 = 450 | lamp write-script buffer (board × 50) | +| `$23CC` | 2 B | script pointer used by `$D331` | +| `$23CE` | `$48` = 72 | lamp state | +| `$2454` | `$48` = 72 | lamp command | +| `$249C` | `$48` = 72 | (button-indexed) | +| `$24ED` | `$14` = 20 | fault/status flags (`$CEFA`) | + +Arrays abut with no padding — `$21C2` + 72 ends at `$2209`, exactly +where the lamp buffer begins. + +## Cycle budget (2 MHz E-clock, 0.5 µs/cycle) + +Hand-counted from M68HC11 instruction timings; all register access is +extended mode (no `LDX #$1000` base pointer exists in the image), so +operand enumeration is exhaustive. + +| Stage | Cycles | Time | +|-------|-------:|-----:| +| Preamble loop (18 × 16) | 288 | 144 µs | +| Bit loop (16 × 75) | 1,200 | 600 µs | +| Prologue/epilogue/JSR | 66 | 33 µs | +| **`$CFC0` acquisition** | **1,554** | **777 µs** | +| `$D004` demux (8 inputs) | ~1,132 | 566 µs | +| `$D144` demux (8 inputs) | ~1,132 | 566 µs | +| **`$CFB6` — one pass** | **~3,829** | **1.91 ms** | +| **Full scan (9 passes)** | **~34,500** | **17.3 ms** | + +**Maximum scan rate ≈ 58 Hz; worst-case button latency ≈ 17 ms.** +Acquisition is only 40% of it (7.0 ms) — **demux is 60%** (10.2 ms). + +Marginal costs: **one more pass (+16 bits) = 1.91 ms**; one more input +inside an existing pass = 135 cycles = 68 µs. + +Keypads, same method (**estimates** — the row patchers and handlers are +state-dependent): + +| Stage | Cycles | Time | +|-------|-------:|-----:| +| `$CCB2` per row (patch + 34 shift + 8 read + loop) | ~1,275 | 637 µs | +| `$CCB2` × 4 rows | ~5,150 | **2.6 ms** | +| `$CDE2` demux (16 keys) | ~2,800 | **1.4 ms** | +| **One keypad** | ~7,950 | **~4.0 ms** | + +A main-loop pass therefore costs roughly **17.3 ms (buttons) + 4.0 ms +(keypad 2) ≈ 21 ms**, plus the encoder sweep and four `$C579` protocol +services — call it ~45 Hz of button/keypad latency. Keypad 1 adds its +4.0 ms only when the host asks for it. + +The scan is **fully interruptible** — every `SEI`/`CLI` in the image is +at `$C0AF` and `$C6E9-$C804` (message-queue critical sections), none in +`$CF40-$D180`. Lengthening the scan costs latency, *not* SCI ISR margin. + +### Why demux dominates + +Two naive constructs, both fixable in the `$DFF0` cave: + +1. Bit extraction shifts a byte right *k* times via + `DECB/BEQ/LSRA/JMP` — **10 cycles per position**, up to 75 to reach + bit 7. A mask table replaces it with ~4. +2. The press/release handler is a `JSR` with full register save + (58 cycles) **even when nothing changed**. + +Estimated together: demux ~1,132 → ~400 cycles/pass, full scan +**17.3 → ~9.4 ms**. **UNVERIFIED** — not implemented or benched. + +## Lamp readback and the fault mask + +The second 8 bits of each board's 16-bit read — the `$D144` path into +`$21C2` — are **lamp readback**, not spare status. This closes the +loop the 1401's second 74HCT165 exists for. + +- `$D144` compares each bit against the expected polarity in `$24EA`; + `$D180` sets the entry's bit 0 accordingly, `$D19E` clears it. + `$21C2[n]` therefore holds **lamp n's health**: 1 = OK, 0 = fault. +- `$D262` sweeps all `$48` = 72 entries and calls `$D28C` for every + **zero**. +- `$D28C` indexes **`$DFA8`** by the same lamp number. Mask byte 0 + suppresses the report; nonzero emits report type **`$03`** with the + lamp index in `$2519` via `$C75E`. + +So `$DFA8` is a **per-lamp populated map** — which of the 72 lamp +positions are physically fitted and therefore worth fault-checking. It +is **72 bytes** (`$DFA8-$DFEF`), exactly matching the array, with the +erased cave starting at `$DFF0`. Suppressed entries: + +| Board | Lamps | Mask | +|------:|-------|------| +| 0-2 | `$00-$17` | all monitored | +| 3 | `$18-$1F` | `$1F` suppressed | +| 4-6 | `$20-$37` | all monitored | +| 7 | `$38-$3F` | `$3C`, `$3F` suppressed | +| **8** | `$40-$47` | **all 8 suppressed** | + +61 of 72 positions are monitored. The ninth board carries no +fault-checked lamps at all. + +This explains the persistent "board/lamp fault item" seen in both +`CheckRequest` snapshots (RIOv4_2-ANALYSIS.md) — it is a type-`$03` +report, and **`$2519` carries the exact lamp index**, so a captured +snapshot identifies precisely which lamp is dead. + +`$D0BC` — the routine `$C0EB` defers by four main-loop passes, and +skips while the test-display flag `$2421` is set — walks the two other +72-entry lamp arrays (`$249C`, `$2454`) under a rotating bit mask +`$24E8` that advances one position per call and reloads at `$80` after +8 (`$24E9`). One bit-plane per tick across all lamps: most consistent +with **per-lamp blink/intensity sequencing** (**INFERRED** — the +bit-plane walk is plain, the visual effect is not proven). + +## The encoder bank — decode has room, the board does not + +Settled 2026-07-26 from the board photo and schematic sheet 1. + +**Addressing.** Each HCTL-2016 consumes **two** 74LS138 outputs, not +one: + +| Line | Role | +|------|------| +| `A[0]` | 0 = data read, 1 = strobe (the `OE`/`RST` pair) | +| `A[2:1]` | channel within a '138 | +| `A[3]` | which '138 — U8 or U9, via the GAL's `NOT_A3` | +| `A[4]` | `SEL` pin = high/low byte of the 16-bit count | + +Both '138s take `A[0:2]` + `PH_SEL*` + `E`; U8's `G2B` is `A[3]`. +Firmware confirms the split — `$C8CC-$C9A7` reads the even (data) +addresses `$A020/$A030` … `$A028/$A038`, while `$C458-$C4C0` hits the +odd (strobe) addresses `$A021`-`$A029`. + +So the ceiling is **8 channels**: U8 serves OPT1-4 (Y0/Y1 … Y6/Y7), +U9 serves OPT5 on Y0/Y1 and has **Y2-Y7 free = 3 spare channel +decodes**. (An earlier estimate reached the same "3 spare" by assuming +one decode line per chip; the count is right, the reasoning was not.) + +**But there are no footprints.** The 1407 carries exactly five +HCTL-2016 positions — U12-U16 = OPT1-5, headers J5/J6/J7/J3/J4 — and +the schematic draws exactly five symbols, U16 being the last. Below +OPT5 the PCB is bare: no silkscreen outline, no plated holes. For +contrast, this board's genuinely unpopulated sites (**U31**, the 22V10, +and **R43/R44**) show an obvious white outline over two rows of empty +pads. The encoder bank has nothing of the kind. + +The '138 outputs are pulled up by **R16**/**R15** (`PULLUP10`, 2.7K); +R16's spare pin is net `nc2`, carrying an explicit fab instruction on +the sheet: *"**Note: Do not wire net nc2 on pcb."* + +**Verdict: +3 axes is NOT firmware-only.** The decode would accept +them; the board offers nowhere to put them. It needs a daughterboard +hung off U9's spare selects, or a respin. + +## Expansion capacity + +| Resource | Now | Add | Gated by | +|----------|----:|-----|----------| +| Buttons | 72 | **+8** | the `0x48-0x4F` gap = one 1401 board | +| Lamps | 72 | **+8** | same board | +| Analog axes | 5 | +3 **(hardware)** | decode has 3 spare selects but **no PCB footprints** | +| Keypad keys | 32 | 0 | map ends at `0x6F` | +| Display digits | 8 | 0 | ICM7228B is 8-digit | +| Pod-bus slots (hardware) | 64 | +53 | **no logical IDs to reach them** | + +**The pod is built out.** 104 of 112 logical inputs are in use. Past ++8 buttons, expansion is *protocol*-bound, not hardware-bound: the bus +can address 64 slots and the CPU could service ~17 at 30 Hz, but the +map has no IDs left. + +**The single tenth buttons board is the only expansion this hardware +takes without modification.** The analog axes looked like the free win +but are not: the decode has three spare channel selects on U9, and the +board has nowhere to solder them (see the encoder-bank section above). +Everything else — more buttons, more keys, more axes — needs either a +protocol revision or new hardware. + +### Adding the tenth board + +No free port — all 8 are populated, so it chains onto an existing +port's cable at a spare `ADR. SEL.` position (`A-3`+ are unused +everywhere; `DD_OUT` is open-collector precisely to allow sharing). +**INFERRED**: port 7 at `A-1` is the natural spot, since `0x48-0x4F` +follows port 7's `0x40-0x47` in scan order. The harness decides. + +Cost: one scan pass (17.3 → 19.1 ms, 58 → 52 Hz), ~100 bytes of script +in the cave, a tenth `JSR $CFB6`, and five `#$48` → `#$50` constants +(`$C399`, `$C3A2`, `$C3B4`, `$C3BD`, `$D26D`). The catch is RAM: the +arrays abut, so growing them in place overruns the lamp buffer at +`$220A` — it needs a re-layout, though SRAM is 32K with only +~`$2000-$2500` in use. + +## Corrections to earlier claims + +Recorded so they don't get re-derived wrongly: + +- **`PORTE` is the pod-bus return path, not spare ADC.** All 8 pins + carry `DI[0..7]`; the A/D is never powered up (`OPTION`/`ADCTL`/ + `ADR1-4` untouched) because the pins are already busy. This is also + *why there are exactly 8 pod ports* — `PORTE` is 8 bits wide. +- **There is no 8× demux win.** An acquisition stores whole `PORTE` + bytes, which looks like all 8 ports captured in parallel — but `SEL` + varies per script and tracks `$204E`, so the 74LS138 gates the + strobes per port and only the selected port returns valid data. +- **Ports 4 and 5 are not empty** — they are the two keypads. +- **The keypads are not daisy-chained** into button shift chains; they + have their own ports and their own scripts at `$DBB0`/`$DBE2`. + +- **`$CC53` is the keypad-1 scanner, not an encoder sweep.** + RIOv4_2-ANALYSIS.md's `CheckRequest` write-up cited `$CC53` for + "sweeps all five encoder channels". The behavioural claim is right + (the sweep does end on channel 4) but the address is wrong: the five + channels are read at **`$C8CC`-`$C9A7`** (pairs `$A028/$A038`, + `$A020/$A030`, `$A022/$A032`, `$A024/$A034`, `$A026/$A036`), driven + from `$C0CB`. `$C5C9`/`$C5CC` in the self-test are the two keypad + scans. +- **The disassembly under-reports hardware access.** Sync gaps hid a + second `PORTE` read (`$CCF7`) and two `$A010` writes + (`$CCEA`, `$CD0B`). Search the raw binary for `B6 10 0A` / + `B7 A0 10` rather than grepping the listing. + +## The main loop, fully accounted for + +``` +C0B0 JSR $C579 ; protocol service (runs between every stage) +C0B3 JSR $CF31 ; button scan — 9 boards, 17.3 ms +C0B9 JSR $CC7E ; keypad 2 scan, 4.0 ms +C0BF JSR $C1CB ; keypad 1 via $C33B + A-9-E chord detector +C0C5 JSR $C0EB ; deferred lamp bit-plane tick (every 4th pass) +C0C8 JMP $C0B0 +``` + +`$C1CB` is the **test-mode chord detector**. It calls `$C33B` (scan +keypad 1, return its key) every pass and matches the sequence +**A → 9 → E** in a 3-byte buffer at `$20F8`; on the third key it sets +the test-display flag `$2421`, sends `TestModeChange` (`$C70E`), and +runs `$CBA7`/`$C241`. So keypad 1 *is* polled continuously — the +earlier "on demand only" reading was wrong; the demand is this +detector. That is the keypad-A-9-E test mode described in +[`../rio-hardware/display-board-1408.md`](../rio-hardware/display-board-1408.md). + +The encoder sweep is not in this loop — it runs from `$C0CB` +(`$C8CC`-`$C9A7`). + +## Open questions + +Nothing outstanding on the I/O path. Remaining loose ends are cosmetic: +the precise visual effect of `$D0BC`'s bit-plane walk, and the three +individually-suppressed lamp positions (`$1F`, `$3C`, `$3F`) — which +may record real cockpit wiring rather than anything in firmware. + +### Reading the schematics without a PDF renderer + +`Scans_018-014.pdf` holds seven **CCITT G4** scans (6800×4400, one per +sheet) and no text layer. With no `pdftoppm`/Ghostscript available, the +working route is to pull each image stream out of the PDF and rewrap it +in a minimal TIFF header (`Compression=4`, `PhotometricInterpretation=0`), +which Pillow decodes directly. Sheet order matches the table in +[`../rio-hardware/README.md`](../rio-hardware/README.md). + +## Cross-references + +- [`RIOv4_2-ANALYSIS.md`](RIOv4_2-ANALYSIS.md) — wedge fix, baud + ladder, certification history +- [`../rio-hardware/README.md`](../rio-hardware/README.md) — boards, + schematics, pod-bus signal set +- [`../rio-hardware/gal/README.md`](../rio-hardware/gal/README.md) — + U7 decode and the memory map diff --git a/restoration/rio-firmware/README.md b/restoration/rio-firmware/README.md index 27abd47e..768c3681 100644 --- a/restoration/rio-firmware/README.md +++ b/restoration/rio-firmware/README.md @@ -4,6 +4,12 @@ > [wedge-explained.md](wedge-explained.md) — a plain-language account of > the "stick died mid-battle" bug that RIO 4.3 fixes. +> **How the board moves buttons and lamps:** +> [IO-ARCHITECTURE.md](IO-ARCHITECTURE.md) — the pod-bus control latch, +> the 50-byte script format, the full port/address population of a pod +> (9 buttons boards + 2 keypads), the scan cycle budget, and what +> expansion headroom is left. + - **`RIOv4_2.bin`** — RIO cockpit I/O board firmware **v4.2**, dumped 2026-07-04 from one of our own boards' EPROM: an **AMD AM27C512-150** (64K x 8 UV EPROM, 150ns — the image fills it exactly). @@ -88,6 +94,54 @@ Disassembly-verified; run the acceptance ladder on the FastRIO cockpit before deploying (`--e0test COM1` auto-probes 31250, then a mash spot-check at `--baud 31250`). +### "FTDI-class" is really "arbitrary-rate" — CP2102N qualifies + +Bench-measured 2026-07-26 on a **Silicon Labs CP2102N** (`VID_10C4`/ +`PID_EA60`; identify by the bus-reported string "CP2102N USB to UART +Bridge Controller" — the `REV_0100` hardware ID is *not* diagnostic). +It generates the whole HC11 ladder — 20833 / 31250 / 41667 / 62500 — +each tracking its request within measurement noise, so the adapter +requirement is about **arbitrary-rate generation**, not the FTDI brand. + +**Classic CP2101/2102/2103 do NOT qualify**: they quantise to the AN205 +table, where a 31250 request snaps silently to **38400**. The part on +test was verified not to snap — 31250 and 38400 measure as distinct +wire rates. + +**UNVERIFIED**: absolute rate against an independent clock, and +round-trip latency against the FastRIO ACK window (`$D9E7 = $28`, +40 ticks ≈ 12.8 ms at 31250). The certification rig used an FTDI with +its latency timer at 1 ms; **CP210x exposes no latency-timer setting** +— though it also has no FTDI-style 16 ms coalescing default to defeat. +Confirm on-cockpit before deploying a CP2102N in a FastRIO seat. + +> **Measurement trap.** On Silabs driver **6.7.0.0**, +> `SerialPort.BytesToWrite` reaches 0 while ~640 bytes (the CP2102N TX +> FIFO) are still in flight; driver **6.7.6.2130** waited for a true +> drain. Timing a bulk write to `BytesToWrite == 0` therefore +> *over-reads* the baud rate — at 31250/3 s it reported a bogus +> **+7.15%**, which looks exactly like a rate fault but is pure +> bookkeeping. Measure instead by the **slope** of elapsed-vs-bytes +> across several transfer sizes; a fixed buffer only moves the +> intercept. + +## Standard-rate (16550) future — the oscillator-swap path + +FastRIO's 31250 is FTDI-only by arithmetic: from the stock 8 MHz +oscillator every SCI rate above 9615 sits +8.5% off the nearest +16550-standard rate (exhaustive proof: [`baudscan.py`](baudscan.py)). +Swapping the board's 4-pin oscillator can (8.000 MHz, by U4) for a +**7.3728 MHz** part makes the entire standard ladder exact — 9600 +(still native-game compatible) through 115200 — at the cost of a +mandatory one-byte BAUD retune per rate. Recommended target: **28800 +"FastRIO-Std"**, which keeps the exact per-byte CPU margin of the +certified 31250 v2 while being reachable by *any* UART, including the +period pods' 16550s. Full analysis (the closed-form impossibility, can +options, margins, required edits, and the why-8-MHz design hypothesis): +RIOv4_2-ANALYSIS.md "Standard-rate (16550) feasibility". Unbuilt as of +2026-07-24 — needs the can, new `BAUD_OPTS`/ACK-wait entries in +`make_patch.py`, and the acceptance ladder. + ## Archive All prior generations live in [`archive/`](archive/) with their diff --git a/restoration/rio-firmware/RIOv4_2-ANALYSIS.md b/restoration/rio-firmware/RIOv4_2-ANALYSIS.md index 4c6ccb81..1b9b9981 100644 --- a/restoration/rio-firmware/RIOv4_2-ANALYSIS.md +++ b/restoration/rio-firmware/RIOv4_2-ANALYSIS.md @@ -163,7 +163,9 @@ sha256 `9f866cf353d04906e1d3e3847b6375eaae251c987b656f68f093e61ba1bd545b`, diff vs the classic patched image shows exactly the one operand line. **Caveats:** 31250 is non-standard — FTDI-class USB adapters make it -exactly, classic 16550 UARTs cannot; the **native games still speak +exactly, classic 16550 UARTs cannot (the closed-form *why*, and the +oscillator-swap escape hatch, are in "Standard-rate (16550) +feasibility" at the end of this doc); the **native games still speak 9600**, so a 31250 chip is bench/RIOJoy-only until Firestorm/Red Planet get BTL4OPT-style baud patches; and the 2 MHz HC11's RX ISR has ~640 cycles/byte at this rate (fine) — do NOT be tempted to `$01`/62500 or @@ -510,8 +512,9 @@ movement does not change it) and version-only exchanges leave it alone. The real mechanism is the **CheckRequest (`0x80`) handler at `$C5A6`**: a full built-in self-test that sets the test-display flag `$2421` (`$C5B1`), sends `TestModeChange 0x8C` to the host (`$C70E`), runs a -lamp pattern (`$D34A`), scans the pods, sweeps all five encoder -channels (`$CC53`, ending on channel 4 — whose `cc00hhll` frame is the +lamp pattern (`$D34A`), scans the pods (`$C5BA`/`$C5C6`), scans both +keypads (`$C5C9`/`$C5CC`), sweeps all five encoder channels +(`$C8CC`-`$C9A7`, ending on channel 4 — whose `cc00hhll` frame is the last thing rendered), enqueues the fault reports, clears the flag (`$C5E1`), notifies `0x8C` again — and **returns without repainting the display**. Every status check therefore leaves a stale @@ -524,6 +527,12 @@ E0 readout, which the edit-5 gate suppresses below threshold), `RTS`. Built into **`RIOv4_3rc1.bin`** (with edits 1-2 and 5; sha256 `dc59bd51cae3…`); prior generations moved to `archive/`. +> **Citation corrected 2026-07-26.** This passage previously cited +> `$CC53` for the encoder sweep. `$CC53` is in fact the **keypad-1 +> scanner** (see [IO-ARCHITECTURE.md](IO-ARCHITECTURE.md)); the +> behaviour described — ending on channel 4 — is unchanged and still +> correct, only the address was wrong. + Why `04000000` was never seen in period (user-verified by re-slotting the ORIGINAL stock chip, 2026-07-19): stock leaves the same stale frame after every check, but the ungated E0 readout papers over it at the @@ -557,3 +566,134 @@ board announce 4.3; final sha256 `6d67a2fc7713…` (69 bytes vs stock). No host software validates the version value (native games verified not to check it — Cyd, 2026-07-19); tools will print `firmware: 4.3` after the next burn. + +## Standard-rate (16550) feasibility (2026-07-24) — firmware is closed; the oscillator is the door + +Question: can the link run above 9600 at a rate a *standard* PC UART +(16550-class, 1.8432 MHz clock) can also make? Motivation: FastRIO's +31250 needs an FTDI-class adapter; the period pod PCs — and any future +BTL4OPT-style game-side baud patch — can only produce 115200/n. + +### Firmware-only: mathematically closed + +SCI baud = E/(16·SCP·2^SCR), SCP ∈ {1,3,4,13}, single write site +(`$D62C STAA $102B`). With E = 2 MHz every reachable rate is 125000/m +(m = SCP·2^SCR); a 16550 makes 115200/n. Their ratio 125000/115200 = +**625/576 ≈ 1.0851**, and m can never carry the factor 5⁴ needed to +cancel it (prescales 1/3/4/13, dividers powers of two). An exhaustive +scan of every (m, n) pairing ([`baudscan.py`](baudscan.py)) finds only +two within UART tolerance (~2%/end): + +| HC11 | 16550 | error | note | +|---|---|---|---| +| 9615 (m=13) | 9600 (n=12) | +0.16% | stock — the ÷13 prescale ≈ 12·1.0851 | +| 10417 (m=12) | 10473 (n=11) | −0.54% | legal but pointless: +8.3% throughput | + +Every other pairing — 20833 vs 19200, 31250 vs 28800, 41667 vs 38400, +62500 vs 57600, 125000 vs 115200 — is **+8.51%** (the 625/576 residue), +far outside async tolerance. This is *why* the FastRIO ladder is +FTDI-only: no firmware byte can fix an arithmetic impossibility. + +### Hardware + one byte: swap the oscillator can + +The clock is not a bare crystal — it is a 4-pin **canned oscillator +module** ("8.000000MHZ", by U4; see +[`../rio-hardware/photos/rio-board-1407-component-side.jpeg`](../rio-hardware/photos/rio-board-1407-component-side.jpeg)), +so the mod is a can swap, not a crystal + load-cap job. E = can/4. +Candidate frequencies (all common catalog parts): + +| can | E-clock | CPU delta | exact standard rates | verdict | +|---|---|---|---|---| +| **7.3728 MHz** | 1.8432 MHz | −7.8% (in spec) | **all**: 9600·14400·19200·28800·38400·57600·115200 | **recommended** | +| 9.8304 MHz | 2.4576 MHz | +23% (overclock) | 9600·19200·38400 | possible; CPU rating unverified | +| 11.0592 MHz | 2.7648 MHz | +38% | 14400·28800·57600 — **no 9600** | breaks native games — no | +| 14.7456 MHz | 3.6864 MHz | +84% | all incl. 115200 | far out of spec — no | + +The bench campaign calibrated the RX CPU wall in **E-cycles per byte**: +640 = certified clean (31250 v2), 480 = untested, 320 = chord failure +(62500), 160 = collapse (125000). The 7.3728 can mapped onto that +ladder: + +| rate | BAUD | cyc/byte | prognosis | +|---|---|---|---| +| 9600 | `$12` | 1920 | native production — now *exactly* 9600 (was 9615) | +| 19200 | `$11` | 960 | 2× stock, more margin than certified FastRIO | +| **28800** | **`$20`** | **640** | **3× stock, byte-identical margin to certified 31250 v2 — "FastRIO-Std"** | +| 38400 | `$10` | 480 | 4× stock, the untested middle — needs the mash ladder | +| 57600 | `$01` | 320 | = shelved-62500 margin — expect chord RX overrun; skip | + +9.8304 is tempting (its 38400 = BAUD `$02` sits at the certified +640-cycle margin *and* beats FastRIO wall-clock by 23%) but is a 23% +bus overclock. Photo zoom (2026-07-24) identifies the CPU as a +**TMP68HC11E1T, PLCC-52, in a socket** — date code 9917, so a '99 +service replacement on the '94 board. The Toshiba speed grade is still +unverified against its datasheet, but the socket changes the calculus: +a Motorola **MC68HC11E1CFN3** (3 MHz-rated, same E1 core/pinout) would +be a drop-in that makes 2.4576 MHz bus *in spec* if the 9.8304 path is +ever wanted. The rest of the board doesn't care at 2.4576 MHz: +AM27C512-150 and LH52B256-70 timing margins are ample, the GAL is +15 ns, the HCTL-2016 clock input is good to 14 MHz, and the MAX232 leg +was bench-proven at 125000. + +Required firmware deltas for any can swap: + +1. **The BAUD byte is mandatory, not optional** — with a 7.3728 can + the stock `$30` (÷13) yields 8861 baud and nothing talks. New + `BAUD_OPTS` entries in `make_patch.py` per target rate. +2. **Re-derive the ACK-wait operand** (edit 4, byte-clocked): holding + ~12.8 ms of wall clock needs 25 ticks (`$19`) at 19200, 37 (`$25`) + at 28800, 50 (`$32`) at 38400. +3. Everything else (wedge fix, E0 gate, check-repaint) is + rate-agnostic; keep-alives are byte-clocked and scale automatically. + Timer-derived behavior (lamp PWM, debounce) shifts −7.8% — expected + imperceptible; eyeball once on the bench. +4. Full acceptance ladder per rate (`RioSerialMonitor --mash`). + +Sourcing (checked 2026-07-24): the exact replacement exists as a +current catalog part — **ECS-100AX-073** (ECS Inc.), 7.3728 MHz, +full-size DIP-14 4-lead metal can, 5 V TTL, ±100 ppm, 0-70 °C: DigiKey +`X953-ND`, $3.24 @ qty 1, ~2,470 in stock, active part (Mouser carries +the ECS-100AX line too). The stock can (a "DXL10401A-2" full-size can +by U4) is soldered, not socketed — fit a machined DIP-14 socket during +the swap so the 8.000/7.3728/9.8304 cans become plug-in and the mod is +reversible. Buy spares; the can is the one part the whole plan hangs +on. + +**Recommended target: 7.3728 can + 28800 image ("FastRIO-Std")** — the +certified FastRIO per-byte CPU margin at ~92% of its wall-clock speed, +reachable by *any* UART ever made, including the period pods' 16550s. +That is the prerequisite for ever raising the *native-game* link rate, +which 31250 can never satisfy. A modified board still serves stock +cabinets, since exact 9600 stays in the ladder. + +### Why the original design used 8 MHz, not 7.3728 (hypothesis) + +No mystery, and mildly elegant — ranked by likely weight: + +1. **The ÷13 prescale is Motorola's silicon answer to exactly this.** + The HC11's odd {1,3,4,13} prescale set exists so the rated 8 MHz + crystal still yields standard-ish baud rates; the datasheet's baud + table lists 9600 @ 2 MHz (BAUD `$30` — the exact byte at `$D62B`) + as a blessed configuration with the +0.16% error footnoted. There + was no decision to make. Contrast the 8051, whose timer-based baud + generator has no ÷13 trick — hence that world's 11.0592 MHz crystal + habit. HC11 designers never formed the habit because the chip had + solved it. +2. **8 MHz is the rated maximum, and this board earns its clock** — + bit-banged pod bus via the `$A010` latch, eight port scans, lamp + PWM, five encoder counters, an SCI interrupt per byte. Trading 7.8% + CPU for baud exactness bought nothing in 1994. +3. **9600 was the requirement's ceiling, not a constraint being + fought** — tiny packets, ~18 Hz host polling, byte-clocked retry + windows. A baud-friendly crystal only pays off above 9600; this + restoration is the first to want that. +4. **Availability and habit** — 8.000 MHz cans were the commonest and + cheapest (good fleet-spares story), every Motorola EVB/app note ran + 8 MHz, and 500 ns cycles make hand-counted assembly delay loops + pleasant. + +The fingerprint: a fixed 9600 requirement satisfied "for free" by the +chip, so the clock defaulted to maximum rated speed on the commonest +part. The 9615 wart is the datasheet recipe, not a weighed compromise +— and it's why the can swap is such a clean retrofit: it makes the +crystal choice the original designers never had a reason to make. diff --git a/restoration/rio-firmware/baudscan.py b/restoration/rio-firmware/baudscan.py new file mode 100644 index 00000000..cfafd464 --- /dev/null +++ b/restoration/rio-firmware/baudscan.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Exhaustive HC11 SCI rate scan vs 16550-standard rates, per candidate +oscillator can. Evidence behind the "Standard-rate (16550) feasibility" +section of RIOv4_2-ANALYSIS.md. + +HC11 SCI baud = E / (16 * SCP * 2^SCR), E = can/4, +SCP (prescale) in {1,3,4,13}, SCR (divider) 2^0..2^7. +16550 standard = 1.8432MHz / (16 * divisor), divisor integer >= 1. +A pairing is workable if |error| <= 2.0% (conservative per-link UART +budget; >3% is hopeless, 2-3% marginal). From the stock 8 MHz can the +only in-tolerance pairings are 9615<->9600 (stock, the /13 prescale) +and 10417<->115200/11 (+8.3% throughput, pointless) -- every faster +rate carries the +8.51% residue of 125000/115200 = 625/576. +""" +STD = [9600, 14400, 19200, 28800, 38400, 57600, 115200] # 16550-reachable +XTALS = { + "8.000000 (stock)": 8_000_000, + "7.372800": 7_372_800, + "9.830400": 9_830_400, + "11.059200": 11_059_200, + "12.288000": 12_288_000, + "14.745600": 14_745_600, +} +for name, x in XTALS.items(): + e = x / 4 + print(f"\n=== can {name} MHz -> E = {e/1e6:.4f} MHz ===") + rows = [] + for scp, scpbits in [(1, 0b00), (3, 0b01), (4, 0b10), (13, 0b11)]: + for scr in range(8): + rate = e / (16 * scp * (1 << scr)) + if rate < 9000: + continue + baudreg = (scpbits << 4) | scr + best = min(STD, key=lambda s: abs(rate - s) / s) + err = (rate - best) / best * 100 + cyc = e / (rate / 10) # E-cycles per 10-bit byte + rows.append((rate, baudreg, best, err, cyc)) + for rate, baudreg, best, err, cyc in sorted(rows, reverse=True): + flag = " <== EXACT" if abs(err) < 0.01 else ( + " <-- ok" if abs(err) <= 2.0 else "") + print(f" BAUD=${baudreg:02X} {rate:9.1f} vs {best:6d} " + f"err {err:+6.2f}% {cyc:6.0f} E-cyc/byte{flag}") diff --git a/restoration/rio-hardware/README.md b/restoration/rio-hardware/README.md index 5f0ff7e3..853166fe 100644 --- a/restoration/rio-hardware/README.md +++ b/restoration/rio-hardware/README.md @@ -15,7 +15,7 @@ board), so these drawings post-date the Rev. 0/1 boards they describe. | P/N | Title | Rev photographed | Key silicon | |-----|-------|------------------|-------------| -| **1407** | RIO board ("CPU board") | Rev. 3 | Toshiba **TMP68HC11** @ 8 MHz, GAL20V8A decoder, 27C512 EPROM, LH52B256 32K SRAM, 5× HCTL-2016 | +| **1407** | RIO board ("CPU board") | Rev. 3 | Toshiba **TMP68HC11E1T** (PLCC-52, socketed, date 9917 — a '99 service part) @ 8 MHz, GAL20V8A decoder, 27C512 EPROM, LH52B256 32K SRAM, 5× HCTL-2016 | | **1401** | Buttons board | Rev. 0 | 2× UCN5821A lamp drivers, 2× 74HCT165, 74HCT164, 74LS00/05 | | **1402** | Keypad board | Rev. 1 | 2× 74HCT164, CD74HCT165, 74LS00/05 | | **1408** | Display board ("RIO external display") | Rev. 1 | Intersil **ICM7228B** 8-digit driver, 8× LTS313A 7-segment | @@ -25,7 +25,14 @@ Plus the cockpit position sensors: **HP HEDS-5700** two-channel quadrature optical encoders (option code **I** = 512 counts/rev; the photographed unit is date-coded 9515, Singapore), one per analog axis, read by the five HCTL-2016 counters on the RIO board (`OPT1`–`OPT5` = stick X/Y, throttle, left/right -pedals). +pedals). **There is no room for a sixth.** Each counter consumes two +74LS138 outputs (`OE` + `RST`), so U8/U9 decode 8 channels and three +selects on U9 are spare — but the PCB carries exactly five HCTL-2016 +footprints (U12–U16) and sheet 1 draws exactly five symbols, U16 last. +Below OPT5 the board is bare: no silkscreen outline, no plated holes, +unlike the genuinely unpopulated U31 and R43/R44 sites. Adding axes +means a daughterboard off U9's spare selects, or a respin — details in +[`../rio-firmware/IO-ARCHITECTURE.md`](../rio-firmware/IO-ARCHITECTURE.md). ## RIO board 1407 (schematic sheets 1–3, `RIO_1407`) @@ -41,8 +48,22 @@ the serial side is the hardware behind the **DTR reset pulse** described in [PROTOCOL.md](https://gitea.mysticmachines.com/VWE/RIOJoy/src/branch/main/docs/PROTOCOL.md) §1. The "display interface" block (ALS541 buffers, HEADER14) exports latched data lines `HD[00:04]` + digit address `AS0-2` to the display board — this is a memory-mapped port, *not* the pod -bus. A 22V10 site (U31) is drawn mostly N/C and is **unpopulated** on the -photographed board. +bus. A 22V10 site (U31) is **unpopulated** on the photographed board — +and it is drawn on **sheet 3**, not here, as a *completely uncommitted* +spare: pins 1–11 all tie to a single rail that loops back on itself, +pins 14–23 all tie to the R42 (`PULLUP10` 2.7K) / R43 / R44 rail, and +pin 13 is `NC (1,2)`. No address, data, strobe or pod-bus signal +reaches it. **Nothing was ever assigned to U31** — it is reserved board +area with pull-ups so the empty site doesn't float, not a pre-routed +expansion hook, and populating it accomplishes nothing without new +wiring. R43/R44 are unpopulated for the same reason. The CPU clock is a 4-pin **canned oscillator +module** (8.000000 MHz, by U4), not a bare crystal — E-clock = 2 MHz, +which is what makes the SCI's 9615 ≈ 9600 baud (÷13 prescale) work and +every faster 16550-standard rate unreachable in firmware alone; that +can is the single part standing between the board and a standard-rate +speed upgrade (swap analysis: +[`../rio-firmware/RIOv4_2-ANALYSIS.md`](../rio-firmware/RIOv4_2-ANALYSIS.md), +"Standard-rate (16550) feasibility"). **Sheet 2 — Power & Reset Interface.** A MAX690 supervisor provides power-fail reset and a **watchdog (WDI)**; reset can also come from the @@ -81,6 +102,14 @@ and 74LS00 gates qualify `RD_SB`/`WR_SB` with that selection. This hardware addressing is what the 0x00–0x6F logical input map in [PROTOCOL.md](https://gitea.mysticmachines.com/VWE/RIOJoy/src/branch/main/docs/PROTOCOL.md) ultimately resolves to. +The firmware side of this bus — the `$A010` latch bit map, the 50-byte +scripts it replays, which port and `ADR. SEL.` address each of a pod's +9 buttons boards and 2 keypads answers on, and the scan's cycle cost — +is decoded in +[`../rio-firmware/IO-ARCHITECTURE.md`](../rio-firmware/IO-ARCHITECTURE.md). +Note it corrects one reading of the table above: `PORTE` carries +`DI[0..7]`, which is *why* there are exactly eight ports. + ## Peripheral boards - **1401 Buttons board** (`PBE_1401`) — eight 4-pin connectors (`J3`–`J10`), @@ -153,7 +182,7 @@ amplifier board 2026-07-19). | `buttons-board-1401-solder-side.jpeg` | `signal-2026-07-15-093045.jpeg` | Buttons board solder side, PCB etch NT 1-0 3695 | | `buttons-board-1401-component-side.jpeg` | `signal-2026-07-15-093051.jpeg` | Buttons board: 2× CD74HCT165E, 74HCT00AP, 2× UCN5821A-type drivers, 74HCT164N, 8× lamp/button connectors, "P/N 1401 REV. 0" | | `rio-board-1407-solder-side.jpeg` | `signal-2026-07-15-093058.jpeg` | RIO board solder side, PCB etch NT 3-0 4996 | -| `rio-board-1407-component-side.jpeg` | `signal-2026-07-15-093105.jpeg` | RIO board: TMP68HC11 QFP, GAL20V8A ("5764" sticker), AM27C512 EPROM, LH52B256 SRAM, 5× HCTL-2016 + OPT1–5, J11–J18 pod ports, status LEDs, "P/N 1407 REV. 3" | +| `rio-board-1407-component-side.jpeg` | `signal-2026-07-15-093105.jpeg` | RIO board: TMP68HC11E1T (PLCC-52 in socket, 9917E8Z — not QFP as first cataloged), 8.000000 MHz oscillator can ("DXL10401A-2"), GAL20V8A ("5764" sticker), AM27C512 EPROM, LH52B256 SRAM, 5× HCTL-2016 + OPT1–5, J11–J18 pod ports, status LEDs, "P/N 1407 REV. 3" | | `amp-board-1413-component-side.jpeg` | `signal-2026-07-19-134932.jpeg` | Quad Amplifier Board full view: Pyramid PB-150P module, corner crossovers, "P/N 1413 REV. 1", J5 pinout silkscreen | | `amp-board-1413-solder-side.jpeg` | `signal-2026-07-19-134926.jpeg` | Amplifier board solder side, PCB etch NT 1-0A 3695 | | `amp-board-1413-pyramid-inputs.jpeg` | `signal-2026-07-19-134921.jpeg` | Pyramid input face: HIGH INPUT connectors, LEVEL pots, LOW INPUT RCAs; front-corner crossovers |