Mouse Joystick PCB Rev 0 Plan¶
Status: Rescoped 2026-08-13, InstrumentPort added back 2026-08-16; schematic capture complete and ERC-clean 2026-08-17 — 0 violations at every severity, 142 components. Layout not started. Date: 2026-08-17 Purpose: Get a manufacturable single-axis board designed and ordered as fast as possible, so the researcher can exercise the interfaces — motor, encoder, limit switch, and the pyControl serial link — while the twin-axis form factor is still being decided.
Rescoped 2026-08-13¶
Rev 0 was cut down to shorten the path to testable hardware. The board no longer carries the analog encoder-angle output or the pyControl TTL event lines. It carries the interfaces, the means to observe them, and nothing else.
The purpose changed with it. The earlier plan aimed at a board the researcher could run experiments against. This one aims at a board that proves the interfaces work, and defers everything whose value only appears once experiments start.
| Moved out of Rev 0 | Returns in |
|---|---|
| DAC80501 0-3.3 V analog angle output and its BNC | rev 1 |
PULL_EVENT, FAULT, RSYNC and their BNCs |
rev 1 |
| Electronic brake, torque sensor, extra analog outputs | rev 2+ |
Synthetic homing stimulus (TMC_REF_STIM) |
never — dropped permanently |
The InstrumentPort Came Back, 2026-08-16¶
It was in the table above until 2026-08-16, deferred on the grounds that six of its eighteen digital slots carried signals Rev 0 does not have. That reasoning was partly wrong — the encoder nets exist in Rev 0, they just do not reach the MCU — and it weighed the wrong thing. The port's Rev 0 value is firmware development, not experiments: seeing the pyControl link at the cable and the TMC5130A SPI bus in one capture is what makes protocol and driver bring-up tractable, and that value is highest exactly when the board is new.
Rev 0 populates J1501, buffer packages U1601, U1701, and U1801, and
eleven of the eighteen digital Strands. The other seven are allocated and
unrouted, four of them reserved for the rev 2 brake and torque sensor so those
circuits do not have to renegotiate the pinout.
The buffer grouping as built is not the documented one, and that is accepted.
U1601 carries ENC_A, ENC_B, PYC_UART_RX, PYC_UART_TX; U1701 the four
TMC SPI signals; U1801 LIMIT_SW, FW_TRIG0, FW_TRIG1, and one spare
channel whose input is grounded. The property that matters — no buffer input
floats — holds. The cost is that the grouping mixes domains, so the rev 2 swap
touches two packages instead of one.
ENC_A, ENC_B, and LIMIT_SW borrow three of those reserved slots until rev 2. Rev 0 has no
cross-check at all — the encoder never reaches the MCU — so the TMC5130A's
X_ENC register is the only encoder count on the board; capturing the raw edges beside the SPI reads of that register is the
only way to confirm the driver is decoding correctly. The same applies to homing:
with no GP18 copy either, LIMIT_SW on the port is the only witness that the
switch closed when the driver says it did. All three sit in the rev 2 package's
slots, so they survive rev 1 and are evicted as late as possible. Full allocation and reasoning in docs/interface-map.md.
This costs schedule. It adds a 40-pin FFC, three buffer packages, five
board-level termination resistors (R1104-R1601), and eighteen more routed
nets to a board whose layout was already the long pole — see
.agents/handoff.local.md. It is the researcher's call whether that is worth a
slip; the design record's job is to say plainly that it is a slip.
Corrected 2026-08-17; this said "five buffer footprints of which three are
populated" and "four pull-down resistors". Only three packages were ever drawn,
and the terminations came out at 10 kΩ rather than the 100 kΩ pull-downs
originally specified — see docs/design-block-map.md, "Board-Level Terminations".
TMC_REF_STIM is not deferred, it is deleted. The limit switch now arrives
through a real debouncer on its own terminal block, so homing is exercised by
closing the actual switch. Its pin pays for the GPIO isolation enable; see
docs/interface-map.md.
What This Board Is¶
One PCB controlling one joystick axis:
- RP2040 controller MCU with USB-C for firmware load and a CDC debug console.
- A GPIO isolation bus switch between the MCU and the switched half of the board, isolated at power-on until firmware enables it, so the MCU cannot back-power chips it has deliberately switched off.
- Two power domains: a switched one carrying everything resettable, and an always-on MCU domain fed from USB or pyControl.
- One TMC5130A stepper driver section in SPI mode, with a 4-pin motor terminal.
- One single-ended encoder on a 5-pin terminal, read by the TMC5130A only — it reaches no GPIO in any revision.
- One limit switch on a 2-pin terminal through a hardware debouncer, to the
TMC5130A
REFLinput only — it reaches no GPIO. - pyControl RJ-45 behavior port carrying the serial link and nothing else.
- 36 V motor supply through a protected, electronically switched terminal-block input.
- Local safe-state authority: boot-safe outputs, host heartbeat, bounded motion limits, latched faults.
Out of scope: everything in the rescope table above, plus audio, reward and lickport hardware, and animal-facing operation. Those are separate work and a separate gate.
Why This Board Instead Of The Twin-Axis Board¶
The rig has two joysticks, and mouse-joysticks-pcb is the twin-axis design
for it. This board exists because it can be finished sooner:
| Twin-axis board | This board |
|---|---|
28 of 30 GPIOs, reached only after cutting encoder index, DIAG1, homing stimulus, and DAC ~LDAC/~CLR |
24 of 30 at full scope with six spares; 18 of 30 in Rev 0. The DAC ~LDAC/~CLR pins cost nothing to give up here — the part chosen on 2026-08-26 does not have them |
| Event-line routing is an open blocking question: 4 lines, no home | Deferred entirely in Rev 0; 3 lines and 4 BNCs when it returns |
| Layout zoning across two motor sections, two encoder paths, two analog outputs | One motor section, one encoder path, no analog output |
| Axis symmetry is a first-class layout and scientific constraint | Not applicable |
| Two motors' supply current, doubled bulk and protection | One motor |
| Two of every harness and connector | Seven connectors in Rev 0 — J201 36 V, J301 USB-C, J1101 analog BNC, J1102 motor, J1103 encoder, J1301 limit switch, J1501 InstrumentPort FFC, J1901 pyControl RJ-45 |
See docs/relationship-to-twin-axis-board.md for what is being traded away and
what carries forward.
Deliberate Differences From The Twin-Axis Board¶
- 36 V input instead of 24 V, using
hv_input_36V_terminal_2pos_5p08_protected. Better motor performance, and it pre-resolves the brake rail tension the twin board records as unresolved. It costs transient headroom at the TMC5130A, which at this voltage no TVS can restore — the driver relies on the soft-start switch and layout instead, while the TVS onHV_INcovers the switch and the buck. Seedocs/power.md. - A switched domain plus GPIO isolation. The twin board carries neither. Here the high-side switch doubles as a chip-reset mechanism the researcher can trigger without unplugging anything, and the bus switch is what makes that safe — the MCU survives the reset it performs, on a rail the switch cannot reach.
Controller MCU¶
RP2040 (OW-MCU-RP2040, QFN-56) via orb_weaver_private:rp2040, which
provides QSPI flash, a 12 MHz ABM8 crystal, decoupling, RUN/BOOTSEL, SWD, and
debug/timing GPIO breakouts. Proven on tmc51x0-driver-dev-pcb.
There is no reason to consider RP2354B here. Its only motivation on the twin board is the Rev 1 brake and torque GPIO count, and this board carries neither.
Required support:
- USB device connection via
usb2for firmware load and diagnostics. - Boot access.
SW501BOOTSEL is fitted. - Debug console over USB CDC.
UART0is committed to pyControl, and in Rev 0 USB CDC is the only rich observation path on the board. - Stable 3.3 V logic rail.
UART0pyControl,SPI0TMC5130A.SPI1and PIO0 are unallocated in Rev 0 but their pins are reserved.
The SWD header and the RUN connection are deferred to a future board
revision. Reversed 2026-08-17; the list above said "Boot and reset access, SWD
header", and the rp2040 block description still names RUN and SWD among
what it provides. As built, RUN (pin 26) and SWCLK/SWD (pins 24, 25) are
unconnected and no-connect-flagged, by explicit decision on 2026-08-17, not
by oversight.
This is an access limitation, not an electrical defect. RP2040 RUN has an
internal 50-80 kΩ pull-up and the datasheet permits leaving it open or tying it
to IOVDD, so the part comes out of reset on its own; what is missing is a way
to force a reset without power-cycling, and a way to attach a debug probe.
Rev 0's development path is BOOTSEL plus USB CDC, which is why the trade was
judged acceptable for a bench board. Restore both in the revision that adds the
DAC — a hard reset and a probe are worth more once there is firmware complex
enough to hang.
Full allocation in docs/interface-map.md.
GPIO Isolation¶
orb_weaver_private:rp2040_gpio_isolation_sn74cb3q3245, one instance: four
SN74CB3Q3245 8-channel bidirectional FET bus switches, 32 channels, one shared
active-low ~OE with a pull-up.
The rule is the far end's power domain: a GPIO goes through the switch if and
only if what it connects to loses power when HV_EN drops. That is what stops
the MCU back-powering a chip it just switched off, which is the board's normal
idle state rather than an edge case.
- Isolated in Rev 0 — 7 channels: the TMC5130A SPI bus, enable, and
diagnostics (
GP2-GP8). Ten at full scope, with the rev 1 DAC bus (revised 2026-08-26 from twelve; the DAC needs three wires, not five). As built, six of the seven are onU601and the seventh (TMC_DIAG1) is onU602;U603andU604carry nothing at all, and at ten channels of thirty-two placed they never will. - Bypassed: the pyControl UART above all, plus
~{BUS_SW_EN},HV_EN,HV_DIAG_EN,HV_SNS,~{HV_FAULT},SW_RAIL_SENSE, and bothFW_TRIGmarkers. Nothing at those far ends is ever unpowered byHV_EN. Per-signal rationale indocs/interface-map.md. - Wire the A side of all 12 isolated GPIOs now, including those reserved for rev 1, so a later revision adds board-side wiring only. Not done. Only the seven Rev 0 channels are wired; every unused channel has its A pin open and its B (MCU-side) pin tied to GND, so the rev 1 DAC channels are not pre-wired and rev 1 must edit this block regardless.
- Sequencing: isolate before dropping
HV_EN, and reconnect only afterSW_RAIL_SENSEconfirms+3.3V_SWis up. Both it andHV_ENbypass, so firmware can do this with the bus isolated. - A side is the TMC/switched side, B side is the MCU side. Inverted from
docs/interface-map.md; the board is the authority, and the part is a symmetric FET pass gate so it works either way.
Decided 2026-08-13: place the proven block whole. Four SN74CB3Q3245
packages isolate 30 GPIOs and this board needs 12, so two packages and their
decoupling are dead weight — and as built they are literally empty, not merely
under-used. Accepted deliberately — schedule beats parts count for Rev 0, and the
block is already placed and proven. Revisit in a later revision if board area
gets tight, and decide what to do with U603/U604 before layout.
The block runs from +3.3V_MCU, which diverges from its own part note. The
note says to power it from the switched target-domain rail so Ioff provides
isolation at VCC = 0. Here isolation is a firmware property instead, defaulting
safe because R601 7.87 kΩ pulls ~{BUS_SW_EN} high at reset. Deliberate; see
docs/design-block-map.md.
Proven on tmc51x0-driver-dev-pcb, which bypasses the same kind of control
group — but bypasses far fewer signals, because it has no two-domain power tree.
Driver Section¶
One instance of orb_weaver_private:tmc5130, composed twice into
tmc51x0-driver-dev-pcb with board ERC at zero.
- TMC5130A-TA (
OW-PWR-TMC5130A-TA-T), verified official symbol and footprint. AnnotatedU1201. - Strapped and routed for SPI operation:
SWSELandSD_MODEto GND,SPI_MODEleft on its internal pull-up. Bring theSWSELstrap to a pad. DIAG0andDIAG1both routed to the MCU — this board has the pins. Both are open-drain and get 10 kΩ pull-ups to+3.3V_SW(R1101/R1102), as the datasheet's ≤47 kΩ requires.ENCNis unused and terminated byR120110 kΩ to GND;REFRandAIN_IREFare no-connects.VSAis fed from+5V_SWrather thanVS, a deliberate deviation that keeps 36 V off the analog supply.DRV_ENNpulled to the disabled state so the driver is off before firmware configures it, and behind the bus switch so it stays that way.- The block includes a 16 MHz oscillator and the charge-pump and
5VOUTdecoupling. - Motor connector: 4-pos 3.5 mm terminal,
OW-CONN-1844236, stocked.
Current Sense — Decided, Inherited¶
Same motor, same answer as the twin-axis board. Do not re-derive it.
Corrected 2026-09-08. The motor rating below was an assumption and it was wrong. The motor was identified on hardware 2026-09-02 as a Lin Engineering WO-208-13-01D — NEMA 08, 1.8°/step, 4-lead bipolar, 0.6 A/phase, 6.5 Ω/phase, 3 oz-in — which is the very part Open Decisions item 1 below explicitly ruled out.
R_SENSEandvsenseare unaffected and stay as tabulated;IRUNchanges from 28 to 17.docs/bringup-log.mdowns the finding. Lin does not state whether 0.6 A is RMS or peak, so two readings exist and firmware enforces the conservative one:
- Peak-of-sine reading (conservative, in force): 0.424 A RMS target →
IRUN17.- Equal-heating reading: 0.600 A RMS target →
IRUN24-25.Settling it needs a coil-current measurement and a motor case temperature. That can be taken on v0.2 hardware — the motor and driver path are unchanged.
Motor rating: 0.6 A per phase (WO-208-13-01D), RMS-or-peak unstated by the
vendor; the conservative peak-of-sine reading gives a 0.424 A RMS target.
| Item | Value |
|---|---|
R_SENSE |
0.15 Ω, both coils — 2 positions |
| Part | OW-PASS-RLH12FTCR150, JLCPCB C2683383 |
| Package / rating | 1206, 1 W, ±1%, ±100 ppm/°C |
vsense (CHOPCONF bit 17) |
1 — high sensitivity, V_FS = 180 mV |
IRUN |
17 → 0.421 A RMS (−0.7% of the 0.424 A conservative target) |
IRUN, equal-heating reading if the thermal measurement supports it |
24 → 0.585 A, or 25 → 0.608 A |
| Ceiling at CS 31 | 0.749 A — 1.25× the 0.6 A rating, and a hardware limit once vsense = 1 is latched |
| Peak sense voltage at IRUN 17 | 101 mV across R_SENSE + 20 mΩ; 89 mV across the 0.15 Ω itself |
| Peak dissipation at IRUN 17 | 53 mW = 5.3% of 1 W |
| Peak dissipation at IRUN 25 | 111 mW = 11% of 1 W |
Derived from TMC5130A rev 1.21 chapter 10:
I_RMS = (CS+1)/32 × V_FS/(R_SENSE + 20 mΩ) × 1/√2
The tmc5130 design block ships 0.12 Ω as its default sense value. Change
both positions to 0.15 Ω when the block is placed, and check the placed value
before ERC rather than after fabrication.
Done — and it is now the one place where the board diverges from kicad-libs,
deliberately. R1203/R1204 are 0.15 Ω on this board; kicad-libs ships
0.12 Ω. Thirteen of the fourteen placed block copies are byte-identical to their
sources and this is the exception, with the board being the correct one.
Resolved 2026-09-08 — do not "fix the library down to 0.15 Ω". That earlier instruction is reversed. The library keeps 0.12 Ω as a deliberate placeholder, because the correct shunt is a property of the motor rather than the driver, so each placing board sizes it. The rule for this board is simply: never re-place
tmc5130, which is also why v1.0 leaves that block alone entirely. If it ever is re-placed, both resistors revert and — with firmware writingIRUN17 — the coils would see 0.511 A RMS, +21.4% over intent, silently: no ERC, no DRC, no fault bit. The old "roughly 25% over the intended 0.679 A RMS" was wrong twice; 0.679 A was never the intent.
See docs/design-block-map.md.
vsense = 1 Is A Firmware Dependency¶
Unchanged, and Rev 0's reduced scope does not reduce this hazard at all.
vsense |
Current at IRUN 17 | Ceiling at CS 31 |
|---|---|---|
| 1 (required) | 0.421 A | 0.749 A |
| 0 (chip reset default) | 0.760 A | 1.35 A |
Leaving vsense = 0 with 0.15 Ω installed drives 1.80× the conservative target
and 1.27× even the equal-heating reading — and note 0.760 A exceeds the
vsense = 1 ceiling of 0.749 A, so the wrong bit defeats the hardware limit
outright. (Updated 2026-09-08 for IRUN 17; at the old IRUN 28 these were
1.23 A and roughly 1.8× a target that was itself wrong.) Requirements:
- Firmware sets
vsense = 1before ever raisingDRV_ENN. - Firmware reads CHOPCONF back and refuses to enable the driver unless
vsense = 1is confirmed, raising anINTERNALfault instead. - Silkscreen the shunt group with its value and note the
vsense = 1requirement on the board and in the bring-up log. - Gate D measures actual coil current, not commanded
IRUN.
Once vsense = 1 is set the configuration is self-limiting: the ceiling at
CS 31 is 0.749 A, 25% above the 0.6 A rating (and 1.78× the conservative
0.424 A target). The hazard is confined to that one register bit. (Was "12%
above" against the wrong 0.67 A figure; corrected 2026-09-08.)
Encoder¶
Rev 0 decides single-ended, and it is a design block now.
quadrature_encoder_input_5v_sn74lvc2g17 was built in kicad-libs and placed on
2026-08-17, so this section is no longer board-local glue. Reversed
2026-08-17; this paragraph read "A, B, and N route from a 5-pos 3.5 mm
terminal (OW-CONN-1844249, stocked) directly to the TMC5130A
ENCA/ENCB/ENCN pins with local filtering and ESD protection".
Three of those words are wrong as built. There is no N — J1103 pin 2 is a
reserved index no-connect, there is no ENC_N net, and the TMC's ENCN is
terminated by R1201 10 kΩ to GND, which is what the datasheet asks for on an
unused pin. There is no ESD device anywhere in the encoder path. And nothing
routes directly: the chain is J1103 → R801/R802 100 kΩ pull-downs →
R803/R804 4.7 kΩ → C802/C803 470 pF → U801 SN74LVC2G17 dual Schmitt on
+3.3V_SW → R805/R806 100 kΩ → ENC_A/ENC_B.
J1103 is 5-position: pin 1 GND, pin 2 reserved index no-connect, pin 3
ENC_A_RAW, pin 4 +5V_SW, pin 5 ENC_B_RAW.
The 5 V-encoder-against-3.3 V-inputs problem is solved in hardware. U801
runs from +3.3V_SW, so it takes the 5 V swing and re-emits 3.3 V logic before
anything reaches the TMC's VCC_IO-referenced inputs or the always-on
instrumentation buffers.
differential_encoder_receiver is not placed in Rev 0. If the chosen encoder
turns out to be RS-422, that block exists and is proven three times on
tmc51x0-driver-dev-pcb, but fitting it is a rev 1 change and the connector
conductor count changes with it.
The encoder does not reach the MCU, in any revision. Decided 2026-08-16;
GP14/GP15/GP16 went back to the spare pool and the PIO cross-check was
deleted rather than deferred. GP14 did not stay spare — it was taken on
2026-08-17 for HV_DIAG_EN. The consequence is unchanged: ENCODER_MISMATCH has
nothing to compare and is never implementable; encoder integrity rests on the
driver's count. It no longer rests on the index pulse either, since there is not
one.
Still open, and still the decision that can invalidate the board: the encoder
part number, and therefore the supply voltage and whether single-ended survives
contact with the real device. The previous-generation rig used a Broadcom
HEDR-5421-EP111. Two things the placed block now has a stake in that answer:
the 100 kΩ pull-downs assume a push-pull 5 V encoder and become pull-ups
for an open-collector part, and the 4.7 kΩ + 470 pF filter is a 72 kHz corner
(τ = 2.2 µs) that must clear the encoder's maximum output frequency. The pin
order of J1103 depends on the answer too.
Limit Switch And Homing¶
A new design block, limit_switch_debounced_input, one instance. It owns the
connector as well as the conditioning, which is what makes the limit switch
independent of the encoder harness:
| Element | Part |
|---|---|
| Connector | 2-pos 3.5 mm terminal, OW-CONN-1844210, stocked |
| Debouncer | MAX6816EUS+T, OW-MISC-MAX6816EUS-T, stocked, orb_weaver_misc:MAX6816 / orb_weaver_smd:SOT192P237X122-4N |
| Support | Local decoupling; the MAX6816 supplies its own pull-up and ±15 kV ESD protection |
- Route the debounced output to the TMC5130A
REFLinput only — not to any GPIO. Tap it to InstrumentPortDIG11and a test pad.REFLis the hardware-latched reference; the MCU copy allows independent state reporting and fault detection. As built the connector isJ1301, the debouncer isU1301,DIG11is live — and the test pad does not exist, because no test point exists anywhere on the board yet. - No synthetic stimulus.
TMC_REF_STIMis gone. BringREFLto a labelled test pad so a jumper can still force a closure during bring-up. Still owed. - The always-on instrumentation buffer input on this net is defined by
R160110 kΩ to GND ininstrument_port, so it does not float when the switched domain is off. - Homing must be deterministic and bounded: travel limit and timeout, both enforced locally, with a fault if either is exceeded.
Keep the block project-local until bring-up verifies it. The twin-axis board needs the same circuit, so design it to be promotable.
pyControl Interface¶
Rev 0 populates the serial link only. Pinout established from
syringe-pump, a working pyControl module documented
against pyControl v2.1.1. See docs/pycontrol-port-pinout.md.
| RJ-45 pin | Signal | Rev 0 |
|---|---|---|
| 1 | DIO-A |
RP2040 UART0 RX (GP1), with R401 10 kΩ pull-up to +3.3V_MCU |
| 2 | GND |
Ground |
| 4 | DIO-B |
RP2040 UART0 TX (GP0) |
| 6 | 5V |
Populated: PYC_CONN_5V → R1903 → PYC_5V_FUSED → D1901 → +5V_MCU |
| 3, 5, 7, 8 | POW-A, SPECIAL, POW-B, 12V |
Not connected |
Reversed 2026-08-13, built 2026-08-17; pin 6 was to be a footprint only,
gated on the port's 5 V budget. It is populated, because +3.3V_MCU comes from
USB or pyControl and from nowhere else — an unpopulated branch means the board is
dark whenever USB is unplugged. R1903 is a polyfuse position holding a 0.36 Ω
jumper, not protection; see docs/design-block-map.md. The connector is
J1901.
R401 exists because the RP2040 resets every GPIO with its internal pull-down
enabled, and a UART RX line must idle high — without it the port looks like a
permanent break.
115200 8N1, newline-terminated ASCII, ;-delimited fields.
The assigned port must be UART-capable: ports 1, 3, or 4 on Breakout board 1.2, or ports 8, 10, 11, or 12 on the D-series Breakout Board 1.6.
Add series resistors and ESD protection on every populated port signal.
PULL_EVENT, FAULT, and RSYNC are rev 1. Rev 0 therefore has no
hardware-timed event path — every event is a UART line with UART latency. That
is acceptable for interface testing and is not acceptable for behavioral data,
which is the boundary between Rev 0 and rev 1.
Power — Two Domains¶
Decided 2026-08-13. The high-side switch is a reset button: it lets the
researcher power-cycle every chip on the board to a known state without
unplugging anything, while the MCU stays alive and reachable. Everything else
follows from that. Read docs/power.md before drawing the power section.
Switched domain, all of it downstream of HV_EN:
HV_RAW ─|>|─ HV_IN ─[switch]─ +36V_SW ─┬─> TMC5130A VS
└─> hv_to_5v_tpsm365r6 ─> +5V_SW ─┬─> TMC VCC, encoder
└─> 5v_to_3v3 #2 ─> +3.3V_SW ─> TMC VCC_IO, MAX6816, SN74LVC2G17
Always-on domain, which the switch cannot reach:
USB VBUS ─────────|>|─┬─> +5V_MCU ─> 5v_to_3v3 #1 ─> +3.3V_MCU ─> RP2040, bus switch, buffers, input-block pull-ups
pyControl pin 6 ──|>|─┘
Three consequences, all deliberate:
- Two
5v_to_3v3_tps73733instances, one per domain. Not one. - Two ORing diodes, both populated.
OW-MISC-MBR120VLSFT1Gis the stocked candidate. pyControl pin 6 is 5 V — confirmed from the reference design's netlist insyringe-pump, where RJ-45 pin 6 is5VDCand pin 8 is 12 V. - The board does not boot on 36 V alone. It needs USB or pyControl. That is accepted, and it should be silkscreened near the terminal block so a board that looks dead is not reported as broken.
The two mistakes that still look correct on a schematic:
- Merging the two 5 V rails. ORing USB and pyControl onto the rail that feeds
the TMC would drop it below the 4.6 V
VCCminimum. The domains keep them structurally separate; do not "simplify" them together. - Putting the bus switch on
+3.3V_SW. It would isolate for free viaIoff, and it would also take the sequencing away from firmware. It goes on+3.3V_MCU. Note that this is the opposite of what the block's own part note recommends; the divergence is deliberate and recorded indocs/design-block-map.md.
Fit a TVS on HV_IN, not on the motor rail. At hot-plug the switch is off, so
the exposed parts are the TPS1HTC100-Q1 (70 V) and the TPSM365R6V5 (65 V).
SMBJ40A preferred; stocked SMF48A works for first boards. The TMC5130A at
49 V cannot be protected by any standard TVS at this supply voltage; it relies on
bulk capacitance downstream of the soft-start switch — tens of µF, not
hundreds — tight VS/GND layout, and measurement at Gate B.
Still owed: no TVS is populated as built. Related bench item: C1213/C1214
are 100 µF 50 V parts on a 36 V rail, 72% of rating, which is acceptable but
leans on the ringing being measured rather than assumed. The +36V_SW bulk comes
to roughly 208 µF.
The input block needs six edits when placed, two of them functional defects
for this board: the EN divider gives only 150 mV of threshold margin from 3.3 V
logic, and DIAG_EN left unconnected disables the SNS current sense entirely.
Full audit in docs/power.md.
All six are made in the placed copy, with two revisions to the audit itself.
ILIM (R508) is 40 kΩ and SNS (R509) is 249 Ω, not the 1 kΩ an earlier
note suggested — 1 kΩ would have driven GP28 past its absolute maximum on a
fault. And DIAG_EN (R502) goes to GP14, not to a fixed pull-up:
Reversed 2026-08-17; earlier guidance said tie it high. Held high, the
switch's OFF-state open-load detection keeps an internal 150 kΩ pull-up from VS
to VOUT enabled, trickling ~240 µA into the ~208 µF of +36V_SW so the rail
creeps up and the buck hiccups instead of the domain switching cleanly off. That
would break the reset-button property this whole architecture exists for.
Firmware must raise GP14 before reading HV_SNS or ~{HV_FAULT}; current
limit and thermal shutdown are unaffected either way. Full reasoning in
docs/design-block-map.md.
Blocks To Place¶
Fifteen instances, all placed. Full list and status in
docs/design-block-map.md.
| Block | Instances |
|---|---|
rp2040 |
1 |
rp2040_gpio_isolation_sn74cb3q3245 |
1 |
hv_input_36V_terminal_2pos_5p08_protected |
1 |
hv_to_5v_tpsm365r6 |
1 |
5v_to_3v3_tps73733 |
2 — one per power domain |
tmc5130 |
1 |
usb2 |
1 |
pycontrol_behavior_port_rj45_boundary |
1 |
quadrature_encoder_input_5v_sn74lvc2g17 |
1 |
limit_switch_debounced_input |
1 |
instrument_port_40_dut_ffc_boundary |
1 |
instrumentation_signal_buffer_sn74lvc125a |
3 |
Two 5v_to_3v3_tps73733 instances, not one: the two-domain power tree needs
a 3.3 V regulator on each side.
Corrected 2026-08-17; this table listed eight instances and named three
"board-local sections that are not blocks: the pyControl RJ-45 boundary, the new
limit_switch_debounced_input, and the two-diode OR feeding +5V_MCU". Three of
those became blocks — the RJ-45 boundary, the limit switch, and (added since) the
encoder input — and the two-diode OR dissolved entirely: usb2 emits +5V_USB
through its own diode and pycontrol_behavior_port_rj45_boundary emits +5V_PYC
through D1901, so the board only ties them together at +5V_MCU. The
InstrumentPort and its three buffers arrived with the 2026-08-16 rescope.
Nothing in Rev 0 is board-local circuitry any more. What the board itself
authors is three wrapper sheets — mcu, motor_encoder_limit, and
instrument_port — which hold the block instances and the nine termination
resistors, and nothing else.
Open Decisions Before Layout Freeze¶
The rescope resolved or deferred five of the previous ten. What remains, ranked by what blocks the schedule:
- ~~The joystick motor part number.~~ Closed 2026-09-02, and this item had
it backwards. The motor is the previous rig's Lin Engineering
WO-208-13-01D, 0.6 A/phase — the part this line explicitly ruled out. That wrong exclusion is where the 0.67 A figure above came from. Current sense stays 0.15 Ω withvsense= 1, butIRUNis 17, not 28. What remains open is only whether 0.6 A is RMS or peak — the vendor does not say — which is theIRUN17 versus 24-25 choice and needs a coil current plus a case temperature. Seedocs/bringup-log.md. - The encoder part, which sets the encoder supply voltage, confirms that
single-ended receive is correct, and now also validates the placed block's
push-pull assumption, its 72 kHz filter corner, and
J1103's pin order. Rev 0 assumes single-ended. - pyControl pin 6's 5 V current budget, and whether it is shared across
ports. It is now load-bearing: with USB unplugged it is the only thing
powering the board. Measure at Gate A. Pin 6 is 5 V — that part is
confirmed from the reference design's netlist. Related:
R1903is a polyfuse position holding a 0.36 Ω jumper; source a 1206 PPTC with a 250-500 mA hold, or accept the jumper and record it. - Whether screw terminals are acceptable for the motor and encoder harnesses, or
whether a locking 4-pos motor connector must be sourced. Confined to the motor terminal (
J1102as of 2026-08-27). - Which breakout the lab deploys, and which port is assigned to this board.
- Board outline, mounting, and enclosure constraints.
mouse-joysticks-mechdescribes the two-joystick assembly, so a single-axis board's mechanical envelope is not derivable from it without a decision about how two of these boards mount in the rig.
Items 1 through 3 can stop a fabrication order. Everything else can be resolved during layout.
Resolved by the rescope: the DAC VIH question, whether AUX_ANALOG_OUT
gets a populated BNC, and the InstrumentPort cable and mounting arrangement — all
now rev 1 decisions. See docs/encoder-analog-output.md and
docs/instrument-strategy.md, both of which remain valid for rev 1.
VIH was then resolved on its merits, 2026-08-26, rather than merely
deferred: the analog output block was built around a DAC80501, whose VIH is
1.62 V minimum flat across VDD = 2.7-5.5 V, so 3.3 V logic drives it directly
and no level translator is needed. AUX_ANALOG_OUT is still open and has become
more expensive — the block is single-channel, so a second output is a second
instance.
Also resolved: the oversized GPIO isolation block is accepted as-is.
Resolved by the two-domain power architecture: exactly two
5v_to_3v3_tps73733 instances, one per domain, with no third and no split of
+3.3V_SW; the regulator chain is fed post-switch; +3.3V_MCU comes from USB or pyControl only, with no always-on
converter on HV_IN; both ORing diodes are populated rather than one being
conditional; and whether the TMC5130A tolerates VCC_IO without VCC/VS no
longer needs an answer, because all three now fall together. See docs/power.md.
Resolved 2026-08-17, by decision rather than by design: no SWD header and no
RUN connection in Rev 0. Deferred to a future board revision — see "Controller
MCU" above. RP2040 RUN has an internal 50-80 kΩ pull-up, so this costs debug
access, not correct start-up.
Not on this list: the HV_IN TVS. SMBJ40A is worth ordering, but the
stocked SMF48A builds a working first board, so it never blocks a fab order.
First Schematic Pass¶
Complete as of 2026-08-17, with steps 7 and 10 taken differently from the plan. Kept as the record of the intended order and of where reality diverged.
Once the parts above are settled, in this order:
- Insert
rp2040. - Insert
rp2040_gpio_isolation_sn74cb3q3245. Wire the A side of all 29 allocated GPIOs; leave the B side of the 12 reserved channels unconnected. Route the four bypass signals directly to the MCU and leave their channels unused. Built differently: only the seven Rev 0 channels are wired, A is the TMC side rather than the MCU side, and unused B pins are tied to GND rather than left open. - Build the always-on domain first: the two ORing diodes from USB
VBUSand pyControl pin 6 into+5V_MCU, then5v_to_3v3_tps73733#1 to+3.3V_MCU. This is what powers the MCU and the bus switch, so everything else depends on it existing. Both diodes now live inside their own boundary blocks, emitting+5V_USBand+5V_PYC; the board only joins them. - Build the switched domain:
hv_input_36V_terminal_2pos_5p08_protectedfeedinghv_to_5v_tpsm365r6from the switched+36V_SW, then5v_to_3v3_tps73733#2 to+3.3V_SW. Put the bulk capacitance downstream of the switch. Confirm exactly onePWR_FLAGper input net — the 36 V block and theusb2block each carry one already; add one for the pyControl 5 V branch, and one for+5V_MCUitself, which is downstream of both ORing diodes and so has no driver without it. The two 5 V rails must not touch. - Insert
tmc5130. Change both sense resistors from the block default 0.12 Ω to 0.15 Ω (OW-PASS-RLH12FTCR150) — the block default is a deliberate per-motor placeholder, so this step is required, not a workaround. Recordvsense = 1andIRUN = 17alongside (was 28; corrected 2026-09-08). ConfirmVCCis fed from the external+5Vrail. Land the motor coils onOW-CONN-1844236. - Insert
usb2. It now carriesUSBLC6-2P6ESD protection and a shell-to-GND tie; confirm both are present in the placed copy. - Draw the encoder input:
OW-CONN-1844249→ filtering and ESD → TMC5130AENCA/ENCB/ENCN. No MCU connection. Done as a block instead —quadrature_encoder_input_5v_sn74lvc2g17, which owns the connector, and with no ESD part and noENCNconnection. See "Encoder" above. - Draw
limit_switch_debounced_input:OW-CONN-1844210→MAX6816→REFLonly, plusREFLandLIMIT_SWtest pads. Test pads not done. - Draw the pyControl RJ-45 boundary: pins 1, 2, 4 with series resistors and ESD, pin 6 footprint for the ORing branch. Done as a block, with pin 6 populated rather than footprint-only.
- Add test points for the Rev 0 test-point list in
docs/interface-map.md. Not done. There are no test points anywhere on the board. This is the largest single gap between the plan and the schematic, and it is a layout item rather than a schematic one only if the pads are added as footprints. - Add the board-level terminations:
R401onPYC_UART_RX,R1101-R1103inmotor_encoder_limit,R1104-R1601ininstrument_port. Not in the original plan; added 2026-08-17 when it became clear that leaving the always-on buffer inputs and the switched-domain control lines undefined was not survivable. All nine sit in board-authored wrapper sheets so a block re-place cannot lose them. Seedocs/design-block-map.md.
Run per-block ERC as you go:
kicad-cli sch erc --severity-error <block>.kicad_sch with
ORB_WEAVER_KICAD_LIBS set. Expect only pin_not_connected, pin_not_driven,
and power_pin_not_driven on an isolated fragment; anything else is a real
defect. Whole-design ERC is at 0 violations at every severity as of
2026-08-17.
What Rev 1 Adds¶
Written down now so Rev 0 does not foreclose it. All of it lands on pins Rev 0 already allocated and left unconnected:
| Addition | Pins | Blocks | InstrumentPort Strands |
|---|---|---|---|
| DAC80501 analog angle output + BNC | GP10, GP11, GP13 |
analog_output_0_3v3_dac80501, built 2026-08-26 |
DIG6, DIG9, DIG10, AIN0; a fourth buffer package |
| pyControl TTL event lines + two dual BNCs | GP20, GP22, ~~GP23~~ — GP23 was spent on ~{HV_FAULT} 2026-09-18, so this needs a third pin from elsewhere or drops to two lines |
pycontrol_event_bnc_group, new |
DIG13-DIG15; a fifth buffer package |
| Brake driver + torque front end (rev 2) | ~4 more — revised 2026-08-31, was ~6 | brake_current_sink_0_49ma_dac80501 (specified, not drawn), torque_bridge_frontend, both new |
DIG11 (BRAKE_CS, not BRAKE_DIM), DIG12, AIN1 (BRAKE_ISENSE, resolved); takes DIG0/DIG1 back from the encoder; re-inputs U1601 and U1801 |
Nothing in that table requires moving a Rev 0 pin, changing a Rev 0 connector, or
rewriting Rev 0 firmware. That is the property the Rev 0 allocation was built to
have; see docs/interface-map.md.
Two Rev 0 outcomes did erode that property, and both are worth knowing before rev 1 is scoped:
- The rev 1 buffer packages are new placements, not stuffing changes. The
U_C/U_Dfootprints this table assumed were never drawn. Board area for them has to be reserved deliberately. - The rev 1 DAC channels are not pre-wired through the bus switch. Unused
channels have their MCU-side pins tied to GND, so rev 1 edits
rp2040_gpio_isolation_sn74cb3q3245regardless.U603andU604are entirely empty and are the obvious home for it.
The pin allocation itself still holds — with the one exception that GP14 is
no longer spare. It carries HV_DIAG_EN as of 2026-08-17, so the spare pool is
GP15, GP16, GP18, and GP27.