Skip to content

ADR-0017: Realtime I/O Coprocessor (Pico 2)

⚠ PARTIALLY SUPERSEDED by ADR-0019 on 2026-04-24.

ADR-0017’s #1 forcing function — “Linux on the Pi Zero 2 W cannot reliably bit-bang the DMG cartridge bus” — assumed ADR-0013 (DMG 32-pin pinout + MBC5 mapper) as the canonical cartridge format. Later that same day (2026-04-24), ADR-0019 superseded ADR-0013 by moving the cartridge to a full-size SD card carried in a custom clamshell sled, read by the Pi as USB mass storage through the internal hub bridge IC. There is no longer a DMG cartridge bus.

The Pico 2’s other two responsibilities — YM2149 PSG synthesis with I2S out to MAX98357A (forcing function: audio underrun risk under rendering load) and the SSD1322 CIPHER-LINE OLED driver (forcing function: ticker cadence under Pi load) — are independent of the cart bus and remain in scope under this ADR. The Pico 2 is still part of the canonical hardware; only the DMG-cart-bus responsibility was removed.

What this means in practice:

  • §“Decision item 2 (Responsibility split)” — the DMG cartridge bus row is obsolete. The other rows (PSG, MAX98357A I2S, SSD1322 OLED) still apply.
  • §“Decision item 3 (Pico-side responsibilities, in detail)” — the DMG cartridge bus controller subsection is obsolete. The PSG and OLED subsections still apply.
  • §“Decision item 4 (UART command protocol)” — the cart-related frame types (CART_DETECT, CART_READ_BANK, CART_READ_BANK_DATA, CART_READ_SRAM, CART_READ_SRAM_DATA, CART_WRITE_SRAM, CART_RESET) are obsolete. The PSG and OLED frame types remain. The cart frame-type retirement is a follow-up cleanup against docs/architecture/KN-86-Coprocessor-Protocol.md.
  • §“Forcing function #1 (DMG/MBC5 bus timing)” — historically accurate at the moment ADR-0017 was accepted, but no longer applies post-ADR-0019. Retained as design history. Forcing functions #2 (audio underrun) and #3 (CIPHER-LINE ticker cadence) are unaffected.

The body below is retained as a historical artifact and as the source of truth for the still-in-scope PSG + OLED responsibilities. Do not cite the cart-bus sections as current; do not edit body content for the supersession.

Partially superseded by: ADR-0019 — Cartridge Storage and Physical Form Factor (SD-Card Sled). Removes the DMG cartridge bus responsibility from the Pico 2; audio + OLED responsibilities remain. Related: CLAUDE.md Canonical Hardware Specification (gains a Coprocessor row), ADR-0011 (firmware update system — gains Pico image), ADR-0013 (cartridge physical format — DMG/MBC5 bus was the forcing function for the cart-bus role; superseded by ADR-0019), ADR-0014 (display profile — main display path unchanged), ADR-0015 (CIPHER-LINE — OLED driver path migrates from Pi to Pico; FFI surface unchanged), ADR-0019 (cartridge storage and form factor — supersedes ADR-0013 and removes the cart-bus role from this ADR).


ADR-0013 committed the KN-86 to a Game Boy DMG 32-pin cartridge slot with MBC5 mapper semantics. That decision implicitly creates a problem this ADR resolves: Linux on the Pi Zero 2 W cannot reliably bit-bang the DMG cartridge bus. MBC5 expects cycle-accurate reads against a ~4.19 MHz reference; userspace GPIO from a non-realtime kernel introduces scheduler jitter that breaks bus timing well before the carts care about throughput. Every mature Pi-based retro handheld and every serious Game Boy cartridge reader (GBxCart, Joey Jr, Insideo) solves this the same way: a microcontroller with PIO or equivalent state-machine I/O sits between the host and the cartridge edge connector.

Josh’s stated motivation for considering this now, beyond the cart bus, is freeing Pi compute headroom for the gameplay surface evolving toward ASCII animation, algorithmic art, and Clip playback (see docs/architecture/KN-86-Clip-System.md). The Pi Zero 2 W is not compute-bound on the nominal 80×25 amber render path — an A53 @ 1 GHz crushes that workload. What the Pi is exposed to is scheduler-jitter risk on the realtime peripherals: the 44.1 kHz I2S audio callback, the SSD1322 OLED ticker (ADR-0015), and the cart bus. As rendering pressure grows, audio underruns and ticker hitches become the failure mode — not raw compute exhaustion.

