6502tinymachines

この文書はまだ翻訳されていません。本文は英語のまま表示されています。

v1b: the bridge on the Arduino UNO, all at 5 V

Added 2026-09-08 after the parts arrived. Supersedes v1 as the thing to build first; v1 stays in the set as the C6 version for when v2's counters need it.

Why

The 74HC parts that arrived (HC165, HC595, HC04) need a 3.5 V high at 5 V. The ESP32-C6 gives 3.3 V. The UNO's ATmega328P is a 5 V part, so with it in the middle every signal on the console side is 5 V and in spec, and three things fall out:

  • No 74LVC245: the UNO reads the console's OUT0 and CLK directly.
  • No 74LS245 up-shifter with pullups: the UNO drives the 595 at 5 V.
  • The pad is polled at the 5 V it was built for. Measure-first item 4 (does the 4021 run at 3.3 V) no longer gates anything.

Parts (all on hand)

refpartfrom
A1Arduino UNO R3the pile
U174HC04the Minidodoca kit
U2SN74HC165Nthe TI bag
U3SN74HC595Nthe box of 30
C1..C3100 nF
R1100 R (+ two 1 k if EXT TRIG needs a divider)
J1console port header harness
J2the console's other port housing
DIP-14 and DIP-16 socketsthe kit

UNO pins

pinfunctionwhy this pin
D13, D11SCK, MOSI to U3hardware SPI, 4 MHz, 8 bits in 2 us
D10RCLK to U3one rising edge moves the byte; pulsed only while D5 reads low
D5CON_OUT0Timer1 external clock input (T1): a 16-bit hardware counter of latch rising edges. Also readable as a level for the write gate
D2CON_CLKINT0, falling-edge ISR: clocks per poll
D3TRIGrises at latch T, one loop late
D6, D7, D8PAD_LATCH, PAD_CLK, PAD_D0the bridge's own pad poll, at 5 V
D0, D1serial to the Pi over the UNO's USB115200
5V, GNDthe bridge's supply and groundfrom the Pi through USB

Timer1 in external-clock mode counts edges synchronously with the 16 MHz clock, so a pulse must be longer than one CPU cycle (62.5 ns) to count; the console's latch pulse is microseconds. There is no glitch filter; if the L stream ever shows a latch count that is not one per poll, add a 100 pF to ground on D5 and record it.

Firmware, delta from bridge.ino

The protocol in docs/script.md is unchanged, so headd.py and the tools need nothing beyond the serial speed.

  • write_register(b): SPI.transfer(~b), then if digitalRead(5) is LOW, pulse D10 high for one instruction and read D5 again; if it went HIGH across the pulse, flag the next L line. The complement is not cosmetic: pressed is LOW at the register. SPI is MSB first and the first bit sent travels furthest, so bit 7 sent first puts our bit 0 on QA, which is the A button. Reverse either and the pad is silently mirrored.
  • Latch count: read TCNT1 (16-bit), keep a 32-bit running total, handle wrap. Clock count: a volatile counter in the INT0 ISR.
  • Loop order: read counts, emit L, then write. Fixed 64-byte line buffer, no String. Schedule cursor, not a scan.
  • The schedule holds 128 entries, not the C6's 2048. This part has 2 KB of SRAM in total and the compiler is the authority: at 256 it reports 2161 bytes of globals, 105 percent, and refuses to link; at 128 it reports 1521 and leaves 527 for the stack. tools/b3.py record refuses a longer record before the run, tools/fake-bridge.py is bounded the same way so the failure can be rehearsed, and the head stops any run in which the bridge answers # schedule full.
  • There is no Serial.printf on the AVR core and avr-libc's printf carries no 64-bit conversion, so the C6's uint64_t counters and %llu would have compiled to nothing useful. Every counter here is uint32_t and %lu: the latch index wraps after 2.2 years at 60 polls a second.
  • The L line stays at four fields, L n hh c. A timestamp is v2's t_us and does not belong here: tools/b3.py, tools/compare-logs.py and head/headd.py all require exactly four, so a fifth field would break the record, the comparison and the head's latch tracking at once. The head already timestamps what it receives.
  • MUTATE ON: no jumper and no config pin. D5 is PD5, which is also PCINT21, so the latch line can raise a pin-change interrupt while Timer1 goes on counting it in hardware. Under MUTATE the clock counter is fed by that interrupt on rises only, and INT0 stops feeding it. That is B0's stated sabotage, the clock counter fed the latch line, done in software with nothing to forget to move back. A mutated run reports one clock per latch instead of eight, so the 8-per-latch gate is red.

Build order (replaces v1's)

  1. Meter the harness (unchanged).
  2. Meter the port with the console on (unchanged).
  3. Scope the original pad in the other port for the four pulse widths (unchanged; these replace every authored number).
  4. U1 + U2 + wire links on H..A, console on, QH on the scope: the pattern appears in pad order. Proves the console side alone.
  5. UNO + U3 + J2, console off: STATUS shows the pad byte following the buttons; SET 08 puts 08 on U3's outputs (meter them).
  6. Join: U3 outputs to U2 inputs, D5 and D2 to J1 pins 3 and 2, GND to GND. Console on, MODE PASS, a game: L lines, 8 per latch.
  7. Trigger, reset, power as before.

What v1b gives up

  • The C6's 50 ns glitch filter and its second hardware counter. The UNO has one external timer input free (T0 is used by millis).
  • Wi-Fi and BLE on the bridge. Irrelevant; the Pi is the head.
  • v2's six counted lines. Those go on the classic ESP32 (8 PCNT units) when v2 is built, with the UNO still owning the 5 V register and pad side. Two microcontrollers, each in its own voltage domain, joined only by the Pi's USB.

Amendments made when the firmware was written, 2026-09-08

The sketch is firmware/bridge-uno/bridge-uno.ino, and it compiles: 8,018 bytes of flash, 24 percent, and 1,521 bytes of SRAM, 74 percent. Writing it settled six things this document had left open or wrong, and the bullets above now say what was built rather than what was planned.

  • SPI.transfer(b) had to become SPI.transfer(~b), and the bit order through the 595 into the 165 had to be reasoned about rather than assumed. Both are silent-mirror bugs.
  • The micros() field would have broken three tools that require four.
  • The MUTATE jumper and its D4 config pin are not needed; PCINT21 on the latch line does it in software.
  • The schedule does not fit and never could; 128 is measured, not chosen, and three places now refuse rather than truncate.
  • The 64-bit counters do not exist on this part.
  • The relay modules on hand are 5 V coil parts with opto inputs, so the v1 sheet's 3.3 V rail was wrong for them too; both sheets now show the Pi's 5 V pin and an active-low input.

One correction that came from the instrument rather than the compiler: both sheets put the console's video on the scope's CH1. It is on CH3. That is where scope-capture.py has always defaulted, it is where the probe actually sits (measured 2026-09-07, 1,512 sync pulses), and CH1 is B2's master-clock channel, so the drawing was claiming the one channel the alignment classifier needs for something else.

Pulled at build time from nes-bench/docs/bench-v1b-uno.md; the repository is the one copy.