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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}")
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user