Both problems share a solution: move the realtime I/O off Linux’s plate.

  1. DMG/MBC5 bus timing is infeasible from Linux userspace. ADR-0013 is accepted; the cart bus has to work; the hardware path it requires is a microcontroller. This is the load-bearing reason.
  2. Audio underrun risk under rendering load. Pi-side YM2149 synthesis at 44.1 kHz competes with Fe VM execution and framebuffer composition for kernel-scheduler attention. Under heavy Clip playback or algorithmic art workloads, ALSA underruns are predictable. Dedicated audio silicon eliminates the failure mode entirely.
  3. CIPHER-LINE ticker cadence (ADR-0015 §2 Row 2/3 scrollback animation). The current ADR-0015 routing has the Pi driving SPI0 directly to the SSD1322 and running the ticker scroll on a Linux timer. That’s functional but periodically hitches when Linux is busy. An autonomous OLED driver makes the cadence rock-steady regardless of Pi load.
  • The Pi Zero 2 W is the canonical processor (CLAUDE.md). This ADR adds a coprocessor; it does not split the hardware target. The Pi remains primary.
  • 3000 mAh / ~20-hour runtime envelope (CLAUDE.md Battery row). Coprocessor power delta must be measured, documented, and accepted.
  • Pelican 1170 shell is unmodified (CLAUDE.md Case row). Coprocessor mounts on the internal sub-board with the existing Pi; no case work.
  • Cart authoring stays uniform — cartridges still target the NoshAPI FFI (ADR-0005). The Pico is invisible to cart authors; only the firmware-internal driver path changes.
  • The USB hub on the Pi is already claimed for the keyboard MCU (and future inputs). The Pi↔Pico link must not consume a USB port.
  • ADR-0014’s main-display rendering pipeline (Pi → HDMI → Elecrow) is untouched. The coprocessor handles I/O, not pixels.
  • Spec Hygiene Rule 3: this ADR’s Documentation Updates section is the authoritative checklist for stale-reference cleanup, including amendments to ADR-0011 and ADR-0015.

The KN-86 adopts a Raspberry Pi Pico 2 (RP2350) as a realtime I/O coprocessor mounted on the internal sub-board alongside the Pi Zero 2 W. The Pico owns three responsibilities and the Pi gives up the corresponding peripheral driver paths.

Concrete commitments:

  • Module class: Raspberry Pi Pico 2 (RP2350). Dual-core Cortex-M33 @ 150 MHz, 520 KB SRAM, 4 MB onboard flash, 26 usable GPIOs, 12 PIO state machines (3 banks × 4). Pin-compatible with the original Pico (RP2040) for prototype bring-up flexibility.
  • Why RP2350 and not RP2040: the DMG cart bus needs ≥15 address pins + 8 data + 6 control = 29 GPIOs at minimum without multiplexing. RP2040’s 26 GPIOs force a shift-register expansion (more parts, more timing complexity). RP2350’s pin budget fits cleanly. Power delta vs RP2040 is small (~5–15 mA depending on workload); the GPIO count and the extra PIO headroom for future protocols (link cable, shoulder button matrix, future cartridge enhancements) earn the slot. See §Options Considered for the full RP2040-vs-RP2350 trade.
  • Mount: internal sub-board, co-located with Pi Zero 2 W. No Pelican shell modification.
  • Power: ~25–55 mA active typical at 150 MHz with the workload described in §3 (PIO state machines + I2S DMA + main-loop ticker). Idle (no audio, no animation, no cart access) drops to <5 mA via dormant mode. This pushes the Pi Zero 2 W’s typical-load draw from 120–180 mA to ~145–235 mA, shifting the runtime band from ~20 h toward ~13–17 h depending on workload. This is a real envelope hit and Josh’s call to accept; see §Trade-off Analysis.
SubsystemPre-ADR (current/planned)Post-ADR
DMG cartridge bus(infeasible from Pi)Pico — PIO state machine implements MBC5 read/write
YM2149 PSG synthesisPi (ALSA + userspace synth)Pico — runs YM2149 emulator, generates 44.1 kHz mono PCM
MAX98357A I2S audioPi GPIO18/19/21Pico — I2S out via PIO + DMA
SSD1322 CIPHER-LINE OLEDPi SPI0 (per ADR-0015 §1)Pico — Pico-side SPI; runs ticker animation autonomously
HDMI to Elecrow primary displayPiPi (unchanged)
USB hub (keyboard MCU + future)PiPi (unchanged)
microSD storage / Linux rootfsPiPi (unchanged)
Fe VM execution + nOSh orchestrationPiPi (unchanged)
Mission board, deck state, save/loadPiPi (unchanged)

