Build guide: five sittings at the bench

Generated by tools/build-guide.py from the step table in tools/bringup.py and the log in docs/lab-log.jsonl. It is the procedure that actually runs and the state it is actually in, so it cannot drift from either. Re-run that tool and this updates.

One command per sitting. Each one prints what to do, waits, and then measures something. It never asks whether a step worked. A step that fails stops the run and tells you which one to repeat.

The running record, with every attempt and the photographs, is the lab notebook.

Where it stands

sittingwhat it isstate
1Instrumentsstopped at 0.2
2The console, measurednot started
3The bridge, builtnot started
4Joinednot started
5The head's handsnot started

Before the first sitting

Two things bite before anything else, and both are one line.

The serial port will refuse to open until your user is in the dialout group:

sudo usermod -aG dialout $USER

Then log out and back in. A new shell is not enough. Check it took with id -nG | grep dialout.

The scope's address lives in bench.local.md, which git ignores. Nothing else needs it, and nothing else should carry it.

On the desk, over the five sittings: a meter, two scope probes, the UNO and a USB cable, the console open with a game in it, two original pads, the breadboard, a 74HC04, a 74HC165, a 74HC595, three 100 nF capacitors, a 100 ohm resistor, a BNC lead to the scope's rear EXT TRIG, the PC817 module, the relay module, and the Pi.

Sitting 1: Instruments

Nothing is wired. The scope answers, the UNO's port opens, the sketch is on it.

python3 tools/bringup.py --session 1

0.1 The scope answers, and says what it is (held)

  • Power the scope and put it on the LAN.
  • Its address belongs in bench.local.md, which git ignores. Nothing else needs it.

Then it asks the scope who it is over the LAN.

Last run 2026-09-08 11:07:20: held. DS1054Z, firmware 00.04.05.SP2

0.2 The workstation can open a serial port (did not hold)

  • Plug the Arduino UNO into this workstation by USB, with nothing else connected to it yet: no chips, no console, no pad. This step only proves the port opens.

Then it opens the UNO's serial port, and names the fix if it cannot.

Photographs:

  • docs/lab/00-uno-bare.jpg (wanted)

Last run 2026-09-08 11:07:24: did not hold. no /dev/ttyACM* or /dev/ttyUSB*: is the UNO plugged in?

0.3 The bridge firmware is on the UNO and answers STATUS

  • Flash the sketch, putting your own port after -p:
arduino-cli compile --fqbn arduino:avr:uno firmware/bridge-uno
arduino-cli upload  --fqbn arduino:avr:uno -p /dev/ttyACM0 firmware/bridge-uno

Then it sends STATUS and reads the bridge's reply.

Sitting 2: The console, measured

Still nothing built. A meter and two probes on the console you already have.

python3 tools/bringup.py --session 2

1.1 The controller harness's colours mapped by continuity

Answers: wiring.md's port table is a published pinout until this step confirms it on THIS board.

  • Console UNPLUGGED from the wall. Open it if it is not already.
  • Find the white header on the board where the controller port harness lands (IMG_5666).
  • With the meter on continuity, ring each header pin out to the port socket's pins.
  • Port pinout, looking into the socket: 1 GND, 2 CLK, 3 OUT0, 4 D0, 5 D3, 6 D4, 7 +5V.
  • Colours are not evidence. Ring every one.

Then it takes your continuity readings, and refuses fewer than four.

Photographs:

  • docs/lab/01-board-header.jpg (wanted)
  • docs/lab/01-port-socket.jpg (wanted)

1.2 The port's idle levels with the console on

  • Console powered, NOTHING plugged into the port you are measuring.
  • Meter black on port pin 1 (GND). Measure pins 7, 3, 2 and 4 in turn.
  • Expect: pin 7 near 5 V, pins 3 and 2 idle, pin 4 pulled up.

Then it takes four meter readings and REFUSES to continue if the supply pin is not about 5 V.

Photographs:

  • docs/lab/01-meter-on-port.jpg (wanted)

2.1 The scope on an original pad's port, a game running

Answers: wiring.md's authored 'latch high a few us, clock low a few hundred ns, ~7 us between clocks, 60 polls/s'.

  • Put an original pad in the console's OTHER port and start a game that polls.
  • Scope CH1 probe on that port's pin 3 (OUT0, the latch).
  • Scope CH2 probe on that port's pin 2 (CLK).
  • Both probe grounds on port pin 1. Probes at 1x if they have a switch.
  • This is the measurement that replaces four authored numbers at once.

Then it captures both lines and measures the latch pulse, the clock pulse, the gap between clocks and the polls per second.

Photographs:

  • docs/lab/02-probes-on-port.jpg (wanted)
  • docs/lab/02-scope-screen.jpg (wanted)

Sitting 3: The bridge, built

The breadboard, in two halves that are tested apart before they are joined.

