diff --git a/_plans/poc-listener-printer-PLAN.md b/_plans/poc-listener-printer-PLAN.md index 926b51e..740d63f 100644 --- a/_plans/poc-listener-printer-PLAN.md +++ b/_plans/poc-listener-printer-PLAN.md @@ -62,11 +62,14 @@ From the research, a pure listener has a radically reduced surface: |-------|-------------------------|--------------| | ATN | C64 drives; we react | **input** | | CLK | Talker (C64) drives during data; we sample | **input** | -| DATA | Listener drives (ack ATN, ready, byte-ack, EOI-ack) | **output** (open-collector) | +| DATA | Listener drives the handshake (ack ATN, ready, byte-ack, EOI-ack); talker drives the bits we sample | **bidirectional** (open-drain) | | RESET | C64 drives; we react | **input** | | SRQ | unused on stock C64 | not connected | -So we drive **exactly one** bus line (DATA) and read three. No turnaround, no +So we **actively drive exactly one** bus line (DATA, open-drain) and sense the +rest — but DATA doubles as an *input* during bit sampling (we release it to Hi-Z, +then read the talker's bits), which is why it is a single bidirectional pin (§3). +No turnaround, no EOI generation, no 60 µs talker timing. The only timing the device generates is the 1 ms ATN ack and the per-byte ack — both forgiving. The tight constraint is *sampling* the C64's ~20 µs bit clock while receiving (see §6). @@ -75,34 +78,50 @@ the 1 ms ATN ack and the per-byte ack — both forgiving. The tight constraint i ## 3. Hardware -### 3.1 Interface circuit (per the research, simplified for one output) +### 3.1 Interface circuit (single bidirectional DATA pin, sd2iec-style) -- **DATA (drive):** open-collector driver. Two equivalent options: - - **7406/74LS06** inverting OC buffer: Pi GPIO → 7406 input → bus. Pi HIGH ⇒ bus - pulled low (asserted). *(Software must invert.)* - - **NPN transistor** (2N3904/BC547): Pi GPIO → 1 kΩ → base; emitter → GND; - collector → DATA line. Pi HIGH ⇒ transistor on ⇒ bus low. Same inversion. - - Use whichever is on hand; the 7406 also gives spare drivers for later (CLK, - talker mode). Recommend the **7406** to match the eventual full device. -- **ATN, CLK, RESET (sense):** resistor divider 5 V→~3.1 V into each Pi input - (e.g. 3.3 kΩ top / 2.2 kΩ to GND). **No inversion** in sensing: bus low (0 V, - asserted) ⇒ Pi reads LOW. -- 100 nF bypass cap across the 7406 VCC–GND. Common GND between C64 IEC pin 2 and Pi. +Design decision: DATA is a **single bidirectional pin** doing **open-drain +emulation**, mirroring the sd2iec one-line scheme rather than a separate +drive-pin + sense-pin pair. Because the Pi is **not 5 V-tolerant**, the line goes +through a **bidirectional MOSFET level shifter** (BSS138 + pull-ups — the NXP +AN10441 / common "logic level converter" topology). sd2iec needs no shifter only +because its AVR runs at 5 V; the 3.3 V Pi does. + +- **DATA (single pin, bidirectional):** GPIO 18 ↔ level shifter ↔ IEC DATA. The Pi + pin is never a push-pull driver of the bus; it emulates open-drain: + - **Assert** (pull bus low): GPIO 18 = **output LOW** → shifter pulls bus to 0 V. + - **Release / read**: GPIO 18 = **input (Hi-Z)** → the bus pull-ups define the + level and GPIO 18 reads the bus. **Non-inverting:** bus low (asserted) ⇒ Pi + reads LOW. (Releasing to Hi-Z is what lets us sample the talker's bits in §6.) + - Direction is flipped in the hot path via the BCM `GPFSEL` register — a single + register write, ~tens of ns, negligible against the 20 µs bit window. +- **ATN, CLK, RESET (sense, input only):** route through the **same kind of + shifter** (remaining channels of a 4-channel BSS138 board). Pi side always input. + **Non-inverting:** bus low (asserted) ⇒ Pi reads LOW. *(A resistor divider also + works for these input-only lines, but reusing the shifter gives a uniform, fully + in-spec 3.3 V swing and sidesteps the marginal divider voltages flagged in the + kernel research.)* +- **Recommended part:** one 4-channel bidirectional level-shifter board carries all + four lines (DATA bidirectional; ATN/CLK/RESET input-only). No 7406, no transistor, + no hand-matched dividers — low side to Pi 3.3 V, high side to bus 5 V. +- 100 nF bypass cap on the shifter VCC pins. Common GND between C64 IEC pin 2 and Pi. - **Do not connect bus 5 V to any Pi pin directly — Pi GPIO is not 5 V tolerant.** ### 3.2 Pin map (Pi Zero 2 W, 40-pin header) | IEC signal | Pi GPIO (BCM) | Header pin | Direction | Logic note | |------------|---------------|------------|-----------|------------| -| ATN | GPIO 2 | 3 | input (divider) | bus low = Pi low = asserted | -| CLK | GPIO 17 | 11 | input (divider) | bus low = Pi low = asserted | -| DATA (in) | GPIO 18 | 12 | output → 7406 → bus | Pi HIGH = bus asserted (inverted) | -| RESET | GPIO 3 | 5 | input (divider) | bus low = Pi low = asserted | -| GND | — | 6 (or any) | — | tie to IEC pin 2 | +| ATN | GPIO 2 | 3 | input (via shifter) | bus low = Pi low = asserted | +| CLK | GPIO 17 | 11 | input (via shifter) | bus low = Pi low = asserted | +| DATA | GPIO 18 | 12 | **bidirectional (via shifter)** | open-drain: assert = output LOW, release/read = input. bus low = Pi low = asserted | +| RESET | GPIO 3 | 5 | input (via shifter) | bus low = Pi low = asserted | +| GND | — | 6 (or any) | — | tie to IEC pin 2 and shifter GND | -> Convention used in code: a helper layer converts electrical reads/writes into -> **logical** `asserted=True / released=False` so the rest of the code reasons in -> the protocol's true/false sense and never juggles the inversions inline. +> Convention used in code: a helper layer (`iec_lines.h`) converts electrical +> reads/writes into **logical** `asserted=True / released=False`. With the +> non-inverting level shifter the mapping is direct — assert ⇒ DATA GPIO output +> LOW, release ⇒ DATA GPIO input (Hi-Z), and a Pi LOW read on any line means +> asserted. No 7406 inversion to juggle. --- @@ -205,9 +224,11 @@ never juggles inversions inline (mirrors the original userspace plan, now in C): - `atn_asserted()` — true when ATN GPIO reads low - `clk_asserted()` — true when CLK GPIO reads low -- `data_in()` — read DATA line (the talker drives it during bit transfer) -- `data_assert()` / `data_release()` — drive DATA true / float it; encapsulates - the 7406 inversion (assert ⇒ Pi GPIO HIGH) +- `data_in()` — read DATA line (the talker drives it during bit transfer; only + valid after `data_release()` has set the pin to Hi-Z input) +- `data_assert()` / `data_release()` — assert DATA (set GPIO 18 **output LOW**) / + release it (set GPIO 18 **input / Hi-Z**, letting the bus float high). Open-drain + emulation through the level shifter; **non-inverting** (no 7406 to invert) - `reset_asserted()` — true when RESET GPIO reads low ### 5.2 Userspace record stream (`device.py`) @@ -416,7 +437,7 @@ Requirements: | Even in-kernel, busy-poll mid-byte gets preempted/IRQ'd and garbles a bit | Medium | Disable local IRQs/preemption for the duration of a byte; if still marginal, isolcpus + steer other IRQs away (raspbiec pattern); last resort `PREEMPT_RT`. Quantify in Phase 0.5. | | Keeping IRQs off too long for a byte harms system stability | Medium | Bound the off-window to one byte (~few hundred µs); re-enable between bytes. A design constraint to validate in Phase 0.5. | | Kernel module build/ABI churn against Raspberry Pi OS kernel | Medium | Build against installed kernel headers; pin kernel version; document in `kernel-notes.md`. | -| 7406 inversion / divider miswire | Medium | `iec_lines.h` logical layer isolates it; module `selftest` ioctl toggles DATA and reads ATN/CLK with the C64 off. | +| Level-shifter wiring / DATA direction-flip bug | Medium | `iec_lines.h` logical layer isolates assert/release/read; module `selftest` ioctl drives DATA (output LOW), releases to Hi-Z and reads it back, and reads ATN/CLK with the C64 off. Confirm Hi-Z release truly floats so the talker's bits read through. | | C64 has JiffyDOS | Low (printer rarely uses it) | Out of scope; document. Standard protocol still works for a printer device. | | ATN response missed | Low | Hardware IRQ pulls DATA in the ISR; 1 ms budget is ample for kernel interrupt latency. |