Architecture summary: Pi = compute brain (Fe VM, nOSh, HDMI render). Pico = realtime I/O front-end (cart, audio, OLED).

  • DMG cartridge bus controller. PIO state machine drives the 32-pin edge connector. Implements MBC5 bank-switching natively (per ADR-0013). Accepts high-level commands from Pi over UART: cart-detect, cart-read-bank N, cart-read-sram offset len, cart-write-sram offset bytes, cart-eject-cleanup. Pico pushes CART_INSERTED / CART_REMOVED events to the Pi as backchannel notifications.
  • YM2149 PSG synthesis + I2S out. Pico maintains the full YM2149 register state (14 registers + envelope generator + LFSR noise). Pi sends register-write commands over UART (psg-reg N V); Pico applies them and continues generating samples. Output: 44.1 kHz mono PCM to MAX98357A via PIO-driven I2S + DMA. This is exactly how original ST/Amiga/MSX audio chips worked — host writes registers, dedicated silicon synthesizes. The fiction matches the architecture.
  • SSD1322 CIPHER-LINE driver. Pico drives the 4-wire SPI bus to the OLED. Accepts high-level commands over UART: oled-set-row R "string", oled-scroll-row R direction px-per-sec, oled-fill rect mode, oled-clear. Pico runs ticker scroll animations on its own timer; Pi sends a set-row once and walks away. Frame buffer lives on the Pico (~2 KB for 256×64 mono).

4. Pi↔Pico communication: UART command protocol

Section titled “4. Pi↔Pico communication: UART command protocol”
  • Physical link: UART at 1 Mbps over a 3-wire connection. Pi GPIO14/15 (UART0) ↔ Pico GPIO0/1 (UART0). Common ground.
  • Why UART and not SPI: UART is sufficient (1 Mbps = ~100 KB/s, far above the actual command-rate needs ~1–5 KB/s peak), simpler to integrate on the Linux side (/dev/serial0 userspace daemon), full-duplex without a chip-select line, and frees Pi SPI0/SPI1 for future peripherals. SPI would buy raw throughput we don’t need at the cost of a more complex protocol.
  • Frame format: length-prefixed binary frames. [len_lo, len_hi, type, seq, payload..., crc16_lo, crc16_hi]. CRC-16/CCITT over [type, seq, payload]. Sequence number for request/response correlation on read commands; fire-and-forget commands (PSG register writes, OLED ticker updates) ignore it.
  • Frame types (v0.1 set): HELLO, VERSION_QUERY, VERSION_RESPONSE, CART_DETECT, CART_READ_BANK, CART_READ_BANK_DATA, CART_READ_SRAM, CART_READ_SRAM_DATA, CART_WRITE_SRAM, CART_RESET, PSG_REG_WRITE, PSG_BULK_WRITE, PSG_RESET, OLED_SET_ROW, OLED_SCROLL_ROW, OLED_FILL, OLED_CLEAR, EVENT (Pico→Pi backchannel: CART_INSERTED, CART_REMOVED, BUFFER_OVERFLOW, INTERNAL_ERROR), ERROR (Pico→Pi only). CART_RESET, CART_READ_SRAM_DATA, and PSG_BULK_WRITE were added as v0.1 amendments after gap-resolution review on 2026-04-24.
  • Versioning: VERSION_QUERY/VERSION_RESPONSE exchange on Pi boot. Pi knows minimum compatible Pico firmware version; refuses to start nOSh if the Pico is older than the minimum. Hard fail with a clear error on Row 24 of the primary display.
  • Protocol detail spec: docs/architecture/KN-86-Coprocessor-Protocol.md (the F1 deliverable, drafted 2026-04-24). This ADR commits to the shape (UART, length-prefixed binary, CRC-16, the frame type set above). The protocol spec commits the bytes — every payload layout, error code, timeout, recovery flow, and the v0.1 gap-resolution log.
  • Pico powers on alongside the Pi. Boots from internal flash in <100 ms. Initializes PIO state machines, opens UART, waits for HELLO from Pi.
  • Pi powers on, boots Linux from microSD (~5–10 s to userspace). nOSh starts, sends HELLO over UART, waits for response. If Pico does not respond within 1 s, Pi retries up to 3 times, then fails to a clear error state on Row 24.
  • Version handshake follows HELLO. Mismatch → hard fail with operator-visible error.
  • Once handshake clears, Pi begins normal nOSh boot sequence. Cart-insertion events from Pico are processed normally.
  • Pico firmware lives in the Pi filesystem at /lib/firmware/kn86-pico.uf2.
  • Two Pi GPIOs control Pico’s BOOTSEL and RESET pins (proposed: GPIO22 = BOOTSEL, GPIO23 = RESET; TBD during bring-up if conflict).
  • Update workflow: Pi pulses BOOTSEL low, asserts RESET, releases RESET, waits 100 ms for Pico to enumerate as USB MSC; copies UF2 to the mass-storage mount; releases BOOTSEL; resets again. Pico boots into the new firmware, completes version handshake, normal operation resumes.
  • ADR-0011 (Pi Zero 2 W firmware update system) is the parent system. ADR-0011 needs an amendment to incorporate the Pico image into the OTA update bundle so Pi+Pico firmware updates atomically. See §Documentation Updates.