python3 tools/bringup.py --session 3
  • Console OFF. Build only the console-facing half on the breadboard:
    • 74HC04 (U1): pin 14 to port +5V, pin 7 to GND, pin 1 from port pin 3 (OUT0).
    • 74HC165 (U2): pin 16 to +5V, pin 8 to GND, pin 15 (/CE) to GND, pin 10 (DS) to GND, pin 1 (/PL) from U1 pin 2, pin 2 (CP) from port pin 2 (CLK), pin 9 (QH) to port pin 4 (D0).
    • 100 nF across each chip's supply pins.
    • Wire links on the eight inputs to make a KNOWN byte. In pad order A, B, Select, Start, Up, Down, Left, Right those are pins 6, 5, 4, 3, 14, 13, 12, 11, and LOW is pressed.
  • Scope CH1 still on OUT0, CH2 moved to QH (U2 pin 9). Console on, game running.

Then it captures QH and decodes the eight bits against the byte your links make.

Photographs:

  • docs/lab/03-console-side-built.jpg (wanted)
  • docs/lab/03-qh-on-scope.jpg (wanted)

4.1 The UNO drives the 595, measured on its outputs

  • Console OFF and its port half left alone. On the UNO's side of the board:
    • 74HC595 (U3): pin 16 to the UNO's 5V, pin 8 to GND, pin 13 (/OE) to GND, pin 10 (/SRCLR) to 5V, pin 14 (SER) from UNO D11, pin 11 (SRCLK) from UNO D13, pin 12 (RCLK) from UNO D10.
    • 100 nF across its supply pins.
  • Do NOT join U3's outputs to U2's inputs yet. Meter on QA (pin 15), black on GND.

Then it sets a byte, reads it back from STATUS, and has you meter two of the register's outputs.

Photographs:

  • docs/lab/04-uno-and-595.jpg (wanted)

4.2 The original pad polled by the bridge at 5 V

  • Plug the console's spare port housing into the bridge's pad side:
    • pad pin 1 to GND, pin 7 to the UNO's 5V, pin 3 (OUT0) to UNO D6, pin 2 (CLK) to UNO D7, pin 4 (D0) to UNO D8.
  • Plug an original pad into it. Console still off.
  • You will be asked to hold buttons; the bridge's own poll should follow them.

Then it asks you to hold three buttons and checks each one reaches the bridge's pad byte.

Photographs:

  • docs/lab/04-pad-on-bridge.jpg (wanted)

Sitting 4: Joined

The halves wired together. B0's first gate.

python3 tools/bringup.py --session 4

5.1 The two halves joined, eight clocks per latch on a game

Answers: the plan's B0 gate 1, which is the first thing the part gets to answer.

  • Console OFF. Remove the wire links from U2's inputs and join the halves:
    • U3 QA..QH (pins 15, 1, 2, 3, 4, 5, 6, 7) to U2 H..A (pins 6, 5, 4, 3, 14, 13, 12, 11).
    • UNO D5 to port pin 3 (OUT0). UNO D2 to port pin 2 (CLK).
    • The console's GND, the breadboard's GND and the UNO's GND are one net.
  • Console on, a game running that polls the pad, MODE PASS.
  • This is B0's first gate: eight clocks on every poll.

Then it listens to the bridge for twenty seconds and prints the clocks-per-poll histogram.

Photographs:

  • docs/lab/05-bridge-joined.jpg (wanted)
  • docs/lab/05-console-running.jpg (wanted)

5.2 A pressed button reaches the console through the bridge

  • Same setup. You will be asked to hold a button; the game should see it,
  • and the bridge's log should carry the same byte at the same latches.

Then it checks the game reacts and the log carries the byte.

Photographs:

  • docs/lab/05-button-through.jpg (wanted)

Sitting 5: The head's hands

The trigger, the reset optocoupler and the power relay.

python3 tools/bringup.py --session 5

6.1 The trigger reaches the scope

  • UNO D3 through a 100 ohm resistor to the scope's rear EXT TRIG.
  • Check the EXT TRIG input's rating first; if 5 V is over it, a 2:1 divider after the resistor.

Then it arms the scope on EXT TRIG and fires the bridge's trigger at a latch.

Photographs:

  • docs/lab/06-trigger-cable.jpg (wanted)

6.2 The reset optocoupler pulses the console

  • Console on. Find the reset button's two pads; meter which is ground and which is pulled up.
  • PC817 module: OUT to the pulled-up pad, its GND to the ground pad, VCC unconnected, and the Pi's GPIO17 to INPUT + with INPUT - to the Pi's GND.

Then it records which reset pad is ground and checks the pulse resets the console.

Photographs:

  • docs/lab/06-reset-pads.jpg (wanted)

6.3 The power relay switches the console

  • MAINS SAFETY: the contact goes in series with ONE lead of the low-voltage adapter cable, between the adapter and the console's DC jack. Never the mains side, and never both leads.
  • Relay module VCC to the Pi's 5V pin, IN to GPIO27 (active low), GND to the Pi's GND.

Then it refuses unless the contact is on the adapter side, then checks it switches.

Photographs:

  • docs/lab/06-relay-inline.jpg (wanted)

When a sitting is done

python3 tools/lab-notebook.py     # fold the run into the notebook
python3 tools/build-guide.py      # and update this page's state

Photographs go in docs/lab/ under exactly the name the tool asked for. Push them and re-run those two: the notebook embeds the ones that arrived and keeps naming the ones that have not, so nothing here is ever a broken image and nothing is quietly forgotten.

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