The Pi gives up SPI0 (was driving SSD1322 per ADR-0015) and the I2S pins (was driving MAX98357A). It gains responsibility for:

  • UART0 TX (GPIO14) → Pico RX
  • UART0 RX (GPIO15) ← Pico TX
  • Pico BOOTSEL control (proposed GPIO22)
  • Pico RESET control (proposed GPIO23)

Net Pi GPIO change: +4 claimed (UART0 + 2 control), −5 freed (SPI0 4-wire + I2S BCLK/LRCLK/DOUT, plus DC/RST GPIOs that ADR-0015 had earmarked for SSD1322 routing). Slight net reduction in claimed Pi GPIOs.


Option A: All-on-Pi (no coprocessor, status quo)

Section titled “Option A: All-on-Pi (no coprocessor, status quo)”

Keep PSG, OLED, and cart bus all on the Pi. Use kernel-level GPIO drivers and tight scheduling discipline to mitigate jitter.

Rejected because: the cart bus alone is the dealbreaker. ADR-0013’s MBC5 timing budget is incompatible with any non-realtime OS — including Linux with PREEMPT_RT, which cannot guarantee sub-microsecond GPIO toggling under arbitrary workload. The audio and OLED issues are softer (they are jitter problems, not feasibility problems), but the cart bus is hard-impossible. Once we’re adding silicon to solve the cart bus, the marginal cost of consolidating audio + OLED onto the same chip is small (a few hundred lines of firmware, no additional BOM) and the headroom benefit is real.

Option B: Raspberry Pi Pico 2 (RP2350) coprocessor (ACCEPTED)

Section titled “Option B: Raspberry Pi Pico 2 (RP2350) coprocessor (ACCEPTED)”

Single Pico 2 owns cart bus + PSG + OLED. UART command link to Pi. Pi keeps HDMI, USB hub, microSD, compute.

Chosen because: the GPIO budget fits the cart bus without external multiplexing, the PIO headroom (12 SMs vs RP2040’s 8) accommodates future peripherals (link cable, shoulder buttons, debug bus), the M33 cores are more efficient per MHz than RP2040’s M0+, and the chip is current-gen with secure-boot and longer projected supply availability through the Q4 2027 ship target. The power delta vs RP2040 is small in actual measurement (~5–15 mA depending on workload); not enough to justify the GPIO compromise.

Single Pico owns cart bus + PSG + OLED, but with RP2040 instead of RP2350.

Rejected because: 26 usable GPIOs on RP2040 cannot accommodate the full DMG cart bus (15 address + 8 data + 6 control = 29) without external multiplexing via shift register. That works (it’s how some Game Boy readers handle it) but adds parts, adds a timing layer to the PIO program, and erodes the architectural cleanliness. Power savings vs RP2350 are ~5–15 mA at our duty cycle — material on a battery budget, but not enough to overcome the GPIO compromise. Reconsider if power measurement at bring-up shows RP2350 active draw materially above the ~40 mA midpoint estimate.

Option D: Cart-bus-only Pico, audio + OLED stay on Pi

Section titled “Option D: Cart-bus-only Pico, audio + OLED stay on Pi”

Add a Pico solely as a cart bus controller. Keep PSG and SSD1322 driver paths on the Pi.

Rejected because: half-measure. The cart bus chip is going in regardless of how we feel about audio/OLED. Once it’s there, consolidating PSG + OLED onto the same chip is essentially free incremental architecture (a few hundred lines of additional firmware, no new BOM line, no new sub-board real estate) and is the only path that delivers the headroom benefit Josh wants for ASCII animation / algorithmic art / Clip playback. Picking D leaves the audio underrun risk and ticker jitter exactly where they are.

Option E: Two coprocessors (cart on one Pico, audio+OLED on another)

Section titled “Option E: Two coprocessors (cart on one Pico, audio+OLED on another)”

Split realtime jobs across two Picos.

Rejected because: unjustified complexity. A single Pico 2 has ample compute, GPIO, and PIO headroom for all three jobs. Two-coprocessor designs make sense when one MCU can’t fit the workload or when isolation is a hard requirement (e.g., automotive safety, medical). Neither applies here.

Option F: Use a higher-end SoC (e.g., RK3566, ESP32-S3, BeagleBone)

Section titled “Option F: Use a higher-end SoC (e.g., RK3566, ESP32-S3, BeagleBone)”

Replace the Pico with a more capable host or co-host.

Rejected because: the Pico class is the right fit for the workload. Higher-end coprocessors solve problems we don’t have (multi-MB DRAM, full Linux, networking) at the cost of problems we don’t want (longer boot, firmware complexity, higher power, sourcing risk). The Pico is the canonical “realtime I/O microcontroller” and the workload is exactly its sweet spot.


DimensionA (Pi only)B (RP2350 ACCEPTED)C (RP2040)D (cart-only Pico)
DMG cart bus realtime feasibilityinfeasibleyesyes (with shift register)yes
Audio underrun risk under rendering loadhigheliminatedeliminatedunchanged (high)
OLED ticker jitterexistseliminatedeliminatedunchanged
Pi compute headroom freed for renderingbaselinemeaningfulmeaningfulminor
GPIO count fit for cart busn/acleanrequires shift registerclean
PIO state-machine headroomn/a12 SMs8 SMssimilar to B/C
BOM cost (per unit)$0+$5 (Pico 2 module)+$4 (Pico module)+$4
Active draw delta (typical)0+25–55 mA+20–40 mA+20 mA
Runtime band shift0~20 h → 13–17 h~20 h → 14–18 h~20 h → 15–19 h
Firmware codebase count1222
Cart-author-visible changen/anone (FFI unchanged)nonenone
ADRs amended0ADR-0011 + ADR-0015ADR-0011 + ADR-0015ADR-0011 only

The honest cost of Option B: ~3–7 hour runtime regression vs the current envelope. That’s the price for realtime guarantees on cart bus, audio, and OLED ticker. The cart bus alone justifies the chip; the audio and OLED bonuses are essentially free incremental architecture once it’s in. Josh should explicitly accept the runtime impact before this ADR moves to Accepted. If 13–17 h is unacceptable, paths to recover include: (i) larger battery (revisits the Battery row of CLAUDE.md and changes the case foam-cut; the Pelican 1170 has ~80 mm interior depth so a larger pack fits mechanically), (ii) aggressive Pico dormant-mode gating during idle (already in the design but worth measuring), or (iii) underclock the Pico when not playing audio.


  • DMG cart bus becomes feasible. The single most important consequence — without this, ADR-0013 has no implementation path on Pi-only hardware.
  • Audio underrun risk eliminated. YM2149 synthesis is now in dedicated silicon with deterministic 44.1 kHz output. Pi can be saturated with rendering work and audio stays clean.
  • CIPHER-LINE ticker cadence becomes rock-steady. Animation runs on the Pico’s own timer, immune to Linux scheduler jitter.
  • Pi compute headroom freed. PSG synthesis was 2–5% CPU on the A53; SPI bit-banging for OLED was 1–2%; cart bus polling would be 100% (and still wrong). Moving all three off Linux gives the rendering pipeline room to breathe.
  • Architectural cleanliness. Pi = compute + display, Pico = realtime I/O. Mature host+MCU pattern that Pi-based retro handhelds converge on. Easier to reason about, easier to test (the Pi can be exercised independently of the Pico via a stub coproc daemon, and the Pico can be exercised independently via UART loopback).
  • Future-proof PIO budget. 12 PIO state machines on RP2350 leave 8+ free after this ADR’s workload. Future protocols (link cable for multi-deck linking, shoulder-button matrix, custom cartridge enhancements like real-time clock) have a home without adding more silicon.
  • Cart authors see no change. NoshAPI FFI surface is identical; only the firmware-internal driver path changes. ADR-0015’s cipher-emit and aux-* primitives still target CIPHER-LINE; the call site just routes through a Pi userspace daemon → UART → Pico, instead of Pi userspace daemon → SPI0.
  • Runtime regression: ~3–7 hours off the ~20 h envelope. Mid-band ~16 h. This is the load-bearing cost. Josh’s explicit accept needed before this ADR moves to Accepted.
  • +$5 BOM per unit (Pico 2 module at small quantity; ~$4 if we go bare RP2350 chip on a custom sub-board at production scale).
  • Second codebase. New kn86-pico/ subdirectory in the repo, pico-sdk + cmake build, picotool flashing, separate version stream.
  • Paired firmware versioning. Pi firmware and Pico firmware ship as a matched pair; OTA bundle includes both. ADR-0011 amendment scope.
  • Hardware re-routing on prototype board. I2S pins move from Pi to Pico. SPI0 pins free up on Pi (Pico has its own SPI to OLED). DMG slot wires connect to Pico, not Pi. Sub-board layout work.
  • 2 additional Pi GPIOs claimed for Pico BOOTSEL/RESET control (offset by ~5 GPIOs freed from peripheral migration).
  • ADR-0011 amendment required. Firmware update system grows to cover Pico image atomically with Pi image.
  • ADR-0015 amendment required. CIPHER-LINE driver path moves to Pico. Spec values, FFI surface, and authoring contract are all unchanged — only the §1 “Interface: 4-wire SPI on the Pi Zero 2 W’s SPI0 bus” sentence becomes stale and needs editing.
  • Bring-up complexity. Stage 1b of the Assembly Plan in KN-86-Pi-Zero-Build-Specification.md gains a coprocessor sub-stage. UART handshake debugging, Pico flash workflow, joint power measurement.
  • F1. UART command protocol full spec. Frame schemas for every type, byte-level layouts, error codes, sequence-number semantics, backchannel event taxonomy. New doc at docs/architecture/KN-86-Coprocessor-Protocol.md, or ADR-0017 amendment if it stays compact. Embedded Systems agent scope.
  • F2. Pico firmware skeleton. New kn86-pico/ directory with pico-sdk + cmake build, UART command parser, PIO programs for cart bus + I2S, YM2149 emulator port from current Pi-side code, OLED driver. C Engineer scope, post-protocol-spec.
  • F3. Pi-side coprocessor daemon. Userspace daemon that owns /dev/serial0, exposes the command surface to nOSh as an internal API, handles backchannel events. C Engineer scope.
  • F4. Emulator coproc stub. kn86-emulator/src/coproc.c (new) — stubs the Pico interface so the desktop emulator continues to work without real hardware. PSG synthesis stays in psg.c for the emulator path; OLED rendering stays in oled.c. The stub just routes commands the same way the real Pico would, internally to the same emulator process. C Engineer scope.
  • F5. Hardware re-routing — schematic + PCB. Sub-board layout update for prototype build. Embedded Systems agent scope.
  • F6. ADR-0011 amendment. Pico image lands in firmware OTA bundle. Atomic Pi+Pico update flow. Embedded Systems agent scope.
  • F7. ADR-0015 amendment. Note CIPHER-LINE driver path moved to Pico per ADR-0017. FFI surface unchanged; only the SPI host changes. Trivial edit. PM scope.
  • F8. Power-draw measurement at Stage 1b bring-up. Validate the ~25–55 mA active estimate. If real draw exceeds the high end of that band materially, escalate to Josh — runtime envelope review.
  • F9. GPIO finalization. BOOTSEL (proposed GPIO22) and RESET (proposed GPIO23) pin assignments are TBD until prototype bring-up confirms no conflict with peripherals not yet inventoried.

Documentation Updates (REQUIRED — part of the decision, not aspirational)

Section titled “Documentation Updates (REQUIRED — part of the decision, not aspirational)”

Every file below must change in the same PR that lands this ADR, or in an explicit follow-on PR from the named agent. The audit agent enforces this list; failing to tick a box is treated as a live contradiction per Spec Hygiene Rule 3.

  • CLAUDE.md — Canonical Hardware Specification gained a Coprocessor row (RP2350 Pico 2, mounted internal sub-board, ~25–55 mA active typical, owns DMG bus + PSG + CIPHER-LINE driver). Battery row note updated to reflect runtime band shift to ~13–17 h pending bring-up measurement. Emulator Architecture diagram gained a coproc.c stub line. Stale §137 paragraph (RP2350-as-primary-archived) clarified to acknowledge RP2350-as-coprocessor under ADR-0017.
  • docs/architecture/adr/ADR-0017-realtime-io-coprocessor.md — this file. Status flipped to Approved.
  • docs/architecture/adr/README.md — index gained ADR-0015 (pre-existing bug fix — was missing from the table) and ADR-0017. ADR-0016 still on a feature branch; the ADR-0016 PR will add its own row when it merges.
  • docs/hardware/KN-86-Pi-Zero-Build-Specification.md — Hardware Topology diagram redrawn to show the Pico 2 between the Pi UART0 and the SPI/I2S/DMG-bus peripherals. Subsystem Roles table gained a Coprocessor row; Audio synthesis row updated to Pico-driven; Audio output row updated for I2S now from Pico. §2 CIPHER-LINE SPI subsection rewritten for Pico-driven SPI (Pi SPI0 freed). §3 Power Topology gains the coprocessor power-draw delta and the post-coprocessor runtime envelope. §4 Assembly Plan gained Stage 1c (Coprocessor bring-up) covering Pico flash, UART handshake, joint power measurement, cart-bus end-to-end test; Stage 1b and Stage 2 amended to point to Pico-side wiring.
  • docs/hardware/KN-86-Pi-Zero-Sourcing-Guide.md — BOM line 1b added for Raspberry Pi Pico 2 (RP2350) at $5/unit, qty 2, sourced from Adafruit / Pimoroni / Raspberry Pi direct. Electronics Core subtotal updated $191 → $201; Grand Total $446 → $456. Cost summary bench-rig bands shifted +$10. ADR-0017 reference added.
  • docs/KN-86-Platform-Design-Master-Index.md — ADR-0017 row added to Part II ADR list.
  • kn86-emulator/src/types.h — no changes required for v0.1; the coproc layer is an internal abstraction (per the original ADR statement). Confirmed during the doc-update sweep.

Delegated to Embedded Systems agent (separate PRs)

Section titled “Delegated to Embedded Systems agent (separate PRs)”
  • docs/architecture/adr/ADR-0015-cipher-line-auxiliary-display.md — §1 Interface section amended: SPI driver path now Pico 2 per ADR-0017; pre-coprocessor pin assignment retained for design history but marked superseded. All other ADR-0015 content (FFI surface, layout, exclusivity rule, capability flags, CIPHER-LINE row layout) is unchanged. Bundled into this PR rather than landing as a separate trivial PR.

Delegated to C Engineer agent (separate PRs, post-protocol-spec)

Section titled “Delegated to C Engineer agent (separate PRs, post-protocol-spec)”
  • kn86-pico/ (new directory) — Pico firmware: pico-sdk + cmake, UART parser, PIO programs, YM2149 emulator, OLED driver.
  • kn86-emulator/src/coproc.c (new) — emulator-side coprocessor stub.
  • Pi userspace daemon — /dev/serial0 owner, command-surface exposer.

A PR that lands this ADR without ticking the first six boxes (CLAUDE.md, this ADR, README.md, build spec, sourcing guide, master index) fails review. The amendment and code-implementation boxes close in their own follow-on PRs.


Why not just buy time on a real-time Linux kernel?

Section titled “Why not just buy time on a real-time Linux kernel?”

PREEMPT_RT and similar real-time Linux extensions reduce scheduler jitter from ~milliseconds to ~tens of microseconds. That’s a huge improvement, and not enough. MBC5 cart bus expects bus events on the order of single microseconds; PSG audio at 44.1 kHz tolerates ~22 µs jitter per sample before glitches are audible; OLED ticker is the only one PREEMPT_RT could plausibly handle. The cart bus is the hard constraint and PREEMPT_RT does not solve it.

Why YM2149 emulation belongs on the Pico, not the Pi

Section titled “Why YM2149 emulation belongs on the Pico, not the Pi”

This is the spiciest of the three migrations. The argument for keeping it on the Pi is that an A53 @ 1 GHz is overkill for YM2149 synthesis (the original chip ran at 1–2 MHz; an A53 has ~1000× the cycles/sec). The argument for moving it to the Pico is that synthesis cost isn’t the issue — output cadence is. A 44.1 kHz I2S sample stream cannot underrun. Once the Pi’s compute pressure rises (clip playback compositing, algorithmic art, large bitmap blits), the audio thread loses scheduling slots and underruns become the failure mode. Dedicated silicon eliminates the failure mode by construction. The Pico has plenty of compute headroom (M33 @ 150 MHz, single-digit MHz needed for YM2149 synth) and direct DMA access to its I2S output via PIO — it’s the right home.

Why PSG register state lives on both sides during transition

Section titled “Why PSG register state lives on both sides during transition”

In the steady state, PSG state lives on the Pico exclusively — the Pi sends register-write commands and forgets. During the migration period (and for emulator parity), the Pi’s psg.c continues to maintain a shadow register state used by the desktop emulator’s audio path. The desktop emulator never has a real Pico; it runs the synth in-process. The hardware target uses the Pico path. The two paths diverge cleanly behind the coproc.c interface.

What if Pico firmware crashes mid-mission?

Section titled “What if Pico firmware crashes mid-mission?”

Pi watchdog: if the Pico stops responding to UART commands (heartbeat ping every 5 s; 3 missed pings = degraded), the Pi initiates Pico reset via the GPIO23 reset line. Pico re-boots in <100 ms, version handshake completes, normal operation resumes. Audio glitches briefly during reset; the cart is held in an inert state until handshake recovers. Mission state on the Pi is preserved.

Pico continues running its current audio + OLED state for ~5 s after losing UART contact, then idles to silence + holds last OLED frame. On Pi reboot, version handshake re-establishes; Pi-side nOSh restores its state from the Universal Deck State page (per the Capability Model Spec); resumes commanding the Pico.


  1. Power-draw envelope at real workload. The ~25–55 mA active estimate is a datasheet-plus-PIO-program-extrapolation. Real draw on the actual workload (cart polling cadence, audio always-on, OLED ticker active) is TBD at Stage 1b bring-up. If real mid-band exceeds ~50 mA, runtime envelope revisit is the right escalation path.
  2. GPIO22/GPIO23 finalization for BOOTSEL/RESET control. Conflict possible with peripherals not yet inventoried. TBD during prototype bring-up.
  3. Bare RP2350 chip vs Pico 2 module on the production sub-board. Module is easier for prototype; bare chip is right for production (smaller footprint, lower BOM, no on-module USB we don’t use). Decision deferred to first production-tooling pass.
  4. UART vs USB CDC for Pi↔Pico link. UART chosen for simplicity and because USB ports are claimed for the keyboard hub. If a future use case needs the higher throughput of USB CDC (>1 Mbps), the Pico’s USB stack is available without hardware change. Not load-bearing now.
  5. Audio latency budget. Round-trip from Pi-issued PSG register write to audible tone: target <30 ms. Dominated by UART transit (~hundreds of µs at 1 Mbps), Pico command processing (~tens of µs), and the I2S output buffer depth (configurable; 5–20 ms typical). TBD measurement at Stage 1b to confirm the budget holds.
  6. CIPHER-LINE animation primitive scope. v0.1 supports set-row, scroll-row, fill rect, clear. Future primitives (blink, fade-in via dither, sprite-like icon update) are deferrable to follow-on protocol versions without breaking v0.1 carts.
  7. Fictional positioning of the coprocessor. Does the Kinoshita Electronics Consortium fiction need to acknowledge the second chip? Not load-bearing for the architecture; relevant for any teardown imagery, packaging copy, or in-universe service manuals. Marketing/Product agent scope when packaging concept work begins.

The cart bus was the reason. ADR-0013 committed the KN-86 to a Game Boy DMG slot with MBC5 timing, and Linux is not a real-time OS — bit-banging cycle-accurate ~4.19 MHz reads from userspace is unreliable, and even kernel GPIO is jittery enough to break MBC5’s bus contract. We needed a microcontroller. Once that decision was implicit in ADR-0013, the natural follow-on was to ask what else belongs on a coprocessor that was already going to be sitting in the case. Two answers fell out: the YM2149 PSG audio synthesis (because audio underruns are the failure mode under heavy Pi rendering load, not Pi compute exhaustion), and the SSD1322 CIPHER-LINE OLED driver (because the ticker animation specified in ADR-0015 deserves cadence guarantees that Linux’s scheduler can’t promise). All three jobs fit comfortably on a single Raspberry Pi Pico 2 (RP2350) — the GPIO budget covers the cart bus without external multiplexing, the PIO state-machine count covers cart-bus + I2S + future protocols with headroom, and the M33 cores have ample compute for a YM2149 emulator that originally ran on a 1 MHz logic chip. The architecture becomes a clean host+MCU split: Pi handles compute and display (Fe VM, nOSh, HDMI to Elecrow); Pico handles realtime I/O (cart bus, audio, OLED). This is the pattern mature Pi-based retro handhelds converge on; we’re adopting it deliberately rather than discovering it under duress. The cost is real — ~$5 per unit, ~3–7 hours off the runtime envelope, a second firmware codebase, and amendments to ADR-0011 (firmware updates) and ADR-0015 (CIPHER-LINE driver path). Future readers should take three things from this ADR: (1) the cart bus is the load-bearing reason — the chip would be needed even if audio and OLED stayed on the Pi; (2) cart authors see no change — the NoshAPI FFI is unchanged, only the firmware-internal driver path moves; (3) the runtime regression is the price of realtime guarantees on three peripherals at once, and Josh accepted it explicitly with eyes open.