Skip to content

projects/ janelia/ Mouse Joystick/

Interface Map

Board Manifest: mouse-joystick-pcb

Single-axis mouse joystick: RP2040 with GPIO isolation, one TMC5130A, torque bridge, brake current sink, analog angle output, InstrumentPort-Digital-50 taps, 36 V protected input with 12 V and 5 V regulators; v1.0 as built.

Project: mouse-joystick-pcb.kicad_pro Netclass profiles: hv-power-24v, instrument-taps Datum: aux axis origin at KiCad (137.425, 95) mm Placements: 15; board sheets: 4; nets: 76; open ports: 36

Placements

Instance Block Parent sheet Anchor (mm) Rotation Side Sheet pins Note
instrument_port_digital_50_dut_ffc_boundary_vertical orb_weaver_private:instrument_port_digital_50_dut_ffc_boundary_vertical /instrument_port (0, 36) 180 front own
rp2040_with_gpio_isolation_sn74cb3q3245 orb_weaver_private:rp2040_with_gpio_isolation_sn74cb3q3245 /mcu (-32, 19) 90 front own
5v_to_3v3_tps73733_1 orb_weaver_private:5v_to_3v3_tps73733 /mcu (-28.5, 2) off-grid 90 front own
pycontrol_behavior_port_rj45_boundary_vertical orb_weaver_private:pycontrol_behavior_port_rj45_boundary_vertical / (-39.98, -8) off-grid 270 front own
5v_to_3v3_tps73733_2 orb_weaver_private:5v_to_3v3_tps73733 / (-14, 1.75) off-grid 180 front own
usb2_vertical orb_weaver_private:usb2_vertical / (-45, 11) 90 front own
analog_output_0_3v3_dac80501 orb_weaver_private:analog_output_0_3v3_dac80501 /motor_encoder_switch (-1, 2) 180 front own
quadrature_encoder_input_5v_sn74lvc2g17 orb_weaver_private:quadrature_encoder_input_5v_sn74lvc2g17 /motor_encoder_switch (9, -28) 270 front own
limit_switch_debounced_input_vertical orb_weaver_private:limit_switch_debounced_input_vertical /motor_encoder_switch (18, -36) 180 front library
tmc5130 orb_weaver_private:tmc5130 /motor_encoder_switch (1, -14) 90 front library
hv_to_5v_tpsm365r6 orb_weaver_private:hv_to_5v_tpsm365r6 / (-21.25, -7) 90 front library
torque_bridge_0_3v3_ina821 orb_weaver_private:torque_bridge_0_3v3_ina821 /brake_torque_sensor (37, -18) 180 front own
hv_to_12v_tpsm365r6 orb_weaver_private:hv_to_12v_tpsm365r6 /brake_torque_sensor (35, -29) 180 front library
brake_current_sink_0_49ma_dac80501 orb_weaver_private:brake_current_sink_0_49ma_dac80501 /brake_torque_sensor (37, 12) 180 front own
hv_input_36V_terminal_2pos_5p08_protected_vertical orb_weaver_private:hv_input_36V_terminal_2pos_5p08_protected_vertical / (-44, -24) 270 front own

Board Sheets

Board-authored wrapper sheets: their hierarchical labels and what each meets in the parent.

instrument_port in / (board-authored sheet, 0 parts)

Port Wired to Net Class Meaning
+3.3V +3.3V_MCU /+3.3V_MCU PWR_3V3
DAQ_SCL DAC_SCL_INST /DAC_SCL_INST INSTRUMENT
DAQ_SDA DAC_SDA_INST /DAC_SDA_INST INSTRUMENT
ENCA ENCA_INST /ENCA_INST INSTRUMENT
ENCB ENCB_INST /ENCB_INST INSTRUMENT
FW_TRIG0 FW_TRIG0 /FW_TRIG0 MCU_FINE Firmware timing marker; out as the MCU sees it
FW_TRIG1 FW_TRIG1 /FW_TRIG1 MCU_FINE Firmware timing marker; out as the MCU sees it
GND GND /GND PWR_RETURN_LOCAL
HV_DIAG_EN HV_DIAG_EN_INST /HV_DIAG_EN_INST INSTRUMENT
HV_EN HV_EN_INST /HV_EN_INST INSTRUMENT
LIMIT_SW LIMIT_SW_INST /LIMIT_SW_INST INSTRUMENT
PG_5V PG_5V /PG_5V Default
PG_12V PG_12V /PG_12V Default
PYC_UART_RX PYC_UART_RX_INST /PYC_UART_RX_INST INSTRUMENT
PYC_UART_TX PYC_UART_TX_INST /PYC_UART_TX_INST INSTRUMENT
TMC_DIAG0 TMC_DIAG0_INST /TMC_DIAG0_INST INSTRUMENT
TMC_DIAG1 TMC_DIAG1_INST /TMC_DIAG1_INST INSTRUMENT
TMC_SPI_CIPO TMC_SPI_CIPO_INST /TMC_SPI_CIPO_INST INSTRUMENT
TMC_SPI_COPI TMC_SPI_COPI_INST /TMC_SPI_COPI_INST INSTRUMENT
TMC_SPI_SCK TMC_SPI_SCK_INST /TMC_SPI_SCK_INST INSTRUMENT
TQ_CAL_EN TQ_CAL_EN_INST /TQ_CAL_EN_INST INSTRUMENT
TQ_CONVST_CS TQ_CONVST_CS_INST /TQ_CONVST_CS_INST INSTRUMENT
TQ_SPI_CIPO TQ_SPI_CIPO_INST /TQ_SPI_CIPO_INST INSTRUMENT
TQ_SPI_COPI TQ_SPI_COPI_INST /TQ_SPI_COPI_INST INSTRUMENT
TQ_SPI_SCK TQ_SPI_SCK_INST /TQ_SPI_SCK_INST INSTRUMENT
~{BRAKE_FAULT} ~{BRAKE_FAULT}_INST /~{BRAKE_FAULT}_INST INSTRUMENT
~{BUS_SW_EN} ~{BUS_SW_EN}_INST /~{BUS_SW_EN}_INST INSTRUMENT
~{HV_FAULT} ~{HV_FAULT}_INST /~{HV_FAULT}_INST INSTRUMENT
~{TMC_CS} ~{TMC_CS}_INST /~{TMC_CS}_INST INSTRUMENT
~{TMC_DRV_EN} ~{TMC_DRV_EN}_INST /~{TMC_DRV_EN}_INST INSTRUMENT

mcu in / (board-authored sheet, 11 parts)

Port Wired to Net Class Meaning
+3.3V_MCU +3.3V_MCU /+3.3V_MCU PWR_3V3
+3.3V_SW +3.3V_SW /+3.3V_SW PWR_3V3
+5V_MCU +5V_MCU /+5V_MCU PWR_5V
ANGLE_OUT_MON ANGLE_OUT_MON /ANGLE_OUT_MON MCU_FINE Analog output readback, ADC3¹¹; in as the MCU sees it
BRAKE_ISENSE BRAKE_ISENSE /BRAKE_ISENSE MCU_FINE Brake sink current readback, ADC1¹¹; in as the MCU sees it
DAC_SCL DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
DAC_SCL_INST DAC_SCL_INST /DAC_SCL_INST INSTRUMENT
DAC_SDA DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
DAC_SDA_INST DAC_SDA_INST /DAC_SDA_INST INSTRUMENT
FW_TRIG0 FW_TRIG0 /FW_TRIG0 MCU_FINE Firmware timing marker; out as the MCU sees it
FW_TRIG1 FW_TRIG1 /FW_TRIG1 MCU_FINE Firmware timing marker; out as the MCU sees it
GND GND /GND PWR_RETURN_LOCAL
HV_DIAG_EN HV_DIAG_EN /HV_DIAG_EN MCU_FINE 36 V switch diagnostics enable⁵; out as the MCU sees it
HV_EN HV_EN /HV_EN MCU_FINE 36 V input switch enable; out as the MCU sees it
HV_LATCH HV_LATCH /HV_LATCH MCU_FINE 36 V switch latch-off select¹⁰; out as the MCU sees it
HV_SNS HV_SNS /HV_SNS MCU_FINE 36 V input load-current sense, R_SNS = 249 Ω; in as the MCU sees it
PYC_UART_RX PYC_UART_RX /PYC_UART_RX MCU_FINE UART RX from pyControl (UART0 RX); in as the MCU sees it
PYC_UART_TX PYC_UART_TX /PYC_UART_TX MCU_FINE UART TX to pyControl (UART0 TX); out as the MCU sees it
TMC_DIAG0 TMC_DIAG0 /TMC_DIAG0 Default Driver diagnostic 0; in as the MCU sees it, via the bus switch
TMC_DIAG1 TMC_DIAG1 /TMC_DIAG1 Default Driver diagnostic 1; in as the MCU sees it, via the bus switch
TMC_SPI_CIPO TMC_SPI_CIPO /TMC_SPI_CIPO Default Driver SPI CIPO (SPI0 RX); in as the MCU sees it, via the bus switch
TMC_SPI_COPI TMC_SPI_COPI /TMC_SPI_COPI Default Driver SPI COPI (SPI0 TX); out as the MCU sees it, via the bus switch
TMC_SPI_SCK TMC_SPI_SCK /TMC_SPI_SCK Default Driver SPI clock (SPI0 SCK); out as the MCU sees it, via the bus switch
TQ_CAL_EN TQ_CAL_EN /TQ_CAL_EN Default Torque calibration enable⁷; out as the MCU sees it, via the bus switch
TQ_CONVST_CS TQ_CONVST_CS /TQ_CONVST_CS Default ADS8689 convert-start / frame, plain GPIO⁷; out as the MCU sees it, via the bus switch
TQ_SPI_CIPO TQ_SPI_CIPO /TQ_SPI_CIPO Default Torque ADC SPI CIPO (SPI1 RX)⁷; in as the MCU sees it, via the bus switch
TQ_SPI_COPI TQ_SPI_COPI /TQ_SPI_COPI Default Torque ADC SPI COPI (SPI1 TX)⁷; out as the MCU sees it, via the bus switch
TQ_SPI_SCK TQ_SPI_SCK /TQ_SPI_SCK Default Torque ADC SPI clock (SPI1 SCK)⁷; out as the MCU sees it, via the bus switch
USB_D+ USB_D+ /USB_D+ USB_HS
USB_D- USB_D- /USB_D- USB_HS
~{BRAKE_FAULT} ~{BRAKE_FAULT} /~{BRAKE_FAULT} Default Brake block fault, active low⁷; in as the MCU sees it, via the bus switch
~{BUS_SW_EN}_INST ~{BUS_SW_EN}_INST /~{BUS_SW_EN}_INST INSTRUMENT
~{HV_FAULT} ~{HV_FAULT} /~{HV_FAULT} MCU_FINE 36 V switch fault, open-drain, pull-up 3.3 V¹¹; in as the MCU sees it
~{TMC_CS} ~{TMC_CS} /~{TMC_CS} Default Driver chip select; out as the MCU sees it, via the bus switch
~{TMC_DRV_EN} ~{TMC_DRV_EN} /~{TMC_DRV_EN} Default Driver enable, active low; out as the MCU sees it, via the bus switch

motor_encoder_switch in / (board-authored sheet, 10 parts)

Port Wired to Net Class Meaning
+3.3V +3.3V_SW /+3.3V_SW PWR_3V3
+5V +5V_SW /+5V_SW PWR_5V
ANGLE_OUT_MON ANGLE_OUT_MON /ANGLE_OUT_MON MCU_FINE Analog output readback, ADC3¹¹; in as the MCU sees it
DAC_SCL DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
DAC_SDA DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
DIAG0 TMC_DIAG0 /TMC_DIAG0 Default Driver diagnostic 0; in as the MCU sees it, via the bus switch
DIAG0_INST TMC_DIAG0_INST /TMC_DIAG0_INST INSTRUMENT
DIAG1 TMC_DIAG1 /TMC_DIAG1 Default Driver diagnostic 1; in as the MCU sees it, via the bus switch
DIAG1_INST TMC_DIAG1_INST /TMC_DIAG1_INST INSTRUMENT
ENCA_INST ENCA_INST /ENCA_INST INSTRUMENT
ENCB_INST ENCB_INST /ENCB_INST INSTRUMENT
GND GND /GND PWR_RETURN_LOCAL
LIMIT_SW_INST LIMIT_SW_INST /LIMIT_SW_INST INSTRUMENT
TMC_SPI_CIPO TMC_SPI_CIPO /TMC_SPI_CIPO Default Driver SPI CIPO (SPI0 RX); in as the MCU sees it, via the bus switch
TMC_SPI_CIPO_INST TMC_SPI_CIPO_INST /TMC_SPI_CIPO_INST INSTRUMENT
TMC_SPI_COPI TMC_SPI_COPI /TMC_SPI_COPI Default Driver SPI COPI (SPI0 TX); out as the MCU sees it, via the bus switch
TMC_SPI_COPI_INST TMC_SPI_COPI_INST /TMC_SPI_COPI_INST INSTRUMENT
TMC_SPI_SCK TMC_SPI_SCK /TMC_SPI_SCK Default Driver SPI clock (SPI0 SCK); out as the MCU sees it, via the bus switch
TMC_SPI_SCK_INST TMC_SPI_SCK_INST /TMC_SPI_SCK_INST INSTRUMENT
VM +36V_SW /+36V_SW PWR_HV_BUS
~{DRV_EN} ~{TMC_DRV_EN} /~{TMC_DRV_EN} Default Driver enable, active low; out as the MCU sees it, via the bus switch
~{DRV_EN}_INST ~{TMC_DRV_EN}_INST /~{TMC_DRV_EN}_INST INSTRUMENT
~{TMC_CS} ~{TMC_CS} /~{TMC_CS} Default Driver chip select; out as the MCU sees it, via the bus switch
~{TMC_CS}_INST ~{TMC_CS}_INST /~{TMC_CS}_INST INSTRUMENT

brake_torque_sensor in / (board-authored sheet, 6 parts)

Port Wired to Net Class Meaning
+3.3V +3.3V_SW /+3.3V_SW PWR_3V3
+5V +5V_SW /+5V_SW PWR_5V
+36V +36V_SW /+36V_SW PWR_HV_BUS
BRAKE_ISENSE BRAKE_ISENSE /BRAKE_ISENSE MCU_FINE Brake sink current readback, ADC1¹¹; in as the MCU sees it
DAC_SCL DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
DAC_SDA DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
GND GND /GND PWR_RETURN_LOCAL
PG_12V PG_12V /PG_12V Default
TQ_AOUT_MON open: no-connect flag
TQ_CAL_EN TQ_CAL_EN /TQ_CAL_EN Default Torque calibration enable⁷; out as the MCU sees it, via the bus switch
TQ_CAL_EN_INST TQ_CAL_EN_INST /TQ_CAL_EN_INST INSTRUMENT
TQ_CONVST_CS TQ_CONVST_CS /TQ_CONVST_CS Default ADS8689 convert-start / frame, plain GPIO⁷; out as the MCU sees it, via the bus switch
TQ_CONVST_CS_INST TQ_CONVST_CS_INST /TQ_CONVST_CS_INST INSTRUMENT
TQ_SPI_CIPO TQ_SPI_CIPO /TQ_SPI_CIPO Default Torque ADC SPI CIPO (SPI1 RX)⁷; in as the MCU sees it, via the bus switch
TQ_SPI_CIPO_INST TQ_SPI_CIPO_INST /TQ_SPI_CIPO_INST INSTRUMENT
TQ_SPI_COPI TQ_SPI_COPI /TQ_SPI_COPI Default Torque ADC SPI COPI (SPI1 TX)⁷; out as the MCU sees it, via the bus switch
TQ_SPI_COPI_INST TQ_SPI_COPI_INST /TQ_SPI_COPI_INST INSTRUMENT
TQ_SPI_SCK TQ_SPI_SCK /TQ_SPI_SCK Default Torque ADC SPI clock (SPI1 SCK)⁷; out as the MCU sees it, via the bus switch
TQ_SPI_SCK_INST TQ_SPI_SCK_INST /TQ_SPI_SCK_INST INSTRUMENT
~{BRAKE_FAULT} ~{BRAKE_FAULT} /~{BRAKE_FAULT} Default Brake block fault, active low⁷; in as the MCU sees it, via the bus switch
~{BRAKE_FAULT}_INST ~{BRAKE_FAULT}_INST /~{BRAKE_FAULT}_INST INSTRUMENT

Connections

Per placed block: each port, the label it meets in its parent sheet, the net that resolves to and its class.

instrument_port_digital_50_dut_ffc_boundary_vertical (orb_weaver_private:instrument_port_digital_50_dut_ffc_boundary_vertical in /instrument_port)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_MCU PWR_3V3
DIG0 input FW_TRIG0 /FW_TRIG0 MCU_FINE Firmware timing marker; out as the MCU sees it
DIG1 input FW_TRIG1 /FW_TRIG1 MCU_FINE Firmware timing marker; out as the MCU sees it
DIG2 input PYC_UART_TX /PYC_UART_TX_INST INSTRUMENT
DIG3 input PYC_UART_RX /PYC_UART_RX_INST INSTRUMENT
DIG4 input GND /GND PWR_RETURN_LOCAL
DIG5 input GND /GND PWR_RETURN_LOCAL
DIG6 input GND /GND PWR_RETURN_LOCAL
DIG7 input GND /GND PWR_RETURN_LOCAL
DIG8 input TMC_SPI_SCK /TMC_SPI_SCK_INST INSTRUMENT
DIG9 input ~{TMC_CS} /~{TMC_CS}_INST INSTRUMENT
DIG10 input TMC_SPI_COPI /TMC_SPI_COPI_INST INSTRUMENT
DIG11 input TMC_SPI_CIPO /TMC_SPI_CIPO_INST INSTRUMENT
DIG12 input TMC_DIAG0 /TMC_DIAG0_INST INSTRUMENT
DIG13 input TMC_DIAG1 /TMC_DIAG1_INST INSTRUMENT
DIG14 input ~{TMC_DRV_EN} /~{TMC_DRV_EN}_INST INSTRUMENT
DIG15 input LIMIT_SW /LIMIT_SW_INST INSTRUMENT
DIG16 input TQ_SPI_SCK /TQ_SPI_SCK_INST INSTRUMENT
DIG17 input TQ_CONVST_CS /TQ_CONVST_CS_INST INSTRUMENT
DIG18 input TQ_SPI_COPI /TQ_SPI_COPI_INST INSTRUMENT
DIG19 input TQ_SPI_CIPO /TQ_SPI_CIPO_INST INSTRUMENT
DIG20 input TQ_CAL_EN /TQ_CAL_EN_INST INSTRUMENT
DIG21 input ~{BRAKE_FAULT} /~{BRAKE_FAULT}_INST INSTRUMENT
DIG22 input DAQ_SDA /DAC_SDA_INST INSTRUMENT
DIG23 input DAQ_SCL /DAC_SCL_INST INSTRUMENT
DIG24 input HV_EN /HV_EN_INST INSTRUMENT
DIG25 input HV_DIAG_EN /HV_DIAG_EN_INST INSTRUMENT
DIG26 input ~{HV_FAULT} /~{HV_FAULT}_INST INSTRUMENT
DIG27 input ~{BUS_SW_EN} /~{BUS_SW_EN}_INST INSTRUMENT
DIG28 input ENCA /ENCA_INST INSTRUMENT
DIG29 input ENCB /ENCB_INST INSTRUMENT
DIG30 input PG_5V /PG_5V Default
DIG31 input PG_12V /PG_12V Default
GND passive GND /GND PWR_RETURN_LOCAL

rp2040_with_gpio_isolation_sn74cb3q3245 (orb_weaver_private:rp2040_with_gpio_isolation_sn74cb3q3245 in /mcu)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V_MCU /+3.3V_MCU PWR_3V3
GND passive GND /GND PWR_RETURN_LOCAL
GPIO0 bidirectional PYC_UART_TX /PYC_UART_TX MCU_FINE UART TX to pyControl (UART0 TX); out as the MCU sees it
GPIO1 bidirectional PYC_UART_RX /PYC_UART_RX MCU_FINE UART RX from pyControl (UART0 RX); in as the MCU sees it
GPIO2 bidirectional open: no-connect flag
GPIO3 bidirectional open: no-connect flag
GPIO4 bidirectional open: no-connect flag
GPIO5 bidirectional open: no-connect flag
GPIO6 bidirectional open: no-connect flag
GPIO7 bidirectional open: no-connect flag
GPIO8 bidirectional open: no-connect flag
GPIO10 bidirectional open: no-connect flag
GPIO11 bidirectional open: no-connect flag
GPIO12 bidirectional open: no-connect flag
GPIO13 bidirectional open: no-connect flag
GPIO14 bidirectional HV_DIAG_EN /HV_DIAG_EN MCU_FINE 36 V switch diagnostics enable⁵; out as the MCU sees it
GPIO15 bidirectional open: no-connect flag
GPIO16 bidirectional DAC_SDA_BYP /mcu/DAC_SDA_BYP MCU_FINE
GPIO17 bidirectional DAC_SCL_BYP /mcu/DAC_SCL_BYP MCU_FINE
GPIO18 bidirectional open: no-connect flag
GPIO19 bidirectional HV_EN /HV_EN MCU_FINE 36 V input switch enable; out as the MCU sees it
GPIO20 bidirectional open: no-connect flag
GPIO21 bidirectional FW_TRIG1 /FW_TRIG1 MCU_FINE Firmware timing marker; out as the MCU sees it
GPIO22 bidirectional open: no-connect flag
GPIO23 bidirectional ~{HV_FAULT} /~{HV_FAULT} MCU_FINE 36 V switch fault, open-drain, pull-up 3.3 V¹¹; in as the MCU sees it
GPIO24 bidirectional HV_LATCH /HV_LATCH MCU_FINE 36 V switch latch-off select¹⁰; out as the MCU sees it
GPIO25 bidirectional FW_TRIG0 /FW_TRIG0 MCU_FINE Firmware timing marker; out as the MCU sees it
GPIO26 passive SW_RAIL_SENSE_DIV_2 /mcu/SW_RAIL_SENSE_DIV_2 MCU_FINE +3.3V_SW presence and level, divided by 2 inside mcu (R408/R409); in as the MCU sees it
GPIO27 passive BRAKE_ISENSE /BRAKE_ISENSE MCU_FINE Brake sink current readback, ADC1¹¹; in as the MCU sees it
GPIO28 passive HV_SNS /HV_SNS MCU_FINE 36 V input load-current sense, R_SNS = 249 Ω; in as the MCU sees it
GPIO29 passive ANGLE_OUT_MON /ANGLE_OUT_MON MCU_FINE Analog output readback, ADC3¹¹; in as the MCU sees it
ISO0 passive open: no-connect flag
ISO1 passive open: no-connect flag
ISO2 passive ~{TMC_CS} /~{TMC_CS} Default Driver chip select; out as the MCU sees it, via the bus switch
ISO3 passive ~{TMC_DRV_EN} /~{TMC_DRV_EN} Default Driver enable, active low; out as the MCU sees it, via the bus switch
ISO4 passive TMC_SPI_CIPO /TMC_SPI_CIPO Default Driver SPI CIPO (SPI0 RX); in as the MCU sees it, via the bus switch
ISO5 passive TMC_DIAG0 /TMC_DIAG0 Default Driver diagnostic 0; in as the MCU sees it, via the bus switch
ISO6 passive TMC_SPI_SCK /TMC_SPI_SCK Default Driver SPI clock (SPI0 SCK); out as the MCU sees it, via the bus switch
ISO7 passive TMC_SPI_COPI /TMC_SPI_COPI Default Driver SPI COPI (SPI0 TX); out as the MCU sees it, via the bus switch
ISO8 passive TMC_DIAG1 /TMC_DIAG1 Default Driver diagnostic 1; in as the MCU sees it, via the bus switch
ISO10 passive TQ_SPI_SCK /TQ_SPI_SCK Default Torque ADC SPI clock (SPI1 SCK)⁷; out as the MCU sees it, via the bus switch
ISO11 passive TQ_SPI_COPI /TQ_SPI_COPI Default Torque ADC SPI COPI (SPI1 TX)⁷; out as the MCU sees it, via the bus switch
ISO12 passive TQ_SPI_CIPO /TQ_SPI_CIPO Default Torque ADC SPI CIPO (SPI1 RX)⁷; in as the MCU sees it, via the bus switch
ISO13 passive ~{BRAKE_FAULT} /~{BRAKE_FAULT} Default Brake block fault, active low⁷; in as the MCU sees it, via the bus switch
ISO14 passive open: no-connect flag
ISO15 passive TQ_CONVST_CS /TQ_CONVST_CS Default ADS8689 convert-start / frame, plain GPIO⁷; out as the MCU sees it, via the bus switch
ISO16 passive DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
ISO17 passive DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
ISO18 passive TQ_CAL_EN /TQ_CAL_EN Default Torque calibration enable⁷; out as the MCU sees it, via the bus switch
ISO19 passive open: no-connect flag
ISO20 passive open: no-connect flag
ISO21 passive open: no-connect flag
ISO22 passive open: no-connect flag
ISO23 passive open: no-connect flag
ISO24 passive open: no-connect flag
ISO25 passive open: no-connect flag
ISO26 passive open: no-connect flag
ISO27 passive open: no-connect flag
ISO28 passive open: no-connect flag
ISO29 passive open: no-connect flag
USB_D+ bidirectional USB_D+ /USB_D+ USB_HS
USB_D- bidirectional USB_D- /USB_D- USB_HS
~{BUS_SW_EN} output open: no-connect flag
~{BUS_SW_EN}_INST output ~{BUS_SW_EN}_INST /~{BUS_SW_EN}_INST INSTRUMENT

5v_to_3v3_tps73733_1 (orb_weaver_private:5v_to_3v3_tps73733 in /mcu)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V_MCU /+3.3V_MCU PWR_3V3
+5V passive +5V_MCU /+5V_MCU PWR_5V
GND passive GND /GND PWR_RETURN_LOCAL

pycontrol_behavior_port_rj45_boundary_vertical (orb_weaver_private:pycontrol_behavior_port_rj45_boundary_vertical in /)

Port Shape Wired to Net Class Meaning
+5V_PYC passive +5V_MCU /+5V_MCU PWR_5V
GND passive GND /GND PWR_RETURN_LOCAL
PYC_CONN_5V passive open: no-connect flag
PYC_DIO_A_HOSTTX_SCL_MOSI bidirectional PYC_UART_RX /PYC_UART_RX MCU_FINE UART RX from pyControl (UART0 RX); in as the MCU sees it
PYC_DIO_A_INST output PYC_UART_RX_INST /PYC_UART_RX_INST INSTRUMENT
PYC_DIO_B_HOSTRX_SDA_MISO bidirectional PYC_UART_TX /PYC_UART_TX MCU_FINE UART TX to pyControl (UART0 TX); out as the MCU sees it
PYC_DIO_B_INST output PYC_UART_TX_INST /PYC_UART_TX_INST INSTRUMENT

5v_to_3v3_tps73733_2 (orb_weaver_private:5v_to_3v3_tps73733 in /)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V_SW /+3.3V_SW PWR_3V3
+5V passive +5V_SW /+5V_SW PWR_5V
GND passive GND /GND PWR_RETURN_LOCAL

usb2_vertical (orb_weaver_private:usb2_vertical in /)

Port Shape Wired to Net Class Meaning
+5V_USB passive +5V_MCU /+5V_MCU PWR_5V
GND passive GND /GND PWR_RETURN_LOCAL
USB_D+ bidirectional USB_D+ /USB_D+ USB_HS
USB_D- bidirectional USB_D- /USB_D- USB_HS
VBUS passive open: no-connect flag

analog_output_0_3v3_dac80501 (orb_weaver_private:analog_output_0_3v3_dac80501 in /motor_encoder_switch)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_SW PWR_3V3
+5V passive +5V /+5V_SW PWR_5V
AOUT output ANGLE_OUT /motor_encoder_switch/ANGLE_OUT Default
AOUT_MON output ANGLE_OUT_MON /ANGLE_OUT_MON MCU_FINE Analog output readback, ADC3¹¹; in as the MCU sees it
GND passive GND /GND PWR_RETURN_LOCAL
SCL input DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
SDA bidirectional DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch

quadrature_encoder_input_5v_sn74lvc2g17 (orb_weaver_private:quadrature_encoder_input_5v_sn74lvc2g17 in /motor_encoder_switch)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_SW PWR_3V3
EA input ENCA_5V /motor_encoder_switch/ENCA_5V Default
EB input ENCB_5V /motor_encoder_switch/ENCB_5V Default
ENCA output ENCA /motor_encoder_switch/ENCA Default
ENCA_INST output ENCA_INST /ENCA_INST INSTRUMENT
ENCB output ENCB /motor_encoder_switch/ENCB Default
ENCB_INST output ENCB_INST /ENCB_INST INSTRUMENT
GND passive GND /GND PWR_RETURN_LOCAL
VENC passive +5V /+5V_SW PWR_5V

limit_switch_debounced_input_vertical (orb_weaver_private:limit_switch_debounced_input_vertical in /motor_encoder_switch)

Port Shape Wired to Net Class Meaning
+3.3V_SW passive +3.3V /+3.3V_SW PWR_3V3
GND passive GND /GND PWR_RETURN_LOCAL
LIMIT_SW output LIMIT_SW /motor_encoder_switch/LIMIT_SW Default
LIMIT_SW_INST output LIMIT_SW_INST /LIMIT_SW_INST INSTRUMENT

tmc5130 (orb_weaver_private:tmc5130 in /motor_encoder_switch)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_SW PWR_3V3
+5V passive +5V /+5V_SW PWR_5V
CIPO output TMC_SPI_CIPO /TMC_SPI_CIPO Default Driver SPI CIPO (SPI0 RX); in as the MCU sees it, via the bus switch
CIPO_INST output TMC_SPI_CIPO_INST /TMC_SPI_CIPO_INST INSTRUMENT
COPI input TMC_SPI_COPI /TMC_SPI_COPI Default Driver SPI COPI (SPI0 TX); out as the MCU sees it, via the bus switch
COPI_INST output TMC_SPI_COPI_INST /TMC_SPI_COPI_INST INSTRUMENT
ENCA input ENCA /motor_encoder_switch/ENCA Default
ENCB input ENCB /motor_encoder_switch/ENCB Default
ENCN input open: no-connect flag
GND passive GND /GND PWR_RETURN_LOCAL
OA1 passive OA1 /motor_encoder_switch/OA1 MOTOR_2A
OA2 passive OA2 /motor_encoder_switch/OA2 MOTOR_2A
OB1 passive OB1 /motor_encoder_switch/OB1 MOTOR_2A
OB2 passive OB2 /motor_encoder_switch/OB2 MOTOR_2A
REFL input LIMIT_SW /motor_encoder_switch/LIMIT_SW Default
REFR input GND /GND PWR_RETURN_LOCAL
SCK output TMC_SPI_SCK /TMC_SPI_SCK Default Driver SPI clock (SPI0 SCK); out as the MCU sees it, via the bus switch
SCK_INST output TMC_SPI_SCK_INST /TMC_SPI_SCK_INST INSTRUMENT
SWN_DIAG0 bidirectional DIAG0 /TMC_DIAG0 Default Driver diagnostic 0; in as the MCU sees it, via the bus switch
SWN_DIAG0_INST output DIAG0_INST /TMC_DIAG0_INST INSTRUMENT
SWP_DIAG1 bidirectional DIAG1 /TMC_DIAG1 Default Driver diagnostic 1; in as the MCU sees it, via the bus switch
SWP_DIAG1_INST output DIAG1_INST /TMC_DIAG1_INST INSTRUMENT
SWSEL input open: no-connect flag
VM passive VM /+36V_SW PWR_HV_BUS
~{CS} output ~{TMC_CS} /~{TMC_CS} Default Driver chip select; out as the MCU sees it, via the bus switch
~{CS}_INST output ~{TMC_CS}_INST /~{TMC_CS}_INST INSTRUMENT
~{DRV_EN} input ~{DRV_EN} /~{TMC_DRV_EN} Default Driver enable, active low; out as the MCU sees it, via the bus switch
~{DRV_EN}_INST output ~{DRV_EN}_INST /~{TMC_DRV_EN}_INST INSTRUMENT

hv_to_5v_tpsm365r6 (orb_weaver_private:hv_to_5v_tpsm365r6 in /)

Port Shape Wired to Net Class Meaning
+5V passive +5V_SW /+5V_SW PWR_5V
GND passive GND /GND PWR_RETURN_LOCAL
PGOOD output PG_5V /PG_5V Default
VIN passive +36V_SW /+36V_SW PWR_HV_BUS

torque_bridge_0_3v3_ina821 (orb_weaver_private:torque_bridge_0_3v3_ina821 in /brake_torque_sensor)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_SW PWR_3V3
+5V passive +5V /+5V_SW PWR_5V
+12V passive +12V /brake_torque_sensor/+12V PWR_12V_LOCAL
CIPO output TQ_SPI_CIPO /TQ_SPI_CIPO Default Torque ADC SPI CIPO (SPI1 RX)⁷; in as the MCU sees it, via the bus switch
CIPO_INST output TQ_SPI_CIPO_INST /TQ_SPI_CIPO_INST INSTRUMENT
COPI input TQ_SPI_COPI /TQ_SPI_COPI Default Torque ADC SPI COPI (SPI1 TX)⁷; out as the MCU sees it, via the bus switch
COPI_INST output TQ_SPI_COPI_INST /TQ_SPI_COPI_INST INSTRUMENT
GND passive GND /GND PWR_RETURN_LOCAL
SCK input TQ_SPI_SCK /TQ_SPI_SCK Default Torque ADC SPI clock (SPI1 SCK)⁷; out as the MCU sees it, via the bus switch
SCK_INST output TQ_SPI_SCK_INST /TQ_SPI_SCK_INST INSTRUMENT
TQ_AOUT output TQ_AOUT /brake_torque_sensor/TQ_AOUT Default
TQ_AOUT_MON output TQ_AOUT_MON /brake_torque_sensor/torque_bridge_0_3v3_ina821/TQ_AOUT_MON Default
TQ_CAL_EN input TQ_CAL_EN /TQ_CAL_EN Default Torque calibration enable⁷; out as the MCU sees it, via the bus switch
TQ_CAL_EN_INST output TQ_CAL_EN_INST /TQ_CAL_EN_INST INSTRUMENT
TQ_CONVST_CS input TQ_CONVST_CS /TQ_CONVST_CS Default ADS8689 convert-start / frame, plain GPIO⁷; out as the MCU sees it, via the bus switch
TQ_CONVST_CS_INST output TQ_CONVST_CS_INST /TQ_CONVST_CS_INST INSTRUMENT

hv_to_12v_tpsm365r6 (orb_weaver_private:hv_to_12v_tpsm365r6 in /brake_torque_sensor)

Port Shape Wired to Net Class Meaning
+12V passive +12V /brake_torque_sensor/+12V PWR_12V_LOCAL
GND passive GND /GND PWR_RETURN_LOCAL
PGOOD output PG_12V /PG_12V Default
VIN passive +36V /+36V_SW PWR_HV_BUS

brake_current_sink_0_49ma_dac80501 (orb_weaver_private:brake_current_sink_0_49ma_dac80501 in /brake_torque_sensor)

Port Shape Wired to Net Class Meaning
+3.3V passive +3.3V /+3.3V_SW PWR_3V3
+5V passive +5V /+5V_SW PWR_5V
+12V passive +12V /brake_torque_sensor/+12V PWR_12V_LOCAL
+36V passive +36V /+36V_SW PWR_HV_BUS
BRAKE_ISENSE output BRAKE_ISENSE /BRAKE_ISENSE MCU_FINE Brake sink current readback, ADC1¹¹; in as the MCU sees it
GND passive GND /GND PWR_RETURN_LOCAL
SCL input DAC_SCL /DAC_SCL Default I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
SDA bidirectional DAC_SDA /DAC_SDA Default I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
~{BRAKE_FAULT} output ~{BRAKE_FAULT} /~{BRAKE_FAULT} Default Brake block fault, active low⁷; in as the MCU sees it, via the bus switch
~{BRAKE_FAULT}_INST output ~{BRAKE_FAULT}_INST /~{BRAKE_FAULT}_INST INSTRUMENT

hv_input_36V_terminal_2pos_5p08_protected_vertical (orb_weaver_private:hv_input_36V_terminal_2pos_5p08_protected_vertical in /)

Port Shape Wired to Net Class Meaning
+3.3V_MCU passive +3.3V_MCU /+3.3V_MCU PWR_3V3
+36V passive +36V_SW /+36V_SW PWR_HV_BUS
DIAG_ENABLE input HV_DIAG_EN /HV_DIAG_EN MCU_FINE 36 V switch diagnostics enable⁵; out as the MCU sees it
DIAG_ENABLE_INST output HV_DIAG_EN_INST /HV_DIAG_EN_INST INSTRUMENT
ENABLE input HV_EN /HV_EN MCU_FINE 36 V input switch enable; out as the MCU sees it
ENABLE_INST output HV_EN_INST /HV_EN_INST INSTRUMENT
GND passive GND /GND PWR_RETURN_LOCAL
HV_IN passive open: no-connect flag
LATCH input HV_LATCH /HV_LATCH MCU_FINE 36 V switch latch-off select¹⁰; out as the MCU sees it
SNS output HV_SNS /HV_SNS MCU_FINE 36 V input load-current sense, R_SNS = 249 Ω; in as the MCU sees it
~{FAULT} output ~{HV_FAULT} /~{HV_FAULT} MCU_FINE 36 V switch fault, open-drain, pull-up 3.3 V¹¹; in as the MCU sees it
~{FAULT}_INST output ~{HV_FAULT}_INST /~{HV_FAULT}_INST INSTRUMENT

Nets

Every net a block port or a board sheet's port reaches, by its qualified name.

Net Class GPIO Ports Meaning
/+3.3V_MCU PWR_3V3 instrument_port (sheet)/+3.3V, mcu (sheet)/+3.3V_MCU, instrument_port_digital_50_dut_ffc_boundary_vertical/+3.3V, rp2040_with_gpio_isolation_sn74cb3q3245/+3.3V, 5v_to_3v3_tps73733_1/+3.3V, hv_input_36V_terminal_2pos_5p08_protected_vertical/+3.3V_MCU
/+3.3V_SW PWR_3V3 mcu (sheet)/+3.3V_SW, motor_encoder_switch (sheet)/+3.3V, brake_torque_sensor (sheet)/+3.3V, 5v_to_3v3_tps73733_2/+3.3V, analog_output_0_3v3_dac80501/+3.3V, quadrature_encoder_input_5v_sn74lvc2g17/+3.3V, limit_switch_debounced_input_vertical/+3.3V_SW, tmc5130/+3.3V, torque_bridge_0_3v3_ina821/+3.3V, brake_current_sink_0_49ma_dac80501/+3.3V
/+36V_SW PWR_HV_BUS motor_encoder_switch (sheet)/VM, brake_torque_sensor (sheet)/+36V, tmc5130/VM, hv_to_5v_tpsm365r6/VIN, hv_to_12v_tpsm365r6/VIN, brake_current_sink_0_49ma_dac80501/+36V, hv_input_36V_terminal_2pos_5p08_protected_vertical/+36V
/+5V_MCU PWR_5V mcu (sheet)/+5V_MCU, 5v_to_3v3_tps73733_1/+5V, pycontrol_behavior_port_rj45_boundary_vertical/+5V_PYC, usb2_vertical/+5V_USB
/+5V_SW PWR_5V motor_encoder_switch (sheet)/+5V, brake_torque_sensor (sheet)/+5V, 5v_to_3v3_tps73733_2/+5V, analog_output_0_3v3_dac80501/+5V, quadrature_encoder_input_5v_sn74lvc2g17/VENC, tmc5130/+5V, hv_to_5v_tpsm365r6/+5V, torque_bridge_0_3v3_ina821/+5V, brake_current_sink_0_49ma_dac80501/+5V
/ANGLE_OUT_MON MCU_FINE GP29 mcu (sheet)/ANGLE_OUT_MON, motor_encoder_switch (sheet)/ANGLE_OUT_MON, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO29, analog_output_0_3v3_dac80501/AOUT_MON Analog output readback, ADC3¹¹; in as the MCU sees it
/BRAKE_ISENSE MCU_FINE GP27 mcu (sheet)/BRAKE_ISENSE, brake_torque_sensor (sheet)/BRAKE_ISENSE, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO27, brake_current_sink_0_49ma_dac80501/BRAKE_ISENSE Brake sink current readback, ADC1¹¹; in as the MCU sees it
/DAC_SCL Default GP17 mcu (sheet)/DAC_SCL, motor_encoder_switch (sheet)/DAC_SCL, brake_torque_sensor (sheet)/DAC_SCL, rp2040_with_gpio_isolation_sn74cb3q3245/ISO17, analog_output_0_3v3_dac80501/SCL, brake_current_sink_0_49ma_dac80501/SCL I2C0 SCL — same bus⁷; out as the MCU sees it, via the bus switch
/DAC_SCL_INST INSTRUMENT instrument_port (sheet)/DAQ_SCL, mcu (sheet)/DAC_SCL_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG23
/DAC_SDA Default GP16 mcu (sheet)/DAC_SDA, motor_encoder_switch (sheet)/DAC_SDA, brake_torque_sensor (sheet)/DAC_SDA, rp2040_with_gpio_isolation_sn74cb3q3245/ISO16, analog_output_0_3v3_dac80501/SDA, brake_current_sink_0_49ma_dac80501/SDA I2C0 SDA — analog 0x48, brake 0x49⁷; bidi as the MCU sees it, via the bus switch
/DAC_SDA_INST INSTRUMENT instrument_port (sheet)/DAQ_SDA, mcu (sheet)/DAC_SDA_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG22
/ENCA_INST INSTRUMENT instrument_port (sheet)/ENCA, motor_encoder_switch (sheet)/ENCA_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG28, quadrature_encoder_input_5v_sn74lvc2g17/ENCA_INST
/ENCB_INST INSTRUMENT instrument_port (sheet)/ENCB, motor_encoder_switch (sheet)/ENCB_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG29, quadrature_encoder_input_5v_sn74lvc2g17/ENCB_INST
/FW_TRIG0 MCU_FINE GP25 instrument_port (sheet)/FW_TRIG0, mcu (sheet)/FW_TRIG0, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG0, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO25 Firmware timing marker; out as the MCU sees it
/FW_TRIG1 MCU_FINE GP21 instrument_port (sheet)/FW_TRIG1, mcu (sheet)/FW_TRIG1, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG1, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO21 Firmware timing marker; out as the MCU sees it
/GND PWR_RETURN_LOCAL instrument_port (sheet)/GND, mcu (sheet)/GND, motor_encoder_switch (sheet)/GND, brake_torque_sensor (sheet)/GND, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG4, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG5, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG6, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG7, instrument_port_digital_50_dut_ffc_boundary_vertical/GND, rp2040_with_gpio_isolation_sn74cb3q3245/GND, 5v_to_3v3_tps73733_1/GND, pycontrol_behavior_port_rj45_boundary_vertical/GND, 5v_to_3v3_tps73733_2/GND, usb2_vertical/GND, analog_output_0_3v3_dac80501/GND, quadrature_encoder_input_5v_sn74lvc2g17/GND, limit_switch_debounced_input_vertical/GND, tmc5130/GND, tmc5130/REFR, hv_to_5v_tpsm365r6/GND, torque_bridge_0_3v3_ina821/GND, hv_to_12v_tpsm365r6/GND, brake_current_sink_0_49ma_dac80501/GND, hv_input_36V_terminal_2pos_5p08_protected_vertical/GND
/HV_DIAG_EN MCU_FINE GP14 mcu (sheet)/HV_DIAG_EN, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO14, hv_input_36V_terminal_2pos_5p08_protected_vertical/DIAG_ENABLE 36 V switch diagnostics enable⁵; out as the MCU sees it
/HV_DIAG_EN_INST INSTRUMENT instrument_port (sheet)/HV_DIAG_EN, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG25, hv_input_36V_terminal_2pos_5p08_protected_vertical/DIAG_ENABLE_INST
/HV_EN MCU_FINE GP19 mcu (sheet)/HV_EN, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO19, hv_input_36V_terminal_2pos_5p08_protected_vertical/ENABLE 36 V input switch enable; out as the MCU sees it
/HV_EN_INST INSTRUMENT instrument_port (sheet)/HV_EN, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG24, hv_input_36V_terminal_2pos_5p08_protected_vertical/ENABLE_INST
/HV_LATCH MCU_FINE GP24 mcu (sheet)/HV_LATCH, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO24, hv_input_36V_terminal_2pos_5p08_protected_vertical/LATCH 36 V switch latch-off select¹⁰; out as the MCU sees it
/HV_SNS MCU_FINE GP28 mcu (sheet)/HV_SNS, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO28, hv_input_36V_terminal_2pos_5p08_protected_vertical/SNS 36 V input load-current sense, R_SNS = 249 Ω; in as the MCU sees it
/LIMIT_SW_INST INSTRUMENT instrument_port (sheet)/LIMIT_SW, motor_encoder_switch (sheet)/LIMIT_SW_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG15, limit_switch_debounced_input_vertical/LIMIT_SW_INST
/PG_12V Default instrument_port (sheet)/PG_12V, brake_torque_sensor (sheet)/PG_12V, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG31, hv_to_12v_tpsm365r6/PGOOD
/PG_5V Default instrument_port (sheet)/PG_5V, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG30, hv_to_5v_tpsm365r6/PGOOD
/PYC_UART_RX MCU_FINE GP1 mcu (sheet)/PYC_UART_RX, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO1, pycontrol_behavior_port_rj45_boundary_vertical/PYC_DIO_A_HOSTTX_SCL_MOSI UART RX from pyControl (UART0 RX); in as the MCU sees it
/PYC_UART_RX_INST INSTRUMENT instrument_port (sheet)/PYC_UART_RX, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG3, pycontrol_behavior_port_rj45_boundary_vertical/PYC_DIO_A_INST
/PYC_UART_TX MCU_FINE GP0 mcu (sheet)/PYC_UART_TX, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO0, pycontrol_behavior_port_rj45_boundary_vertical/PYC_DIO_B_HOSTRX_SDA_MISO UART TX to pyControl (UART0 TX); out as the MCU sees it
/PYC_UART_TX_INST INSTRUMENT instrument_port (sheet)/PYC_UART_TX, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG2, pycontrol_behavior_port_rj45_boundary_vertical/PYC_DIO_B_INST
/TMC_DIAG0 Default GP5 mcu (sheet)/TMC_DIAG0, motor_encoder_switch (sheet)/DIAG0, rp2040_with_gpio_isolation_sn74cb3q3245/ISO5, tmc5130/SWN_DIAG0 Driver diagnostic 0; in as the MCU sees it, via the bus switch
/TMC_DIAG0_INST INSTRUMENT instrument_port (sheet)/TMC_DIAG0, motor_encoder_switch (sheet)/DIAG0_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG12, tmc5130/SWN_DIAG0_INST
/TMC_DIAG1 Default GP8 mcu (sheet)/TMC_DIAG1, motor_encoder_switch (sheet)/DIAG1, rp2040_with_gpio_isolation_sn74cb3q3245/ISO8, tmc5130/SWP_DIAG1 Driver diagnostic 1; in as the MCU sees it, via the bus switch
/TMC_DIAG1_INST INSTRUMENT instrument_port (sheet)/TMC_DIAG1, motor_encoder_switch (sheet)/DIAG1_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG13, tmc5130/SWP_DIAG1_INST
/TMC_SPI_CIPO Default GP4 mcu (sheet)/TMC_SPI_CIPO, motor_encoder_switch (sheet)/TMC_SPI_CIPO, rp2040_with_gpio_isolation_sn74cb3q3245/ISO4, tmc5130/CIPO Driver SPI CIPO (SPI0 RX); in as the MCU sees it, via the bus switch
/TMC_SPI_CIPO_INST INSTRUMENT instrument_port (sheet)/TMC_SPI_CIPO, motor_encoder_switch (sheet)/TMC_SPI_CIPO_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG11, tmc5130/CIPO_INST
/TMC_SPI_COPI Default GP7 mcu (sheet)/TMC_SPI_COPI, motor_encoder_switch (sheet)/TMC_SPI_COPI, rp2040_with_gpio_isolation_sn74cb3q3245/ISO7, tmc5130/COPI Driver SPI COPI (SPI0 TX); out as the MCU sees it, via the bus switch
/TMC_SPI_COPI_INST INSTRUMENT instrument_port (sheet)/TMC_SPI_COPI, motor_encoder_switch (sheet)/TMC_SPI_COPI_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG10, tmc5130/COPI_INST
/TMC_SPI_SCK Default GP6 mcu (sheet)/TMC_SPI_SCK, motor_encoder_switch (sheet)/TMC_SPI_SCK, rp2040_with_gpio_isolation_sn74cb3q3245/ISO6, tmc5130/SCK Driver SPI clock (SPI0 SCK); out as the MCU sees it, via the bus switch
/TMC_SPI_SCK_INST INSTRUMENT instrument_port (sheet)/TMC_SPI_SCK, motor_encoder_switch (sheet)/TMC_SPI_SCK_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG8, tmc5130/SCK_INST
/TQ_CAL_EN Default GP18 mcu (sheet)/TQ_CAL_EN, brake_torque_sensor (sheet)/TQ_CAL_EN, rp2040_with_gpio_isolation_sn74cb3q3245/ISO18, torque_bridge_0_3v3_ina821/TQ_CAL_EN Torque calibration enable⁷; out as the MCU sees it, via the bus switch
/TQ_CAL_EN_INST INSTRUMENT instrument_port (sheet)/TQ_CAL_EN, brake_torque_sensor (sheet)/TQ_CAL_EN_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG20, torque_bridge_0_3v3_ina821/TQ_CAL_EN_INST
/TQ_CONVST_CS Default GP15 mcu (sheet)/TQ_CONVST_CS, brake_torque_sensor (sheet)/TQ_CONVST_CS, rp2040_with_gpio_isolation_sn74cb3q3245/ISO15, torque_bridge_0_3v3_ina821/TQ_CONVST_CS ADS8689 convert-start / frame, plain GPIO⁷; out as the MCU sees it, via the bus switch
/TQ_CONVST_CS_INST INSTRUMENT instrument_port (sheet)/TQ_CONVST_CS, brake_torque_sensor (sheet)/TQ_CONVST_CS_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG17, torque_bridge_0_3v3_ina821/TQ_CONVST_CS_INST
/TQ_SPI_CIPO Default GP12 mcu (sheet)/TQ_SPI_CIPO, brake_torque_sensor (sheet)/TQ_SPI_CIPO, rp2040_with_gpio_isolation_sn74cb3q3245/ISO12, torque_bridge_0_3v3_ina821/CIPO Torque ADC SPI CIPO (SPI1 RX)⁷; in as the MCU sees it, via the bus switch
/TQ_SPI_CIPO_INST INSTRUMENT instrument_port (sheet)/TQ_SPI_CIPO, brake_torque_sensor (sheet)/TQ_SPI_CIPO_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG19, torque_bridge_0_3v3_ina821/CIPO_INST
/TQ_SPI_COPI Default GP11 mcu (sheet)/TQ_SPI_COPI, brake_torque_sensor (sheet)/TQ_SPI_COPI, rp2040_with_gpio_isolation_sn74cb3q3245/ISO11, torque_bridge_0_3v3_ina821/COPI Torque ADC SPI COPI (SPI1 TX)⁷; out as the MCU sees it, via the bus switch
/TQ_SPI_COPI_INST INSTRUMENT instrument_port (sheet)/TQ_SPI_COPI, brake_torque_sensor (sheet)/TQ_SPI_COPI_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG18, torque_bridge_0_3v3_ina821/COPI_INST
/TQ_SPI_SCK Default GP10 mcu (sheet)/TQ_SPI_SCK, brake_torque_sensor (sheet)/TQ_SPI_SCK, rp2040_with_gpio_isolation_sn74cb3q3245/ISO10, torque_bridge_0_3v3_ina821/SCK Torque ADC SPI clock (SPI1 SCK)⁷; out as the MCU sees it, via the bus switch
/TQ_SPI_SCK_INST INSTRUMENT instrument_port (sheet)/TQ_SPI_SCK, brake_torque_sensor (sheet)/TQ_SPI_SCK_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG16, torque_bridge_0_3v3_ina821/SCK_INST
/USB_D+ USB_HS mcu (sheet)/USB_D+, rp2040_with_gpio_isolation_sn74cb3q3245/USB_D+, usb2_vertical/USB_D+
/USB_D- USB_HS mcu (sheet)/USB_D-, rp2040_with_gpio_isolation_sn74cb3q3245/USB_D-, usb2_vertical/USB_D-
/~{BRAKE_FAULT} Default GP13 mcu (sheet)/~{BRAKE_FAULT}, brake_torque_sensor (sheet)/~{BRAKE_FAULT}, rp2040_with_gpio_isolation_sn74cb3q3245/ISO13, brake_current_sink_0_49ma_dac80501/~{BRAKE_FAULT} Brake block fault, active low⁷; in as the MCU sees it, via the bus switch
/~{BRAKE_FAULT}_INST INSTRUMENT instrument_port (sheet)/~{BRAKE_FAULT}, brake_torque_sensor (sheet)/~{BRAKE_FAULT}_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG21, brake_current_sink_0_49ma_dac80501/~{BRAKE_FAULT}_INST
/~{BUS_SW_EN}_INST INSTRUMENT instrument_port (sheet)/~{BUS_SW_EN}, mcu (sheet)/~{BUS_SW_EN}_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG27, rp2040_with_gpio_isolation_sn74cb3q3245/~{BUS_SW_EN}_INST
/~{HV_FAULT} MCU_FINE GP23 mcu (sheet)/~{HV_FAULT}, rp2040_with_gpio_isolation_sn74cb3q3245/GPIO23, hv_input_36V_terminal_2pos_5p08_protected_vertical/~{FAULT} 36 V switch fault, open-drain, pull-up 3.3 V¹¹; in as the MCU sees it
/~{HV_FAULT}_INST INSTRUMENT instrument_port (sheet)/~{HV_FAULT}, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG26, hv_input_36V_terminal_2pos_5p08_protected_vertical/~{FAULT}_INST
/~{TMC_CS} Default GP2 mcu (sheet)/~{TMC_CS}, motor_encoder_switch (sheet)/~{TMC_CS}, rp2040_with_gpio_isolation_sn74cb3q3245/ISO2, tmc5130/~{CS} Driver chip select; out as the MCU sees it, via the bus switch
/~{TMC_CS}_INST INSTRUMENT instrument_port (sheet)/~{TMC_CS}, motor_encoder_switch (sheet)/~{TMC_CS}_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG9, tmc5130/~{CS}_INST
/~{TMC_DRV_EN} Default GP3 mcu (sheet)/~{TMC_DRV_EN}, motor_encoder_switch (sheet)/~{DRV_EN}, rp2040_with_gpio_isolation_sn74cb3q3245/ISO3, tmc5130/~{DRV_EN} Driver enable, active low; out as the MCU sees it, via the bus switch
/~{TMC_DRV_EN}_INST INSTRUMENT instrument_port (sheet)/~{TMC_DRV_EN}, motor_encoder_switch (sheet)/~{DRV_EN}_INST, instrument_port_digital_50_dut_ffc_boundary_vertical/DIG14, tmc5130/~{DRV_EN}_INST
/brake_torque_sensor/+12V PWR_12V_LOCAL torque_bridge_0_3v3_ina821/+12V, hv_to_12v_tpsm365r6/+12V, brake_current_sink_0_49ma_dac80501/+12V
/brake_torque_sensor/TQ_AOUT Default torque_bridge_0_3v3_ina821/TQ_AOUT
/mcu/DAC_SCL_BYP MCU_FINE rp2040_with_gpio_isolation_sn74cb3q3245/GPIO17
/mcu/DAC_SDA_BYP MCU_FINE rp2040_with_gpio_isolation_sn74cb3q3245/GPIO16
/mcu/SW_RAIL_SENSE_DIV_2 MCU_FINE GP26 rp2040_with_gpio_isolation_sn74cb3q3245/GPIO26 +3.3V_SW presence and level, divided by 2 inside mcu (R408/R409); in as the MCU sees it
/motor_encoder_switch/ANGLE_OUT Default analog_output_0_3v3_dac80501/AOUT
/motor_encoder_switch/ENCA Default quadrature_encoder_input_5v_sn74lvc2g17/ENCA, tmc5130/ENCA
/motor_encoder_switch/ENCA_5V Default quadrature_encoder_input_5v_sn74lvc2g17/EA
/motor_encoder_switch/ENCB Default quadrature_encoder_input_5v_sn74lvc2g17/ENCB, tmc5130/ENCB
/motor_encoder_switch/ENCB_5V Default quadrature_encoder_input_5v_sn74lvc2g17/EB
/motor_encoder_switch/LIMIT_SW Default limit_switch_debounced_input_vertical/LIMIT_SW, tmc5130/REFL
/motor_encoder_switch/OA1 MOTOR_2A tmc5130/OA1
/motor_encoder_switch/OA2 MOTOR_2A tmc5130/OA2
/motor_encoder_switch/OB1 MOTOR_2A tmc5130/OB1
/motor_encoder_switch/OB2 MOTOR_2A tmc5130/OB2
/brake_torque_sensor/torque_bridge_0_3v3_ina821/TQ_AOUT_MON Default torque_bridge_0_3v3_ina821/TQ_AOUT_MON

Open Ports

Instance Port Why
brake_torque_sensor TQ_AOUT_MON no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO2 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO3 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO4 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO5 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO6 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO7 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO8 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO10 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO11 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO12 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO13 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO15 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO18 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO20 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 GPIO22 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO0 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO1 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO14 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO19 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO20 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO21 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO22 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO23 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO24 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO25 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO26 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO27 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO28 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ISO29 no-connect flag
rp2040_with_gpio_isolation_sn74cb3q3245 ~{BUS_SW_EN} no-connect flag
pycontrol_behavior_port_rj45_boundary_vertical PYC_CONN_5V no-connect flag
usb2_vertical VBUS no-connect flag
tmc5130 ENCN no-connect flag
tmc5130 SWSEL no-connect flag
hv_input_36V_terminal_2pos_5p08_protected_vertical HV_IN no-connect flag

v1.0 allocation adopted 2026-09-16. The table below is now the v1.0 allocation, not the legacy 40-pin one. Eight pins changed function and one left the sheet entirely; every change is listed in The v1.0 GPIO Changes and marked in the table. The digital-50 channel pinout is in InstrumentPort-Digital-50 Pinout, which is now the only instrument-port pinout here: the 40-pin overlay section was deleted on 2026-09-17 rather than kept alongside it.

Status: v1.0 allocation adopted; schematic mid-rebuild. Blocks were removed from the board on 2026-09-16 to be replaced with updated versions, all traces and vias deleted, and the board-level TMC input biases moved into motor_encoder_switch and renumbered R1104-R1107 (the range was written as -R1108; there are seven, not eight). Rev 0's reviewed, ERC-clean state is the 2026-08-17 snapshot in git, not the working tree. Date: 2026-09-16 Board: single-axis, RP2040, one TMC5130A Source profile: board-interfaces/docs/instrument-port-digital.md (v1.0). The legacy 40-pin profile is board-interfaces/docs/instrument-port-40.md; this board no longer implements it.

This document records this board's RP2040 GPIO allocation. It covers two scopes at once: what Rev 0 populates, and the full scope that later revisions grow into. The InstrumentPort-Digital-50 profile itself belongs in board-interfaces and is not redefined here.

Rev 0 Is A Strict Subset

Rev 0 was scaled back to get testable hardware in the researcher's hands sooner. See docs/rev0-plan.md for what it contains and why.

The allocation below is written so that no pin moves between Rev 0 and full scope. Rev 0 populates 18 of the 24 allocated GPIOs; the other 6 are reserved, routed nowhere, and become live in later revisions without disturbing anything Rev 0 established. Firmware written against Rev 0 keeps working.

GP24 ~{INSTR_BUF_OE} became live on 2026-08-16 when the InstrumentPort moved into Rev 0; it was already allocated and already in the bypass group. The same day, GP14-GP16 and GP18 left the allocation entirely when the encoder and limit switch stopped reaching the MCU, which is why the denominator dropped from 29 to 25. GP14 came back on 2026-08-17 as HV_DIAG_EN, taking the denominator to 26. GP12 and GP17 left it on 2026-08-26 when the analog output block was built against a part with no LDAC or CLR pin, which takes the denominator to 24 and leaves six spares.

That property is not free, and it cost exactly one trade — see The Bus Switch Costs A Pin.

RP2040 GPIO Allocation

Checked against RP2040 peripheral pin muxing. UART0 is the pyControl link, SPI0 serves the TMC5130A, SPI1 serves the torque ADC, and I2C0 serves the two DAC blocks at 0x48 and 0x49. Chip selects are plain GPIO, not hardware CSn — RP2040's PL022 hardware chip select does not hold CS low across the TMC5130A's 40-bit multi-byte datagrams, and the ADS8689's CONVST/CS both starts the conversion and frames the read, so firmware needs direct control of its timing.

SPI1 SCK exists only on GP10, GP14 and GP26, and 14 and 26 are spent. That single constraint is why the freed DAC-SPI pins became the torque bus and the I2C pair moved to GP16/GP17 rather than the other way round.

The rp2040_with_gpio_isolation_sn74cb3q3245 block exposes these as two label banks: raw GPIO0-GPIO8 and GPIO10-GPIO29, and switched ISO0-ISO8 and ISO10-ISO29. There is no GPIO9, no ISO9 and no ~{OE} port — GP9 is consumed inside the block. The ADC pins are plain GPIO26-GPIO29, not the old GPIO26_ADC0 names.

Iso column: sw = wire the board net to the block's ISOn pin, so the signal crosses the bus switch; byp = wire it to the raw GPIOn pin. Per index they are the same node whenever the bus is enabled — use one or the other, never both. The rule is the powered domain of the far end, not convenience — see GPIO Isolation.

Dir column: direction as seen by the RP2040. out = the MCU drives it, in = the MCU reads it. This is also the KiCad shape to give the matching hierarchical pin on the mcu sheet — out → Output, in → Input. See Signal Direction And Sheet Pin Types.

The allocation table itself is generated from kicad/board.yaml's nets (the gpio and meaning of every MCU net) into docs/generated/gpio-allocation.md by make docs, with the class and every block or sheet port each net reaches; it replaced the hand-written table here on 2026-10-13. The numbered notes below are what its meanings cite, and the Iso and Dir reading above still applies: a net whose meaning says "via the bus switch" is switched, one that does not bypasses it, and "out" or "in" is the direction as the MCU sees it.

¹ Rev 0 lands FW_TRIG0/FW_TRIG1 on TRIG0/TRIG1 of the InstrumentPort. They are the cheapest bring-up instrumentation on the board and the natural trigger source for a long capture. They were also to get labelled test points; as of 2026-08-17 no test points exist on the board — see Test Points — Outstanding.

² ~{BUS_SW_EN} is driven only inside the MCU block. GP9 drives all four ~OE inputs plus the 7.87 kΩ pull-up inside rp2040_with_gpio_isolation_sn74cb3q3245, so the board must never drive it — the pull-up is the only thing defining the reset state, and a second driver fights the MCU. A read-only port was added to the block on 2026-09-17, shape output, so a capture can witness the isolation state on DIG27. It is a declaration, not a connection: the netlist still shows exactly six nodes on that net (R40006, GP9, and the four ~OE pins). Because the port is an output on both sides of the sheet boundary, ERC reports a pin conflict if anything on the board ever tries to drive it. The mcu sheet still needs the matching pin. See InstrumentPort-Digital-50 Pinout.

³ See The Encoder And Limit Switch Do Not Reach The MCU.

⁴ Was V5_SENSE on divided +5V_SW until 2026-08-16. See What The Rail Sense Is Actually For. The mcu sheet pin named SW_RAIL_SENSE carries the raw +3.3V_SW rail into the sheet; R408/R409 (1 kΩ/1 kΩ) divide it inside mcu, and the divided node on GP26 is SW_RAIL_SENSE_DIV_2.

⁵ Added 2026-08-17; designator settled 2026-09-16. GP14 drives the TPS1HTC100-Q1's DIAG_EN through a 1 kΩ series RPROT that lives inside the vertical HV input block as R502, so the board owes only the wiring. Earlier text called it R502; that number now belongs to a USB-C CC pull-down. The block's port is named DIAG_ENABLE, not HV_DIAG_EN — the HV_ prefix is the board's, applied at the root. See the note on DIAG_EN below.

⁶ Freed 2026-08-26 when the rev 1 analog output block was built. The DAC80501 has no hardware LDAC and no hardware CLR pin at all — synchronous update is the DAC_SYNC_EN bit plus the TRIGGER register's LDAC bit, and a soft reset writes 0x1010 to TRIGGER. Both functions survive; they cost zero pins instead of two. See docs/encoder-analog-output.md. No table row cites this footnote any more — GP12 and GP17 were re-spent in v1.0 — but the reasoning is why they were available.

⁷ New in v1.0, 2026-09-16. The analog output and brake blocks are both I2C now, not SPI, and they share one bus: two pins instead of the three the SPI DAC needed, and the brake block arrives for the cost of one fault input. The freed GP10/GP11 plus GP12 became the torque ADC's SPI1, and GP15/GP18 became its convert-start and calibration-enable. Do not carry the old DAC_SPI_* / ~{DAC_CS} names forward anywhere.

⁸ Recorded 2026-09-16, and this is a real gap, not a note. The pycontrol_behavior_port_rj45_boundary_vertical block exposes exactly two signal pins — PYC_DIO_A_HOSTTX_SCL_MOSI and PYC_DIO_B_HOSTRX_SDA_MISO — and both are spent on the UART through R301/R302. There is no third or fourth line, and the board's only BNC is J1101 (ANGLE_OUT), so PYC_PULL_EVENT, PYC_FAULT and PYC_RSYNC_IN have no physical destination on this board. They stay allocated so no pin moves, and the two outputs are still worth observing on the InstrumentPort, but PYC_RSYNC_IN has no source and gets no channel. Either add connectors in a later revision or retire the three. PYC_RSYNC_IN is now retired — decided 2026-09-18. Its GP23 was spent on ~{HV_FAULT}, which had to vacate an ADC pin. Of the three it was the right one to take: it is the only one with no source and no instrument channel, while PYC_PULL_EVENT and PYC_FAULT are outputs still worth observing. GP20 and GP22 remain reserved for those two.

⁹ Freed 2026-09-16, and immediately re-spent on HV_LATCH. The digital-50 DUT block is default-enabled with both ~OE inputs held low and no port, so ~{INSTR_BUF_OE} no longer exists. Its pull-up circuit and firmware init are deleted rather than ported. See the migration plan.

¹⁰ Added 2026-09-16, and this pin was owed since 2026-09-02. The TPS1HTC100-Q1's LATCH was strapped high through a 1 kΩ RPROT until the bench showed that latch-off and power-up are in direct conflict on this board: the switch trips relative thermal shutdown 1-2 ms into charging +36V_SW's 208 µF, so auto-retry is the only mechanism that can bring the rail up at all. docs/power.md → LATCH — Tie It High For Latch-Off recorded the fix as "LATCH on a spare GPIO" but no pin was ever allocated here; this is that allocation. Drive it low for the power-up ramp and high once the rail is established — that buys retry-based soft-start and latch-off, and the device's internal 0.7-1.3 MΩ pull-down makes auto-retry the default whenever the MCU is unpowered or in reset, which is exactly when the rail has to be able to charge. Do not strap it either way. The 1 kΩ RPROT is R503 inside the HV input block, so the board owes only the wiring.

¹¹ The ADC reshuffle, 2026-09-18. Only GP26-GP29 can reach the ADC, and ~{HV_FAULT} — a digital open-drain input — was occupying GP29/ADC3. It moved to GP23, spending the PYC_RSYNC_IN reservation (footnote 8) and freeing ADC3. GP27/ADC1 and GP29/ADC3 are now assigned to BRAKE_ISENSE and ANGLE_OUT_MON respectively, and both bypass the isolation switch — see The Rule. The ~{HV_FAULT} move is wired, and so are both monitors — verified at U601 pins 39 and 41. Levels, source impedances, the sheet pins still owed and the accuracy limits are in The Four ADC Channels, And The v1.0 Analog Readback.

v1.0: 29 connected (27 with a destination, 2 reserved per footnote 8), 0 spare, 1 internal (GP9). The MCU is now fully allocated — there is no spare GPIO left. Updated twice on 2026-09-18: the line read 25 with a destination, 3 reserved and 1 spare before ~{HV_FAULT} moved onto GP23 (retiring PYC_RSYNC_IN and freeing GP29), and the last two went to BRAKE_ISENSE and ANGLE_OUT_MON. If more pins are ever needed, retiring the remaining two footnote-8 signals is the cheapest reclaim available — they have no connector, so nothing on the board changes.

Switched: 15 channels — ISO2-ISO8, ISO10-ISO13, ISO15-ISO18. Bypass: 13 — GPIO0, GPIO1, GPIO14, GPIO19-GPIO26, GPIO28, GPIO29. The remaining 30 block pins get no-connect flags: the 15 raw pins whose index is switched, GPIO27, and the 14 unused ISO pins (ISO0, ISO1, ISO14, ISO19-ISO29).

Verified against the schematic 2026-09-16. Every switched signal sits on its index-matched ISO pin and every bypass signal on the matching raw GPIO pin, checked at pad level through the netlist — ~{TMC_CS} on ISO2 through TQ_CAL_EN on ISO18, and GPIO0/GPIO1/GPIO14/GPIO19/GPIO21/GPIO25/ GPIO26/GPIO28/GPIO29 on the raw side. Note that a raw GPIOn net always shows two nodes at the RP2040 end — the MCU pad and the switch's B pin — because the block wires all 29 channels 1:1 internally. That is the block working as designed, not a stray connection.

Four pins were freed on 2026-08-16 and the allocation went from comfortable to roomy. That matters for more than tidiness — see the note on rev 2 below. One of the four, GP14, was spent again on 2026-08-17.

Two more were freed on 2026-08-26, and unlike every earlier reclaim these cost nothing at all. GP12 and GP17 were not traded away or descoped — the part chosen for the analog output simply does not have the pins they were reserved for. That is the difference between this reclaim and the TMC_REF_STIM trade below, and it is why the rev 2 arithmetic changes sign.

DIAG_EN on the input block's switch is on GP14, through R502 10 kΩ. Reversed 2026-08-17; earlier guidance said tie it to +3.3V, not to a GPIO, because diagnostics have no reason to be gated and tying it costs no pin. That reasoning was right about diagnostics and wrong about the cost. Held high, the TPS1HTC100-Q1's OFF-state open-load detection keeps an internal ~150 kΩ pull-up from VS to VOUT permanently enabled (ROL_OFF 110-166 kΩ), injecting ~240 µA into +36V_SW. Against that rail's ~208 µF the voltage creeps at ~1.2 V/s and crosses the TPSM365R6's 3.6 V minimum VIN in about 3 s, so the buck starts, collapses, and hiccups — the switched domain never cleanly powers off. That breaks the property that HV_EN power-cycles every resettable chip, and it corrupts SW_RAIL_SENSE. DIAG_EN has an internal pull-down, so with GP14 high-Z at reset diagnostics are off and +36V_SW sits at true zero. Per the device's Table 7-1 the current limit and thermal shutdown are unaffected by DIAG_EN — only reporting is gated — so firmware must raise GP14 before reading HV_SNS or ~{HV_FAULT}. Without it neither works at all; see docs/power.md.

The v1.0 GPIO Changes

Eight pins changed and one left the sheet. Nothing else moved, so firmware written against Rev 0's bypass group, TMC bus and power-monitoring group keeps working unchanged.

Pin Rev 0 / rev 1 v1.0 Why
GP9 ~{BUS_SW_EN}, board-wired same signal, internal to the block, with a read-only monitor port from 2026-09-17 the combined MCU block owns the enable and its pull-up
GP10 DAC_SPI_SCK TQ_SPI_SCK DACs went I2C; SPI1 SCK exists nowhere else
GP11 DAC_SPI_COPI TQ_SPI_COPI same
GP12 spare TQ_SPI_CIPO SPI1 RX
GP13 ~{DAC_CS} ~{BRAKE_FAULT} I2C needs no chip select
GP15 spare TQ_CONVST_CS plain GPIO, timing-critical
GP16 spare DAC_SDA I2C0 adjacent pair
GP17 spare DAC_SCL same
GP18 spare TQ_CAL_EN plain GPIO
GP24 ~{INSTR_BUF_OE} HV_LATCH digital-50 has no enable; the bench-proven latch pin took the slot

Net naming: COPI/CIPO, never MOSI/MISO or SDI/SDO. Board net names and the comments describing them use COPI/CIPO throughout. SDI/SDO/SDIN appear in this document only where a device's own pin is meant — the TMC5130A's SDI/SDO pins, the DAC80501's SDIN, the block ports of torque_bridge_0_3v3_ina821 — and those are quoted as-is because renaming a datasheet pin would be worse than the inconsistency.

That makes one mapping worth stating explicitly, because crossing it is invisible to ERC and DRC: at the parent sheet, board net TQ_SPI_COPI wires to the torque block's SDI port and TQ_SPI_CIPO wires to its SDO port. Both nets stay driven and terminated either way round, exactly like the AOUT/AOUT_MON crossing that block family already warns about — verify it on the netlist, not by eye. The same holds at the TMC: TMC_SPI_COPI → SDI, TMC_SPI_CIPO → SDO, which the direction table further down already records.

Two of the three analog monitors now reach the MCU — revised 2026-09-18. ANGLE_OUT_MON and BRAKE_ISENSE are assigned to GP29/ADC3 and GP27/ADC1; see The Four ADC Channels. TQ_AOUT_MON still does not and does not need to — torque arrives digitally over SPI1, so digitising its analog monitor would add nothing, and there is no ADC channel left anyway. None of the three goes near the digital-50 connector: it has no AIN tail and the migration plan forbids putting slow analog into a CMOS port buffer, so instrument access to all three is still by test point.

What changed the calculus for the other two is that neither had any readback at all. DAC health over I2C STATUS tells you the DAC acknowledged a write, not that the amplifier downstream of it produced a voltage; BRAKE_ISENSE had only the digital ~{BRAKE_FAULT} bit. The ADC is far too coarse to measure either — see the accuracy note in that section — but it distinguishes a live chain from a dead one, which nothing else on the board did.

InstrumentPort-Digital-50 Pinout

This is the only instrument-port pinout for v1.0, and it is one table in DUT-side pin order covering every contact. The 40-pin overlay that used to follow was deleted on 2026-09-17; board-interfaces/docs/instrument-port-40.md and the git history hold it for boards already built.

Connector: J1301, Hirose FH12-50S-0.5SVA(54), OW-CONN-FH12-50S-0-5SVA-54 — 50 contacts, 0.5 mm pitch, vertical ZIF, 3.6 mm closed height, straight-ended 0.30 ±0.05 mm FFC. Footprint orb_weaver_connectors:Hirose_FH12-50S-0.5SVA_1x50-4MP_P0.5mm_Vertical.

The board never wires a connector pin. The design block instrument_port_digital_50_dut_ffc_boundary_vertical owns J1301, the four SN74LV541A buffers and their ~OE pull-downs. What the board wires is the block's ports: DIG0-DIG31, all thirty-two present and every one shape input, plus +3.3V and GND. Bind +3.3V to +3.3V_MCU, the always-on rail — a buffer on the switched rail cannot observe the domain it is powering down with. So the Strand column below is what you connect in the schematic, one row per sheet pin, and Pin and Bank are reference information for cable, probe and skew work — Bank names the SN74LV541A that carries the channel.

Read-only, and enabled by hardware with no way to disable it. Both ~OE inputs of each package are held low inside the block by R39001-R39004, there is no enable port, and nothing on this connector may be driven inward. Every unused input still has to be tied, because ~OE does not gate the input stage — see Board-Level Input Biases.

Cable orientation remains an open risk rather than a settled fact. The mapping is correct given a reversing cable assembly, and no straight-ended FFC has been bought or continuity-checked. Under a complete end-to-end reversal ground still meets ground and channel order reverses (DIGn to DIG31-n); that symmetry is a convenience, not evidence that a given cable is correct.

Tap column: MCU taps the always-on MCU net directly, ISO taps the ISO/A side of the bus switch so the capture shows what the peripheral actually receives, and block means a design block drives the net in its own domain. The reasoning is in Where Each Signal Is Tapped.

Strand Pin Bank Board net Tap State
DIG0 2 U1301 FW_TRIG0 MCU wired — hold on the upstream marker freeze
DIG1 3 U1301 FW_TRIG1 MCU wired — hold on the upstream marker freeze
DIG2 5 U1301 PYC_UART_TX MCU wired
DIG3 6 U1301 PYC_UART_RX MCU wired
DIG4 8 U1301 unused — tied to GND — PYC_PULL_EVENT retired
DIG5 9 U1301 unused — tied to GND — PYC_FAULT retired
DIG6 11 U1301 unused — tied to GND — spare
DIG7 12 U1301 unused — tied to GND — spare
DIG8 14 U1302 TMC_SPI_SCK ISO wired
DIG9 15 U1302 ~{TMC_CS} ISO wired
DIG10 17 U1302 TMC_SPI_COPI ISO wired
DIG11 18 U1302 TMC_SPI_CIPO ISO wired
DIG12 20 U1302 TMC_DIAG0 ISO wired
DIG13 21 U1302 TMC_DIAG1 ISO wired
DIG14 23 U1302 ~{TMC_DRV_EN} ISO wired
DIG15 24 U1302 LIMIT_SW block wired — debouncer output, never crosses the bus switch
DIG16 27 U1303 TQ_SPI_SCK ISO switch side wired; awaiting the torque block
DIG17 28 U1303 TQ_CONVST_CS ISO switch side wired; awaiting the torque block
DIG18 30 U1303 TQ_SPI_COPI ISO switch side wired; awaiting the torque block
DIG19 31 U1303 TQ_SPI_CIPO ISO switch side wired; awaiting the torque block
DIG20 33 U1303 TQ_CAL_EN ISO switch side wired; awaiting the torque block
DIG21 34 U1303 ~{BRAKE_FAULT} ISO switch side wired; awaiting the brake block
DIG22 36 U1303 DAC_SDA ISO wired
DIG23 37 U1303 DAC_SCL ISO wired
DIG24 39 U1304 HV_EN MCU wired
DIG25 40 U1304 HV_DIAG_EN MCU wired
DIG26 42 U1304 ~{HV_FAULT} MCU wired
DIG27 43 U1304 ~{BUS_SW_EN} MCU wired
DIG28 45 U1304 ENCA block wired — conditioned 3.3 V output
DIG29 46 U1304 ENCB block wired — conditioned 3.3 V output
DIG30 48 U1304 PG_5V block wired — power-good witness, hv_to_5v_tpsm365r6
DIG31 49 U1304 PG_12V block wired — power-good witness, hv_to_12v_tpsm365r6

Twenty-eight assigned, four tied to GND. DIG4 and DIG5 joined the spares when the three phantom pyControl signals were retired — footnote 8 offered that choice and the schematic took it. Every unused input is explicitly tied, which is what the block requires.

The eighteen grounds are deliberately not in this table, and they need no sheet pin. Pins 1, 4, 7, 10, 13, 16, 19, 22, 25, 26, 29, 32, 35, 38, 41, 44, 47 and 50 are wired to GND inside the design block; the board supplies ground once, through the block's single GND port, and the buffer supply once through its +3.3V port. One row here is one sheet pin to wire.

One thing is still owed from outside: the DIG0/DIG1 marker pair is not frozen in board-interfaces/docs/instrument-port-digital.md, so hold FW_TRIG0/FW_TRIG1 until it is.

Why The Channels Are Grouped The Way They Are

Regrouped 2026-09-18. Nine strands changed role; no signal moved between banks. The profile lays each bank out as GND, signal, signal, so channels arrive in pairs that share a gap with no ground between them — pins (14,15), (17,18), (20,21) and so on. Coupling inside a pair is much higher than across a ground, so what sits next to what is a real choice, and board-interfaces/docs/instrument-port-digital.md designed the pair unit for it: "Pairs suit UART TX/RX, I2C clock/data, encoder A/B, and beacon clock/data." That same paragraph is honest about the limit — "The two signals within a pair can still couple; nearby returns are not a guarantee of impedance, crosstalk, or bandwidth."

Four pairs broke the rule before this change and now do not:

Pins Was Now
14,15 TMC_SPI_SCK + its own TMC_SPI_COPI TMC_SPI_SCK + ~{TMC_CS}
17,18 TMC_SPI_CIPO + ~{TMC_CS} TMC_SPI_COPI + TMC_SPI_CIPO
27,28 TQ_SPI_SCK + its own TQ_SPI_COPI TQ_SPI_SCK + TQ_CONVST_CS
30,31 TQ_SPI_CIPO + TQ_CONVST_CS TQ_SPI_COPI + TQ_SPI_CIPO
33,34 TQ_CAL_EN + DAC_SDA TQ_CAL_EN + ~{BRAKE_FAULT}
36,37 DAC_SCL + ~{BRAKE_FAULT} DAC_SDA + DAC_SCL

A clock beside its own data was the one adjacency worth fixing. Not because it endangers the board — nothing on this connector drives anything, the port is read-only into a high-Z analyser — but because the coupled pulse arrives exactly at the instant that data line is sampled against that clock, so it corrupts the measurement you would be taking. COPI beside CIPO is the better pairing: both transition just after a clock edge, so a coupled glitch lands mid-window where the margin is widest. Pairing SCK with a static chip-select removes the correlation entirely.

DAC_SDA and DAC_SCL are together for a different reason, and not to reduce noise. I2C is the least noise-sensitive traffic here — open-drain, pulled up through 4.7 kΩ, RC-shaped edges at 100–400 kHz — so coupling between clock and data is negligible either way. Pairing them matters because it vacates the two slots the split occupied, which is what let DAC_SDA stop sitting beside TQ_CAL_EN and DAC_SCL beside ~{BRAKE_FAULT}, and because a two-wire bus that is always decoded as a unit should travel as a unit.

Magnitude, so this is not mistaken for a defect. Nothing here is measured. With ~5 ns SN74LV541A edges and ~1.5–2 ns one-way delay through ~300 mm of FFC, the line is electrically short, so near-end crosstalk never saturates and the coupled amplitude scales by roughly 2·T_prop/t_r ≈ 0.7. With a ground flanking every pair, the worst glitch is somewhere in the low hundreds of millivolts against an analyser threshold near 1.5 V. It will not flip a captured bit from idle. This is hygiene, done while the sheet pins were being wired and the PCB had no traces, not a fix for an observed problem.

The bank assignment was already right and did not change. Each subsystem sits entirely inside one SN74LV541A, so within-bus skew stays package-matched; every one of the nine changes stayed inside its own bank. U1303 carries three subsystems only because a five-channel bus cannot fill a bank alone.

The analyser imposes no constraint of its own. CH0-CH31 map one-to-one to DIG0-DIG31, and nothing in instrument-control binds a signal type to a channel index — capture takes channel indices as arguments and the beacon's --clock/--data are explicit overrides. The "preferred SPI SCK overlay"-style language in board-interfaces/docs/la5032-instrument-port-40-mapping.md belongs to the legacy 40-pin adapter; the digital-50 draft says outright that DIG names are "not fixed assignments to SPI, UART, or particular MCU pins." The only block-of-eight that matters is the buffer package on this board.

Name the witnesses PG_*, not after the rail — this is a netclass trap, not a style preference. This board's netclass patterns include /+5V_* → PWR_5V, a wildcard meant for /+5V_SW and /+5V_MCU. A net called +5V_SW_PGOOD matches it and is silently graded as a 5 V power rail: 0.25 mm clearance and a 0.5 mm minimum track width on a status line carrying ≤1 µA, in the most congested copper on the board. Renamed to PG_5V on 2026-09-18, which matches no pattern and lands in Default at 0.2 mm. PG_12V is clear for the same reason — every 12 V pattern is either exact (+12V, /+12V, 12V, /12V) or */-prefixed, and there is no /+12V_*. Note docs/power.md calls the rail +12V, not +12V_SW, so +12V_SW_PGOOD would have been wrong twice.

Levels, so neither witness is re-derived. SN74LV541A inputs are 0-5.5 V independent of V_CC and its thresholds are 0.7/0.3 x V_CC, so V_IH is 2.31 V at 3.3 V and 2.52 V at the top of the 3.0-3.6 V range. The 12 V block divides with R18002 10 kΩ / R18003 4.7 kΩ, giving 3.84 V nominal and 3.67-4.01 V worst case — 1.5 V below the input maximum and 1.15 V above the worst-case threshold, from a 3.2 kΩ source that is far inside the part's 100 ns/V transition limit. PG_5V is the tighter of the two: hv_to_5v pulls up through R13001 10 kΩ with no divider, so its high level is the 5 V rail itself, ~5.07 V worst case — 0.43 V from the 5.5 V maximum — at ~67 ns/V against that 100 ns/V limit. Both legal; suspect PG_5V first if a witness ever misreads.

DIG31 is still tied to GND, deliberately. It is the intended home for the 12 V power-good witness, and it stays a ground tie until hv_to_12v_tpsm365r6 is placed, because wiring an input sheet pin to a net with no driver would put an ERC error on the board until then. When that block lands, this is one edit: swap the GND label at (129.54, 139.7) in instrument_port.kicad_sch for a hierarchical label naming the 12 V power-good net, and add the matching sheet pin on the root. DIG30 is the worked example — and U1304 is the right bank for both, because the edges you are timing are timed against HV_EN, which is package-matched here.

No channel on this port needs a bias resistor of its own. The always-on taps are driven whenever the board is powered; the switched taps are defined by the resistors in Board-Level Input Biases, which is why the switched-side I2C pull-ups must reference +3.3V_SW; and LIMIT_SW, ENC_A and ENC_B are defined by pull-downs inside their own blocks.

Board-Level Input Biases

Read with docs/instrument-port-tap-resistors.md from 2026-09-19. Every bias below stays exactly where it is, on the functional side of the new tap resistors. Each tap then inherits its functional net's defined level through the resistor, so no _INST net needs a pull of its own — but the "every net reaching an SN74LV541A input has a DC-defining resistor" sweep has to be re-run across the split nets.

Renumbered and rehomed 2026-09-16. All eight TMC input biases now live in motor_encoder_switch, next to each other, because what they actually do is keep the TMC5130A's inputs defined while the bus switch is open — the instrument taps were always a secondary beneficiary. They were R1104-R1601 in instrument_port; that sheet now holds none of them. Note that R1104 is a reused designator: it previously named the REFR_DIR pull-down that was deleted in favour of a hard GND tie, so older text describing R1104 is about a different part.

Designator Net Termination
R1101 TMC_DIAG0 10 kΩ up to +3.3V_SW
R1102 TMC_DIAG1 10 kΩ up to +3.3V_SW
R1103 ~{TMC_DRV_EN} 10 kΩ up to +3.3V_SW
R1104 TMC_SPI_SCK 10 kΩ down
R1105 TMC_SPI_COPI 10 kΩ down
R1106 TMC_SPI_CIPO 10 kΩ down
R1107 ~{TMC_CS} 10 kΩ up to +3.3V_SW — load-bearing
R1601 LIMIT_SW 100 kΩ down — inside the limit-switch block, correctly: it defines that block's own output

Nine more are owed by the new blocks, and none of them may live inside a block: a twin-axis board placing the same block twice would halve every pull, which is the same argument that keeps SCL/SDA termination out of the DAC blocks.

Net Termination Why
DAC_SDA, DAC_SCL (MCU side) 4.7 kΩ up to +3.3V_MCU the bus switch is open by default, so the MCU side floats otherwise
DAC_SDA, DAC_SCL (ISO side) 4.7 kΩ up to +3.3V_SW a switched-domain pull never references the always-on rail
TQ_SPI_SCK, TQ_SPI_COPI, TQ_SPI_CIPO 10 kΩ down the block refuses to terminate; also defines the tap when the ADC is dark
TQ_CONVST_CS 10 kΩ up to +3.3V_SW must idle high — load-bearing, same as ~{DAC_CS} was
TQ_CAL_EN 10 kΩ down calibration must be off, not floating

All fitted as of 2026-09-18, and three more besides. As built, verified from the netlist:

Net Termination As built Sheet
DAC_SCL_BYP, DAC_SDA_BYP 4.7 kΩ up to +3.3V_MCU R402, R403 /mcu/
DAC_SCL, DAC_SDA 4.7 kΩ up to +3.3V_SW R406, R407 /mcu/
TQ_SPI_SCK, TQ_SPI_COPI 10 kΩ down R902, R903 /brake_torque_sensor/
TQ_SPI_CIPO 10 kΩ down R2012 torque block — correct, see the rule
TQ_CONVST_CS 10 kΩ up to +3.3V_SW R904 /brake_torque_sensor/
TQ_CAL_EN 10 kΩ down R905 /brake_torque_sensor/
~{BRAKE_FAULT} 10 kΩ down R901 /brake_torque_sensor/
FW_TRIG0, FW_TRIG1 10 kΩ down R404, R405 /mcu/

The last three are beyond the original nine. ~{BRAKE_FAULT} belongs to the same class and was simply missing from the list; the FW_TRIG pair closes the final floating-input window, the ~143 ms between reset and firmware driving the markers. Every net reaching an SN74LV541A input now has a DC-defining resistor — verified by sweeping all 28 of them.

Which sheet a pull belongs on

The rule above — "none of them may live inside a block" — reads as though it contradicts the statement further up that "LIMIT_SW, ENC_A and ENC_B are defined by pull-downs inside their own blocks." Both are right. The test is not which sheet, it is who drives the net:

A pull belongs inside a block when it defines that block's own output or an internal node. It belongs at board level when it biases a net the board drives, or shares between blocks.

The reason is placement-count, not electricity: a reusable block instantiated twice onto one shared bus parallels its internal pulls and halves them. A net the block alone owns cannot be doubled that way, so its pull is safe inside.

Board sheets are board level. mcu, motor_encoder_switch and brake_torque_sensor are board-owned wrapper sheets, not design blocks — they exist exactly once, so a termination on any of them satisfies the rule. Choosing between them is organisational, and grouping a pull with the subsystem it serves is preferred: it keeps select-by-sheet useful during placement, and these parts want to sit near the net they define rather than clustered at the MCU.

Every placement on this board follows the rule. Inside their blocks, correctly: R1505/R1506 (ENCA/ENCB), R1601 (LIMIT_SW), R1701 (PG_5V), R1902/R1903 (PG_12V) and R2012 (TQ_SPI_CIPO) all define their own block's output; R1301-R1304 on the buffer ~OE pins, R2011 on the ADC's ~{RST} and R606 on ~{BUS_SW_EN} are internal nodes — R606 included, because the RP2040 that drives it lives in that same block.

Checking clearance while it is ignored

rule_severities.clearance is ignore on this project, so DRC cannot report a clearance violation. make clearance-check restores it on a throwaway copy and diffs the result against kicad/clearance-baseline.json; the real .kicad_pro is never modified. make clearance-baseline re-records the baseline. Run the baseline before routing and the check after — it exists because Freerouting's characteristic failure is exactly the class of violation this board is blind to.

Baseline recorded 2026-09-18, pre-autoroute: 499 clearance findings, 156 unconnected items, of which 10 net pairs are voltage-bearing — including GND ↔ HV_IN at 0.2331 mm against 0.5 mm required, and +36V_SW ↔ PG_5V at 0.2919 mm against 0.4 mm. Those are placement, not routing, and an autorouter will not fix them.

The check gates only on what is reproducible, and that was measured rather than assumed. Three consecutive runs on identical copper gave 499 findings every time but attributed them across 149, 151 and 148 net pairs, with 10 of ~270 positions moving: KiCad's DRC picks a different counterpart item where nets crowd. Stable across runs, and therefore what fails the check: the total count, the voltage-bearing subset, the unconnected count, and the non-clearance violation tallies. The per-pair breakdown is printed for orientation only. --all-track-errors is passed for the same reason — without it KiCad reports one error per track and the choice varies.

Corrected 2026-09-18: TQ_CAL_EN and TQ_CONVST_CS were pulled from inside the torque block, which was wrong — both are MCU-driven inputs. They are now R905 and R904 on /brake_torque_sensor/. Removing them cascade-renumbered the whole 20xx band and shifted R901-R903; the electrical topology was confirmed unchanged by comparing every net-pair and value before and after.

4.7 kΩ, not the 2.2 kΩ the analog block's note suggests — that note is right for one segment and this board has two in parallel the moment the bus switch closes. Use OW-RES-4K7-0402, four of them; the catalog entry is already annotated for exactly this.

The arithmetic, with measured part capacitances rather than a guess. Switched segment: two DAC80501 at 10 pF max per pin (DACx0501 datasheet), the SN74LV541A tap at 2 pF, the SN74CB3Q3245 channel at 11 pF max Cio(ON), plus ~12 pF of trace — about 45 pF. MCU segment: an RP2040 pad and a short run, about 15 pF. Connected, call it 70 pF with margin.

Pull-ups per side Effective when connected t_r = 0.8473·R·C at 70 pF Sink to hold 0.4 V
2.2 kΩ 1.1 kΩ 65 ns 2.64 mA — 88% of the 3 mA floor
4.7 kΩ 2.35 kΩ 139 ns 1.23 mA
10 kΩ 5.0 kΩ 297 ns — at the limit 0.58 mA

Fast mode allows 300 ns of rise and the I²C-bus specification requires a device to sink 3 mA at V_OL ≤ 0.4 V; the DACx0501 datasheet specifies no V_OL at all, it only requires an external pull-up, so that 3 mA is the floor. 4.7 kΩ clears both with room at 400 kHz, which is the design target — the analog block's 987 Hz reconstruction filter caps the useful update rate near 2 kHz, so fast-mode-plus buys nothing here. If it were ever wanted, 3.3 kΩ per side gives 98 ns and 1.76 mA, at the cost of a new internal part.

A third reason to stay at 4.7 kΩ rather than going lower: if firmware ever closes the bus switch with +3.3V_SW down, the MCU-side pull-ups feed the dead DACs' input clamps. 4.7 kΩ bounds that to ~0.6 mA per line instead of handing it an unlimited source. It does not make the sequencing error safe — it makes it survivable.

Neither pair is optional. The MCU-side pair is what defines GPIO16/GPIO17 while the bus is open; the RP2040's internal 50-80 kΩ pull-ups are a firmware-dependent second layer, not a substitute. The switched-side pair must reference +3.3V_SW so both lines sit at a true 0 V when that rail is down — which is simultaneously what gives the always-on DIG21/DIG22 taps a defined low, and why those two channels need no pull-downs of their own.

Before adding a ~{BRAKE_FAULT} pull-up, check the brake block's netlist — R36001 or R36017 may already be it, and a second one in parallel halves it.

The Encoder And Limit Switch Do Not Reach The MCU

Decided 2026-08-16. ENC_A, ENC_B, and LIMIT_SW are the TMC5130A's business. The encoder goes from J1103 through the board's conditioning chain to ENCA/ENCB; the debounced limit switch goes to REFL. Neither reaches an RP2040 pin, in any revision. Firmware gets position from the driver's X_ENC/XACTUAL register and homing state from the driver's status, both over SPI.

There is no ENC_N net on the board. Earlier drafts of this section listed one alongside ENC_A/ENC_B; as built (2026-08-17) J1103 pin 2 is a reserved index no-connect and the TMC5130A's ENCN pin is terminated by R1201 10 kΩ to GND, which is what the datasheet asks for on an unused encoder input. The index is a rev-1 decision, not a Rev 0 signal.

This frees GP14, GP15, GP16, and GP18. GP14 was spent again on 2026-08-17 on HV_DIAG_EN.

What it deletes, permanently rather than deferring:

  • The RP2040 PIO encoder cross-check. It is not "rev 1" any more, it is gone.
  • ENCODER_MISMATCH in docs/uart-protocol.md. There is no second count to disagree with and there never will be, so the fault code should be retired rather than left reserved.
  • The GP14/GP15/GP16 contiguity constraint. It existed only to let one PIO state machine read A, B, and the index with a single input mapping. Those three pins are now ordinary spares with no adjacency requirement — which is precisely what made GP14 available for HV_DIAG_EN on 2026-08-17.
  • The MCU's independent copy of the limit switch. Firmware learns a homing event from the driver, one SPI poll later than the hardware REFL latch — which is the same latency the design already accepted, since REFL was always the hardware path and GP18 was always just a witness.

What replaces the witness: the InstrumentPort. ENC_A, ENC_B, and LIMIT_SW are all on it in Rev 0, which is now the only way to see those signals independently of the driver's own report. That is a stronger reason for them to be on the port than existed when the MCU also had copies.

Settled 2026-09-16 and it no longer "fits with room". What was rev-2 planning is now the v1.0 allocation above: brake and torque cost eight pins between them, not six — SDA, SCL, ~{BRAKE_FAULT}, three for SPI1, TQ_CONVST_CS and TQ_CAL_EN — and HV_LATCH took the last spare after that. One pin is left, GP27, and it is ADC-capable. The paragraphs below are the arithmetic that got us here and every one of their premises has since changed: the brake is I2C with no chip select, so ~{BRAKE_CS} does not exist; ~{BRAKE_FAULT} is not optional; the current monitor stays off the MCU entirely; and the torque bus needs five pins, not two. Read them as history.

Rev 2 fits on RP2040 with room, as of 2026-08-26. Full scope is 24 of 30 with six spares, and the brake plus torque front end for a single axis needs about six GPIOs — BRAKE_DIM, ~{BRAKE_FAULT}, a brake current monitor on ADC, two for the torque bus, and TQ_DRDY. That is six wanted against six spare even keeping TQ_DRDY, with GP27 ADC-capable for the current monitor. Drop TQ_DRDY, which the bus capture already implies, and there is a pin left over.

Got easier 2026-08-31. The settled brake driver needs one new GPIO, not two or three: ~{BRAKE_CS} for its DAC, sharing SCK and SDI with the angle DAC — both are write-only 3-wire devices, so bus sharing carries no contention risk. ~{BRAKE_FAULT} is optional and only exists if the SN74LVC2G17 fault buffer is populated; BRAKE_ENABLE is a stuffing option and largely redundant with HV_EN. So the brake side is 1 GPIO plus 1 ADC, against the 3 GPIOs plus 1 ADC this paragraph budgeted. That leaves more slack for the torque bus, which is where it is actually wanted.

This reversed twice. Until GP14 became HV_DIAG_EN on 2026-08-17 it was five for five with nothing to reclaim; that day it became five wanted against four spare, one pin short of free, and the fix was to spend reclaim-order item 1. On 2026-08-26 the analog output block landed on a part with no LDAC or CLR pin and returned GP12 and GP17 for nothing, which closes the gap outright rather than by trading a function away. Reclaim-order item 1 is now void — see below.

The twin-axis board needs RP2354B because it carries two of everything; this board may not. Do not treat that as settled — it is an observation to check when the rev 2 circuits are actually specified, not a commitment. It is a stronger observation than it was, but the rev 2 circuits are still unspecified.

What The Rail Sense Is Actually For

GP26 senses +3.3V_SW through a ÷2 divider as of 2026-08-16, replacing a divided +5V_SW sense. Same two resistors, better measurement — sensing the end of the chain confirms 36 V, 5 V, and 3.3 V in one reading, and +3.3V_SW is the rail that actually gates the decision, because it is what powers the far side of the bus switch.

Where the divider physically sits, as built 2026-08-17. The mcu sheet pin called SW_RAIL_SENSE carries the raw +3.3V_SW rail across the sheet boundary; R408/R409 (1 kΩ/1 kΩ) divide it inside mcu, and the ADC pin sees SW_RAIL_SENSE_DIV_2. Two consequences worth holding on to. The mcu sheet pin is therefore a rail tap, not a signal, so do not expect a 1.65 V node at the sheet boundary. And R408/R409 are also what holds +3.3V_SW at 0 V when the switched domain is off — a 2 kΩ bleed to GND — which is the property the board-level pull-ups to +3.3V_SW depend on. Removing the divider would leave ~{TMC_CS}, both DIAG lines, and ~{TMC_DRV_EN} undefined in the off state.

Why not delete it, given the TMC answers SPI when the rails are up? Because of a circularity. docs/power.md makes the rail sense one of three supporting measures for the isolation architecture: reconnect only after the rails are confirmed up. SPI reaches the TMC5130A through the bus switch you are deciding whether to enable, so it cannot confirm anything before that decision. Enabling the bus into an unpowered driver is precisely the back-power failure the whole architecture exists to prevent. The sense must bypass the bus switch, and GP26 does.

It is substantially redundant with HV_SNS, though, and that is worth knowing if the pin is ever wanted elsewhere. GP28 also bypasses the bus switch, and the buck, the LDO, and the driver draw quiescent current whenever the rail is really up, so a near-zero HV_SNS after raising HV_EN already means "the rail did not come up." Gating on HV_SNS plus a timeout would be defensible.

~{HV_FAULT} does not cover this, and it is worth being explicit about why: with no 36 V present the TPS1HTC100-Q1 is itself unpowered, so its open-drain fault output floats and the pull-up reads it as no fault. The most likely failure on the bench — nobody plugged the supply in — is invisible to ~{HV_FAULT} and obvious to either SW_RAIL_SENSE or HV_SNS.

Firmware rule: after raising HV_EN, confirm SW_RAIL_SENSE_DIV_2 reads a valid +3.3V_SW before deasserting ~{BUS_SW_EN}. Refuse ARM with a specific reason when it does not, and report the reading in STATUS.

Signal Direction And Sheet Pin Types

A hierarchical pin's direction is stated from the point of view of the sheet that owns it. Output means the signal leaves this sheet; Input means it enters. The same net is Output on the sheet that drives it and Input on every sheet that reads it. Nothing about the direction is global — TMC_SPI_SCK is an Output on mcu and an Input on the tmc5130 sheet, and both are correct.

DIO-A Is This Board's RX. DIO-B Is This Board's TX.

This is the one crossover on the board that reliably gets wired backwards, and the trap is that the alphabetical order reverses:

  RJ-45 pin 1   DIO-A   host TX  ──▶  PYC_DIO_A  ──▶  PYC_UART_RX  ──▶  GP1  (UART0 RX)
  RJ-45 pin 4   DIO-B   host RX  ◀──  PYC_DIO_B  ◀──  PYC_UART_TX  ◀──  GP0  (UART0 TX)

A → RX, B → TX. Both names are right; they are named from different ends. The pyControl block names its pins by connector position on purpose, so a board cannot mirror them by accident — that is exactly what the block's own description warns about. This board names its nets by MCU function. The crossover happens once, at the mcu sheet boundary, and nowhere else.

Sanity check that needs no documentation: PYC_UART_TX must be an Output on mcu. If a sheet pin named *_TX is an Input, it is wrong.

mcu Sheet Pins

Designator warning, reconciled 2026-09-18. The 4xx band shifted when the FW_TRIG pull-downs were inserted, and some of this drift predates that. The netlist is authoritative; this is the as-built map:

As built Value Function Docs used to call it
R401 10 kΩ PYC_UART_RX pull-up to +3.3V_MCU R403
R402, R403 4.7 kΩ MCU-side I2C pull-ups to +3.3V_MCU unchanged
R404, R405 10 kΩ FW_TRIG0/FW_TRIG1 pull-downs new
R406, R407 4.7 kΩ switched-side I2C pull-ups to +3.3V_SW R404/R405
R408, R409 1 kΩ SW_RAIL_SENSE ÷2 divider R401/R402

This file has been updated throughout. docs/power.md, docs/validation-plan.md, docs/design-block-map.md, docs/instrument-strategy.md and docs/rev0-plan.md still use the old names and need the same pass. docs/bringup-log.md must be left alone — it records measurements taken on boards where the old designators were real.

Everything crossing the mcu sheet boundary in Rev 0. The Far side column is what the mating block already declares, so a mismatch here is a wiring error.

mcu pin Type Mates with Far side declares
PYC_UART_TX Output pycontrol_behavior_port_rj45_boundary PYC_DIO_B passive
PYC_UART_RX Input pycontrol_behavior_port_rj45_boundary PYC_DIO_A passive
~{TMC_CS} Output tmc5130 ~{CS} input
~{TMC_DRV_EN} Output tmc5130 ~{DRV_EN} input
TMC_SPI_SCK Output tmc5130 SCK input
TMC_SPI_COPI Output tmc5130 SDI input
TMC_SPI_CIPO Input tmc5130 SDO output
TMC_DIAG0 Input tmc5130 SWN_DIAG0 bidirectional
TMC_DIAG1 Input tmc5130 SWP_DIAG1 bidirectional
HV_EN Output hv_input_36V_terminal_2pos_5p08_protected ENABLE input
HV_SNS Input hv_input_36V_terminal_2pos_5p08_protected SNS output
~{HV_FAULT} Input hv_input_36V_terminal_2pos_5p08_protected ~{FAULT} output
SW_RAIL_SENSE Input the raw +3.3V_SW rail; R408/R409 divide it inside mcu passive
FW_TRIG0 Output buffer U1801 — no test pad exists, see below input
FW_TRIG1 Output buffer U1801 — no test pad exists, see below input
~{INSTR_BUF_OE} Output ~{OE} on all three buffer instances input
USB_D+ Bidirectional usb2 USB_D+ bidirectional
USB_D- Bidirectional usb2 USB_D- bidirectional
+5V_MCU Passive the ORed USB / pyControl 5 V node passive
+3.3V_MCU Passive GP1 pull-up, buffers, and the input block's +3.3V_MCU passive
HV_DIAG_EN Output input block DIAG_ENABLE; GP14 through R502 10 kΩ input
GND Passive board ground passive
BRAKE_ISENSE Input brake_current_sink_0_49ma_dac80501 BRAKE_ISENSE; GP27 passive
ANGLE_OUT_MON Input analog_output_0_3v3_dac80501 ANGLE_OUT_MON; GP29 passive

Twenty-two pins in Rev 0 — ten Output, seven Input, two Bidirectional, three Passive. (Corrected 2026-08-17; the sentence used to say twenty pins and six Input against twenty-one rows, and HV_DIAG_EN was added the same day.) Verified directly against mouse-joystick-pcb.kicad_sch: the mcu sheet carries 22 pins, and the far side is named DIAG_ENABLE, not DIAG_EN, on hv_input_36V_terminal_2pos_5p08_protected. Note there is no LIMIT_SW and no encoder pin on this sheet; those signals never reach the MCU.

Reserved signals get no pin until their far end exists. Corrected 2026-08-17: they are not pre-wired inside mcu either. The earlier text said their A-side channels are wired and stop at the bus switch; as built the unused bus-switch channels have their A pins open and their B (MCU-side) pins tied to GND, so rev 1 has to edit the isolation block regardless — see The Block Is Oversized For This Board. When they arrive: DAC_SPI_SCK, DAC_SPI_COPI, ~{DAC_CS}, PYC_PULL_EVENT, and PYC_FAULT are Output; PYC_RSYNC_IN is Input. (Updated 2026-08-26: ~{DAC_LDAC} and ~{DAC_CLR} are gone from this list — the part has neither pin.)

Rev 1 also adds three pins that are not GPIOs and are easy to miss when counting: the analog output block needs +5V_SW and GND, and its AOUT leaves as an Output on whatever sheet holds it. AOUT does not cross the mcu boundary at all — it goes from the block to the BNC (J1101).

The Bus Switch Does Not Change Direction

Seven of the Rev 0 pins above — the sw group, GP2-GP8 — leave mcu from the A side of the isolation bus switch, whose A and B pins the block correctly declares passive, because a FET pass gate is passive silicon. That means the A-side net inside mcu has no driver of its own, and the sheet pin is what tells KiCad the net is driven. Give it the MCU's direction anyway: the switch passes the signal, it does not reverse it.

Corrected 2026-08-17. This paragraph used to say the pins leave from the B side. As built the orientation is the other way round — A is the TMC / switched side and B is the MCU side — so it is the A-side nets that cross the sheet boundary. Nothing about the direction argument changes; the part is a symmetric FET pass gate.

Do not reach for Tri-state to express the isolated state. It is closer to literally true, but KiCad has no shape meaning "sometimes disconnected", it adds no ERC value, and it would make the sw and byp groups look different for a reason that is about the switch rather than about the signal.

Every Net Wants Exactly One Driver

Output and Bidirectional drive; Input and Passive do not. Three families on this board were flagged as having no on-board driver at all, because the source is off-board or a passive network, and KiCad's "input not driven" check says so. Two of the three are settled as of 2026-08-17; ERC now reports zero violations at every severity.

Net Why it has no driver What to do
PYC_UART_RX Driven by the pyControl host through an all-passive block boundary Done 2026-08-17 — PYC_DIO_A/PYC_DIO_B are now bidirectional
ENC_A/ENC_B Driven on-board by U801's Schmitt outputs as of 2026-08-17 No longer applies. And there is no ENC_N net at all — see below
SW_RAIL_SENSE_DIV_2 A ÷2 divider off +3.3V_SW is a genuinely passive net Accept. HV_SNS is fine — the input block declares SNS as output

The decision was to change the source pin types rather than exclude the ERC item, and it is recorded here. Do not lower the rule's severity board-wide — it is the check that catches a *_TX pin wired as an Input.

kicad-libs change — done 2026-08-17. PYC_DIO_A and PYC_DIO_B on pycontrol_behavior_port_rj45_boundary are now bidirectional in both the library block and this board's placed copy. The reasoning is kept below because it is the precedent for the next connector boundary that needs it. The reasoning for leaving them undirected is right — those lines genuinely could go either way until a board assigns them — but bidirectional is the KiCad shape that means that, whereas passive means "no active device here", which is what a resistor or a shield tab is. It also matches house precedent: usb2 declares USB_D+/USB_D- bidirectional and instrument_port_40_dut_ffc_boundary declares all eighteen DIG*/TRIG* bidirectional. The pyControl block is the only connector boundary in the library that is all-passive. Two shape edits, no topology change, no PCB impact.

The Bus Switch Costs A Pin

Rev 0 adds the rp2040_gpio_isolation_sn74cb3q3245 block, whose shared active-low enable needs one GPIO. The allocation was at 29 of 30 with a single spare when this was decided on 2026-08-13, so the enable had to come from somewhere. The 2026-08-16 encoder and limit-switch cut has since freed four more pins, which makes the trade below look cheap in hindsight — it was not cheap at the time, and TMC_REF_STIM is not coming back regardless.

It is paid for by permanently dropping TMC_REF_STIM, the synthetic homing stimulus that used to sit on GP9. That was already item 2 in the reclaim order below, and it is the right one to spend: the limit switch now reaches REFL through a real debouncer on its own terminal block, so homing is exercised by closing the actual switch rather than by a GPIO impersonating one. Leave a test pad on the REFL net so a jumper can still force a closure — still outstanding as of 2026-08-17; no test pads exist anywhere on the board.

Net effect: the trade was one-for-one. Full scope is now 24 of 30 with six spares, and that headroom arrived by two later routes: four pins on 2026-08-16 from the encoder and limit-switch cut, and two more on 2026-08-26 from the analog output part.

Reclaim Order If More Pins Are Needed

Item 2 of the previous list has been spent on ~{BUS_SW_EN}. Item 1 became void on 2026-08-26. What remains:

  1. ~~Tie ~{DAC_CLR} high in hardware, freeing GP17.~~ Void. The DAC80501 has no CLR pin, so GP17 is already spare and there is no software clear to give up — the function moved into the TRIGGER register. GP12 came back the same way. This is the only entry ever removed from this list without being spent.
  2. Drop TMC_DIAG1, freeing GP8. DIAG0 alone, configured to report what matters. Route DIAG1 to a test point.

Neither is needed today, and the list is now one item long. Corrected 2026-09-18 against the exported netlist: there are four spares, not six, and they were GP20, GP22, GP27 and GP29 — and as of the same day GP27 and GP29 went to BRAKE_ISENSE and ANGLE_OUT_MON, so only the two reserved pyControl pins are left and there is no true spare. The sentence here used to name GP12, GP15, GP16, GP17, GP18 and GP27; v1.0 spent five of those — GP10-GP12 on the torque SPI1, GP15 on TQ_CONVST_CS, GP18 on TQ_CAL_EN, GP13 on ~{BRAKE_FAULT}, and GP16/GP17 on the shared DAC I2C pair. Spend a spare before spending a function, but count them from the netlist, not from this paragraph's history.

Reading spare-ness off the MCU-side net name does not work, and this is the trap. The isolation block names the MCU half of every channel GPIOn and the peripheral half ISOn, and a board assigns the peripheral half — so GPIO4 looks unassigned while its A side is TMC_SPI_CIPO. A channel is spare only when both halves still carry the generic names. Measured, channel by channel:

MCU-side net A (peripheral) side Status
GPIO2-GPIO8 ~{TMC_CS}, ~{TMC_DRV_EN}, TMC_SPI_CIPO, TMC_DIAG0, TMC_SPI_SCK, TMC_SPI_COPI, TMC_DIAG1 assigned
GPIO10-GPIO13, GPIO15, GPIO18 TQ_SPI_SCK, TQ_SPI_COPI, TQ_SPI_CIPO, ~{BRAKE_FAULT}, TQ_CONVST_CS, TQ_CAL_EN assigned
GPIO20, GPIO22, GPIO27, GPIO29 ISO20, ISO22, ISO27, ISO29 spare

The two that remain are electrically free but doctrinally reserved. GP20 and GP22 are held for PYC_PULL_EVENT and PYC_FAULT (footnote 8); the netlist cannot see a paper reservation, so do not treat a netlist sweep as the whole answer. Taking either one retires a pyControl signal, exactly as GP23 retired PYC_RSYNC_IN.

Notes:

  • GP23, GP24, and GP25 are free on the bare RP2040 QFN-56. They are reserved on Raspberry Pi Pico modules but not on a custom board. tmc51x0-driver-dev-pcb already uses GP23 this way, and since 2026-09-18 so does this board — GP23 carries ~{HV_FAULT}.
  • GP15/GP16 no longer need to be contiguous with GP14. That constraint existed only for a single PIO input mapping, and the PIO cross-check is gone. They are ordinary spares — and because the constraint went away, GP14 was free to take HV_DIAG_EN on 2026-08-17.
  • The encoder reaches the TMC5130A only, in every revision. It does not go straight from the connector to the driver, contrary to what this note used to say. As built (2026-08-17) the path is J1103 → R801/R802 100 kΩ harness-side pull-downs → R803/R804 4.7 kΩ series → C802/C803 470 pF → U801 SN74LVC2G17 dual Schmitt on +3.3V_SW → R805/R806 100 kΩ output pull-downs → ENC_A/ENC_B → ENCA/ENCB. See The Encoder And Limit Switch Do Not Reach The MCU and Where Each Signal Is Tapped.
  • The limit switch reaches the TMC5130A REFL pin through the debouncer, and goes nowhere else on the MCU. REFL is the hardware-latched homing path; firmware reads homing state from the driver over SPI.
  • GP19 drives the hv_input_36V_terminal_2pos_5p08_protected block's ENABLE input. That block pulls ENABLE low, so the 36 V rail is off until firmware drives it — and so the board's power-on state is the idle state, with the MCU up on USB or pyControl and everything switched dark. Firmware cycling GP19 is also the board's chip-reset mechanism; see docs/power.md.
  • GP28 reads the TPS1HTC100-Q1 SNS output. Done 2026-08-17: the block's R_SNS is R509 = 249 Ω — R509 before the vertical block was re-placed (OW-PASS-CRCW0402249RFKED, stocked), down from the 1 kΩ the block shipped with — the pin signals faults by sourcing 5.2-6.4 mA into R_SNS, which at 1 kΩ lands at 3.3-4.4 V, above the RP2040's VDD + 0.3 V limit. At 249 Ω the fault level is 1.29-1.59 V and current reads 0.311 V/A. No divider needed. The safe upper bound is about 594 Ω, so ~470 Ω is available if ADC resolution ever matters. See docs/power.md.
  • GP23 reads the switch's ~FAULT open-drain output — moved from GP29 on 2026-09-18 to free ADC3; see The Four ADC Channels below: overload, short-circuit, and thermal shutdown. Needs an external pull-up to +3.3V; fitted 2026-08-17 as R507 10 kΩ to +3.3V_MCU — R507 until the vertical block was re-placed. The design block leaves this pin unconnected; this board routes it because it has the pin and because the motor rail can shut itself off. Note it reads nothing useful until GP14 raises DIAG_EN.
  • GP26 senses +3.3V_SW through a divider — corrected 2026-08-17; this note still said +5V_SW, which the 2026-08-16 change had already superseded. That rail is dead whenever HV_EN is low or the 36 V supply is absent, which is the board's normal idle state, so this is the MCU's primary evidence that the switched domain is actually up. It gates step 3 of the sequencing rule above. +5V_SW is still required in Rev 0 — the TMC5130A's VCC minimum is 4.6 V — even though the analog output that also needs it is deferred to rev 1. Updated 2026-08-26: when it arrives, its OPA197 needs 4.5 V minimum, so +5V_SW acquires a second independent reason to be a real regulated rail rather than the ORed MCU one. See docs/power.md.
  • GP1 needs an external 10 kΩ pull-up to +3.3V_MCU; fitted 2026-08-17 as R401. See GP1 Gets A Hardware Pull-Up below. This is a board-level part on the MCU side of the port block's series resistor, not part of pycontrol_behavior_port_rj45_boundary.

The Four ADC Channels, And The v1.0 Analog Readback

Status: COMPLETE as of 2026-09-18. Verified from the exported netlist — ~{HV_FAULT} is on U601 pin 35 (GP23) through U605 B8; BRAKE_ISENSE is a 4-node net reaching U601 pin 39 (GP27/ADC1); ANGLE_OUT_MON is a 3-node net reaching U601 pin 41 (GP29/ADC3). Both monitors bypass the switch, with only the U606 B-side stub on the node, exactly as HV_SNS does.

Only GPIO26-GPIO29 can reach the ADC, so there are exactly four channels and no more. As built, one of them is spent on a digital signal:

Channel Assigned to Drawn? Signal Iso
GP26 / ADC0 SW_RAIL_SENSE_DIV_2 yes +3.3V_SW through R408/R409 byp
GP27 / ADC1 BRAKE_ISENSE yes brake current-sink readback byp
GP28 / ADC2 HV_SNS yes TPS1HTC100-Q1 SNS across R509 249 Ω byp
GP29 / ADC3 ANGLE_OUT_MON yes analog output readback byp

~{HV_FAULT} moved to GP23, pin 35 — done

~{HV_FAULT} is an open-drain digital input that was sitting on the last free ADC pin, so it had to move before ANGLE_OUT_MON could land. It is now on the isolation block's GPIO23 port instead of GPIO29; nothing else about the net changed. It spent the PYC_RSYNC_IN reservation — see footnote 8, which nominates that signal as the expendable one of the three. Why GP23:

  • Pin 35 is on the same side of the QFN-56 as pin 41 (pins 29-42 are one side), so the move does not cross the package. GP20 (pin 31) and GP22 (pin 34) also qualify; GP23 was the closest to where the signal had been.
  • It lands beside its own group. Pin 36 is GP24/HV_LATCH, with HV_EN at pin 30 and HV_SNS at pin 40.
  • GP23 is usable on a bare RP2040 — reserved only on Pico modules, and tmc51x0-driver-dev-pcb already uses it this way. See the note above.
  • Nothing is lost on the bus switch. It moves from U606 ch6, whose A side ISO29 is unconnected, to U605 ch8, whose A side ISO23 is likewise unconnected — an unused switched path either way.
  • The instrument port does not change. DIG26 carries ~{HV_FAULT} by net name, so the channel map and U1304 pin 7 are untouched.

The move freed GP29, and GP29 and GP27 are now assigned to the two monitors below. GP20 and GP22 stay reserved for PYC_PULL_EVENT and PYC_FAULT, and are the only pins left to take.

The two sheet pins these needed — done

The MCU lives inside the mcu sheet, so both monitor nets have to cross that boundary: mcu gains sheet pins ANGLE_OUT_MON and BRAKE_ISENSE, both input. On the other side, ANGLE_OUT_MON already reaches the motor_encoder_switch level and needs one more hop to the root, while BRAKE_ISENSE is still inside the brake block and needs a pin on the brake_torque_sensor wrapper. The ~{HV_FAULT} half needed no new sheet pin and is done. Do not tick "Re-link footprints to schematic symbols based on their reference designators" afterwards.

What the two monitor signals are

Net Span Source impedance Today
ANGLE_OUT_MON 0-1.650 V 2.6 kΩ — R807||R811 1.6 kΩ plus R810 1 kΩ wired to GP29/ADC3
BRAKE_ISENSE 0-1.006 V 1.849 kΩ — 1.6 kΩ divider plus R1819 249 Ω wired to GP27/ADC1

Both are low enough to drive the ADC directly with no buffer. Route them to the block's GPIO27 / GPIO29 ports; the switch's B-side pin then hangs off the node as an unused stub, which is exactly what HV_SNS already does on GP28. When the switched domain is off the nodes sit at 0 V, held by R805 100 kΩ and R1818 110 kΩ, and the series resistance limits anything regardless.

This is a self-test, not a measurement

12 bits over a 3.3 V range is 806 µV/LSB, so ANGLE_OUT_MON spans 2048 counts and BRAKE_ISENSE 1249. The binding limit is not resolution but the reference: docs/bringup-log.md records ADC_AVDD tied directly to +3.3V_MCU with no separate reference and no filter, "so absolute accuracy is bounded by that rail." Against the DAC's own 50.35 µV LSB at the BNC, the ADC is 16× coarser and rail-referenced, so this readback cannot validate the analog output's gain and offset — that still wants a DMM on the BNC. What it buys is a firmware-visible answer to "is the DAC-amp-BNC chain alive and roughly right", which today is unanswerable: the bring-up log notes ANGLE_OUT reaches no ADC at all. ADC linearity on this part is uncharacterised on this board; only low-level readings have ever been taken.

BRAKE_ISENSE is the more valuable of the two and the cheaper. The brake is a current sink whose actual current is invisible to firmware except through the digital ~{BRAKE_FAULT} bit, so reading it closes command-versus-actual, and the net has no other consumer to perturb. ANGLE_OUT_MON has a cost worth naming: R810 1 kΩ does not isolate much, so ADC input leakage flows through 1 kΩ + 1.6 kΩ onto the divider midpoint — the same node the BNC output comes from. Order a millivolt of error on ANGLE_OUT per microamp of leakage, roughly 0.1 % of full scale. That is precisely what the "≥1 MΩ load" rule in the analog design docs exists to protect, so pull the RP2040's input-leakage figure before assuming it is negligible. If only one channel is wired, wire BRAKE_ISENSE.

GP1 Gets A Hardware Pull-Up

Decided 2026-08-14; fitted 2026-08-17 as R401. A 10 kΩ 0402 (OW-RES-10K-0402, stocked) from PYC_UART_RX to +3.3V_MCU, on the MCU side of the port block's R29001, placed near the RP2040, in the board-authored mcu sheet so a design-block re-place cannot lose it.

GP1 is the pyControl UART RX. It is driven by the host's DIO-A, so it floats whenever the cable is unplugged, whenever pyControl is powered down with the cable connected, and — the case that matters — between this board's power-on-reset and the instruction where firmware configures the pad. A UART RX line that drifts low reads as a start bit or a break, so the RX FIFO fills with framing errors before the first line of protocol code runs.

An internal pull does not cover that window. This is the same argument that keeps the external 10 kΩ pulldown on the 36 V block's EN — see docs/power.md — and the same one behind .agents/AGENTS.md's rule that the board must reach a safe state without firmware's cooperation. A defined idle level on the link carrying the safety heartbeat belongs in that category.

The floating level is genuinely indeterminate rather than merely "probably high": the USBLC6-2P6 on that line leaks 150 nA max, against the RP2040's own ~1 µA input leakage.

Choice Why
10 kΩ 330 µA load when the host drives low, trivial for an STM32 GPIO rated 8-20 mA. Wins against a ~50 kΩ internal pull-down if one is active
+3.3V_MCU, not +3.3V_SW The pyControl UART bypasses the bus switch precisely so it works with the switched domain dark. Pulling to the switched rail would defeat that
MCU side of R29001 The point is to define the RP2040's input, so it must hold when the connector path is open — which is the unplugged-cable case. On the connector side it would only load the cable
Board level, not in the block The block's boundary has no 3.3 V pin, and which 3.3 V domain is a board decision. Keep the block's interface at five pins

Keep the firmware pull enabled as well. They parallel to ~8.3 kΩ and cost nothing.

GP0 (TX) deliberately gets none. A matching pull-up would keep the host seeing idle-high while this board is in reset or BOOTSEL, but it fails differently: with +3.3V_MCU dead and the cable connected, 10 kΩ to a 0 V rail holds the host's RX low, a continuous break, which is arguably worse than floating. That needs both USB absent and the pyControl 5 V branch open, so it is a corner — but fit TX only if host-side framing noise actually shows up at Gate B.

Resolved 2026-08-17, and it made this recommendation mandatory rather than prudent. docs/power.md used to state that RP2040 GPIOs are "inputs with pulls disabled at reset." That is wrong: every RP2040 GPIO resets with its internal pull-down enabled — PUE = 0, PDE = 1 in PADS_BANK0. So the right-hand column below is the one that applies, and docs/power.md needs the correction. The same fact is why the bus switch's MCU (B) side needs no termination of its own, and why GP1 is the one pin that needs a hardware pull the other way.

If pulls are disabled at reset If a pull-down is enabled at reset — this one
The external pull-up is belt-and-braces The external pull-up is required — GP1 is otherwise actively held low until firmware runs

10 kΩ is the right value either way: against a ~50 kΩ internal pull-down it gives 3.3 × 50/60 ≈ 2.75 V, comfortably over a 2.31 V VIH. The same claim is load-bearing in docs/power.md for the EN divider, where it is harmless — the external pulldown pulls the same direction — but the document should still say the right thing.

EXT_FAULT_IN — Still Deferred, Still Cheap

If the rig runs two of these boards, one board's fault should be able to stop the other without depending on the pyControl task to notice. Reserving GP27 as EXT_FAULT_IN and bringing it out — cross-wired to the other board's FAULT output, or to a rig-level disable line — costs one GPIO and one connector position, and reports as fault bit 8 EXT_STOP.

Rev 0 has no FAULT output to cross-wire, so this is a rev 1 decision that arrives with the pyControl event lines. GP27 stays unallocated until then. See the cross-board fault discussion in docs/pycontrol-interface.md.

Peripheral Summary

Peripheral Use Pins Rev 0
UART0 pyControl link, 115200 8N1 GP0, GP1 yes
SPI0 TMC5130A GP6 SCK, GP7 TX, GP4 RX, GP2 CS as GPIO yes
SPI1 DAC80501, write-only GP10 SCK, GP11 TX, GP13 CS as GPIO rev 1
PIO0 Unallocated — the encoder cross-check was deleted — —
ADC SW_RAIL_SENSE_DIV_2, HV_SNS, BRAKE_ISENSE, ANGLE_OUT_MON¹¹ GP26-GP29 yes
USB Debug console, CDC dedicated USB pins yes
QSPI External flash dedicated QSPI pins yes

There is no second UART. The debug console is USB CDC, because UART0 belongs to pyControl. Rev 0's other rich observation path is the InstrumentPort, which carries the TMC SPI bus and both directions of the pyControl link; the event BNCs are still rev 1.

GPIO Isolation

v1.0 places orb_weaver_private:rp2040_with_gpio_isolation_sn74cb3q3245, which contains the RP2040 core and the four SN74CB3Q3245 8-channel bidirectional FET bus switches with all 29 MCU-to-switch routes pre-made — 32 channels, 29 wired 1:1 by index, one shared active-low ~OE pulled up by 7.87 kΩ to the always-on +3.3V so the bus is isolated at power-on until firmware enables it. Rev 0's separate rp2040 + rp2040_gpio_isolation_sn74cb3q3245 pair is superseded; the standalone companion is expected to be retired from kicad-libs once this board is verified.

Corrected 2026-08-17: there is no Ioff second line of defence. All four VCC pins are on +3.3V_MCU, the always-on rail, so the packages are never unpowered and Ioff never engages. Isolation rests entirely on the MCU holding ~{BUS_SW_EN} high, which R601 makes the reset default. That is the deliberate consequence of The Bus Switch Runs From +3.3V_MCU below — it is the price of being able to isolate before switching off — but it means the enable is the only mechanism, not the outer of two.

Orientation, as built 2026-08-17: A is the TMC / switched side and B is the MCU side. This is inverted from what this document said before, and it matters in two places — which side the sheet pins leave from, and which side the instrument taps sit on. Channel pairing on the RGY-20 package is A_n = pin (n+1), B_n = pin (19−n).

The Rule: Follow The Far End's Power Domain

This heading is kept for link stability — kicad-libs links to its anchor — but the rule it names was restated on 2026-09-18 and the text below supersedes it. Following the far end's power domain is the right instinct for anything the MCU drives, and wrong for a high-impedance analog sense node; see the restatement after the table.

The TPS1HTC100-Q1 high-side switch exists so the researcher can power-cycle the board's chips to a known state without unplugging anything, while the MCU stays alive on USB or pyControl. See docs/power.md. That makes "MCU running, switched rails at 0 V" the board's normal idle state, not an edge case — and an MCU pin driving into an unpowered chip forward-biases its input ESD clamps and back-powers a dead rail through them.

The bus switch exists to prevent exactly that, which gives a single mechanical rule:

A GPIO goes through the bus switch if and only if its far end sits on the switched domain (+36V_SW, +5V_SW, +3.3V_SW). Everything else bypasses.

Iso Applies to Because
sw TMC5130A SPI, enable and diagnostics; the I2C DAC bus; ~{BRAKE_FAULT}; the torque ADC's SPI1, TQ_CONVST_CS and TQ_CAL_EN All of them are parts that lose power when HV_EN drops
byp pyControl UART; the reserved pyControl event lines; HV_EN, HV_DIAG_EN, HV_SNS, ~{HV_FAULT}; SW_RAIL_SENSE; FW_TRIG0/FW_TRIG1; BRAKE_ISENSE, ANGLE_OUT_MON No current path into an unpowered far end — see below

The byp test is not "is the far end always powered" — restated 2026-09-18. That wording held until the two analog monitors arrived, and it would have put them on the switch: ANGLE_OUT_MON and BRAKE_ISENSE are buffered by U802 and U1804/U1806, all of which run from block-local +5V_FILT off the switched +5V_SW, so their far ends do die with HV_EN. The real test is whether the MCU side can push or pull current into a dead far end. Switch a channel when it can: every MCU output, and every MCU input that might carry a pull-up — which is why the I2C pair (pulled up to +3.3V_MCU on the MCU side) and ~{BRAKE_FAULT} (a push-pull 2G17 output) are both sw. Bypass a channel when the MCU side is high-impedance and the far-end node is resistively tied to ground, because then there is no path and no hazard. Both monitors qualify: the ADC pin sources nothing, and R805 100 kΩ and R1818 110 kΩ hold their nodes at 0 V whenever the domain is down.

Bypassing them is also the only way they stay useful. Behind the switch they would be unreadable exactly when the interesting question is asked — is the brake current zero because I commanded zero, or because the rail never came up? — and worse, a disabled CB3Q channel leaves the MCU pin floating, so the ADC would return an arbitrary value rather than a true zero. HV_SNS is the precedent: an analog sense node read by the same ADC, on byp.

v1.0 isolates 15 of the 29 available channels, up from 7 in Rev 0: seven TMC, two I2C, one brake fault, five torque. ~{BUS_SW_EN} left the byp row because it is internal to the block now, and ~{INSTR_BUF_OE} left it because the signal no longer exists. The arithmetic in the rest of this section is Rev 0 history, superseded by that sentence and by The v1.0 GPIO Changes.

Rev 0 isolated 7 channels; full scope isolated 10. Both dropped by four on 2026-08-16, when the encoder and limit switch stopped reaching the MCU — those were sw entries, which is why the sw row above no longer names the debouncer copy or the encoder PIO tap. Full scope dropped by two more on 2026-08-26, when the analog output turned out to need three channels rather than five: the DAC80501 has no LDAC and no CLR pin. The as-built netlist confirms the 7: six channels on U601 and one on U602. The bypass group is 14 at full scope — 13 until HV_DIAG_EN arrived on 2026-08-17 — which is larger than a first pass suggests, and each entry earns it:

Net Why it bypasses
PYC_UART_TX, PYC_UART_RX The whole point of the architecture. pyControl is externally powered and must stay reachable when the switched domain is off — that is how the researcher talks to a board that is deliberately powered down
~{BUS_SW_EN} Circular otherwise — the enable cannot be behind the switch it enables
HV_EN The TPS1HTC100-Q1 runs from HV_IN and is never switched off. Rail control must not depend on bus state, and the MCU must be able to switch the rail back on while everything downstream is dark
HV_SNS, ~{HV_FAULT}, HV_DIAG_EN Same device, same always-on supply, and their pull-ups sit on +3.3V_MCU. Fault status must be readable when the rail is off, and DIAG_EN has to be raisable to get it. Direct MCU connections, all three
SW_RAIL_SENSE It reports whether +3.3V_SW came up, which is the precondition for enabling the bus. Reading it through the bus would be circular
FW_TRIG0, FW_TRIG1 No chip at the far end — only the InstrumentPort buffers — and they must stay observable while the bus is isolated. The test pads do not exist yet
PYC_PULL_EVENT, PYC_FAULT, PYC_RSYNC_IN (rev 1) BNC connectors and pyControl, both outside the switched domain
~{INSTR_BUF_OE} The SN74LVC125A buffers sit on +3.3V_MCU so the board can be observed while the switched domain is off

tmc51x0-driver-dev-pcb bypasses the same kind of set — its netlist shows ~{BUS_SW_EN} on GPIO19, ~{INSTR_BUF_OE} on GPIO23, and HV_EN and both FW_TRIG markers wired straight to the MCU, with 27 GPIO nets through the switch. This board bypasses considerably more, because it has a genuinely two-domain power tree and that board does not. Do not "align" the two.

This board puts ~{BUS_SW_EN} on GP9, not GP19. The proven board uses GP19 because it has no 36 V enable there; here GP19 is HV_EN and is already wired into mcu.kicad_sch. Deliberate divergence, noted so nobody "fixes" it.

The Block Is Oversized For This Board — Closed 2026-09-16

This whole question is closed and the section below is history. The combined rp2040_with_gpio_isolation_sn74cb3q3245 block wires all 29 usable GPIOs to a channel 1:1, so there are no un-wired channels left to argue about and no block edit is owed at any future revision — which was the one cost that killed option A and then option B in turn. The four packages stay, and the price is now stated honestly as current rather than as wasted area: I_CC is 1 mA typ / 2 mA max per package with the switch on or off, because the charge pump runs regardless, so the four draw ~4 mA typ and ~8 mA max continuously from the always-on rail even with every channel isolated. Accepted.

Rev 0 History — Option A Versus Option B

The block is four packages because it is built to isolate all 30 GPIOs. Under the rule above this board isolates 7 in Rev 0 and 10 at full scope, so two of the four SN74CB3Q3245 packages are dead weight in Rev 0 and at full scope too. The 2026-08-16 encoder and limit-switch cut made this worse, not better, and the 2026-08-26 analog output part made it worse again — ten channels fit in two packages with six to spare, so U603 and U604 now have no path to ever carrying a signal on this board. The decision to place the proven block whole still stands on schedule grounds, but it is carrying twice the packages it needs and the second half is no longer "reserved for later".

As built 2026-08-17, the seven live channels land on U601 (six: ~{TMC_CS} A3, ~{TMC_DRV_EN} A4, TMC_SPI_CIPO A5, TMC_DIAG0 A6, TMC_SPI_SCK A7, TMC_SPI_COPI A8) and U602 (one: TMC_DIAG1, A1/B1). U603 and U604 carry no channels at all and are an open decision before placement. The earlier crossed-label defect that put TMC_DIAG1 on a hard ground is fixed; all 32 channels were audited.

Option Cost Benefit
A. Place the block whole, wire 16 channels, leave 16 unused 3 unnecessary ICs, their decoupling, and the board area Proven block, zero design work, obvious upgrade path
B. Board-local section with two SN74CB3Q3245 Two ICs, a new schematic section to verify Half the parts and area; matches what the board actually needs

Decided 2026-08-13: option A. Place the proven block whole and leave the unused channels alone — 25 of its 32 as built, not the 16 this option's row predicted, because the encoder and limit-switch cut landed after the decision. Two SN74CB3Q3245 packages and their decoupling are wasted, and that is accepted — the block is already placed, already proven, and Rev 0 is optimising for schedule. Option B remains the right answer for a later revision if board area becomes the binding constraint.

Reversed 2026-08-17. Earlier guidance said to wire the A (MCU) side of all 16 isolated GPIOs now, including the 8 reserved for rev 1, leave the B side of the reserved channels unconnected, and let a later revision add board-side wiring only — no re-place, no re-pin, no firmware change. Neither half of that is what the board does. A is the switched side, not the MCU side, and the unused channels are not pre-wired at all: their A pins are open and their B (MCU-side) pins are tied to GND. Tying the MCU side down is the right treatment for an unused FET pass gate — it leaves no floating node on the always-on rail — but it means rev 1 has to edit this block to reach the DAC channels, so the "adds them without moving anything" property was never delivered. Plan for a block edit at rev 1, and prefer option B if that edit is happening anyway. Bypass-group signals stay off the switch entirely, which is unchanged.

Updated 2026-08-26 — option B is now the clear answer. The rev 1 edit needs only three channels (DAC_SPI_SCK, DAC_SPI_COPI, ~{DAC_CS}), which fit in U602's seven free channels without touching U603 or U604. Since the block has to be edited anyway, and since the full-scope requirement is now ten channels against thirty-two placed, the schedule argument that carried option A has expired. Do the swap when rev 1 opens the block, not before — Rev 0 is already ERC-clean with option A placed.

The DAC Bus Terminations — Superseded 2026-09-16

The SPI DAC bus does not exist any more. Both DAC blocks are I2C at 0x48 and 0x49, so ~{DAC_CS}, DAC_SPI_SCK and DAC_SPI_COPI are gone along with the three in-block terminations described below, and the analog block's own note that it owns no bus terminations now applies to SDA/SCL as well. What replaces them is two board-level pull-up pairs — one per bus segment, MCU side on the always-on rail and DAC side on +3.3V_SW — listed in Board-Level Input Biases. The reasoning below is retained because the rule it establishes is unchanged: a switched-domain pull must never reference +3.3V_MCU, and nothing may be pulled to 5 V through a symmetric FET pass gate.

Rev 1 History — The Three SPI DAC Terminations

Corrected 2026-08-27. This section previously said one was in the block and two were board-level. All three now live in the block, and the board owns none of them:

Net Termination Where Required?
~{DAC_CS} 10 kΩ pull-up +3.3V_SW In block (R2006) Yes
DAC_SPI_SCK 10 kΩ pull-down to GND In block (R2010) Hygiene
DAC_SPI_COPI 10 kΩ pull-down to GND In block (R2009) Hygiene

So the board's nine 10 kΩ terminations stay nine, not eleven. The two pull-downs moved into the block on 2026-08-27 because nothing about them is board-dependent — 10 kΩ to GND is right on any board — and a block re-place cannot then lose them.

Do not add board-level copies when the DIG9/DIG10/DIG6 instrument taps go in. Those taps sit on the A / switched side of the bus switch and need defined levels for exactly the reason R1104-R1107 exist on the TMC taps, but these three resistors already provide them. A second set in parallel would halve them.

Only the pull-up is load-bearing: the DAC80501 blocks SCLK and SDIN entirely while SYNC is high, and a frame interrupted before its 24th SCLK falling edge is discarded with no register update. The other two prevent floating CMOS inputs, not a functional failure. Because the required one is functional rather than board-dependent, hosting it in the block means no board can omit it — the trade is a boundary pin.

The +3.3V pin is that trade, and it must bind to +3.3V_SW. Binding it to +3.3V_MCU would hold ~{DAC_CS} at 3.3 V while the DAC's own VDD is dead, pushing roughly 260 µA through the DAC's input clamp into +5V_SW — the exact back-power mechanism the bus switch exists to prevent, reintroduced inside a block where the bus switch cannot see it. This is the same rule the board already applies to the buffer terminations: the switched-domain nets deliberately do not reference +3.3V_MCU.

Do not pull any of the three to the DAC's 5 V rail. The SN74CB3Q3245 is a symmetric FET pass gate, so a 5 V pull-up on the switched side lands on an RP2040 pad and exceeds its IOVDD + 0.3 V limit. +3.3V_SW is correct and sufficient — the DAC80501's VIH is 1.62 V, so even against the RP2040's internal pull-down the ~2.75 V divider result has margin, the same arithmetic that justifies R401 on GP1.

The Bus Switch Runs From +3.3V_MCU

It has to. Powering it from +3.3V_SW would give free isolation through Ioff whenever HV_EN is low and would cost no GPIO at all — but it would hand the sequencing to a rail ramp instead of to firmware, and it would remove the ability to isolate before switching off. Rejected deliberately; recorded in docs/power.md so it is not rediscovered as a clever idea.

Sequencing Rule

Falls straight out of the architecture, and firmware must follow it:

Powering the switched domain down

  1. Assert ~{BUS_SW_EN} high — isolate first.
  2. Drive HV_EN low.

Powering it back up

  1. Drive HV_EN high.
  2. Wait for the buck and LDO to settle.
  3. Confirm +3.3V_SW is up on SW_RAIL_SENSE_DIV_2 — readable because it bypasses.
  4. Assert ~{BUS_SW_EN} low — connect last.

Isolating first and connecting last means no MCU pin is ever driving a rail that is on its way down or not yet up. Getting this backwards is the failure this whole arrangement exists to prevent, so test it at Gate B with a scope on +3.3V_SW and one TMC input.

Firmware Consequences

Properties of the hardware, not choices:

  • The pyControl link works with the bus isolated. GP0/GP1 bypass, so the board answers ID/STATUS in its idle state and can report why the motor rail is off. This is the behaviour the architecture is for.
  • The USB CDC console is likewise unaffected — USB uses dedicated pins, not the GPIO matrix.
  • The power-monitoring group is readable at all times. SW_RAIL_SENSE_DIV_2, HV_SNS, and ~{HV_FAULT} all bypass, so firmware can diagnose the switched domain without enabling anything. The earlier boot-order awkwardness is gone. HV_SNS and ~{HV_FAULT} need GP14 (HV_DIAG_EN) raised first — the smart switch gates reporting on DIAG_EN, and GP14 is high-Z at reset.
  • Nothing on the switched domain is reachable until the bus is enabled. SPI to the driver, and in rev 1 the DAC. Firmware must treat a read from either as invalid while ~{BUS_SW_EN} is high rather than returning stale or floating data.
  • Position and homing come only from the driver. The encoder and the limit switch never reach a GPIO, so while the bus is isolated firmware has no position and no homing state at all — not stale data, none. Report that honestly in STATUS rather than caching the last value.
  • GP1 has a hardware pull-up, and firmware should still enable its own. The external 10 kΩ defines RX before firmware runs; the internal pull is the second layer. Do not treat the hardware part as permission to skip it. See GP1 Gets A Hardware Pull-Up.
  • Report both ~{BUS_SW_EN} and HV_EN state in ID/STATUS. A session that silently runs isolated should be impossible to mistake for a working one.

Connector Complement

Designators are assigned for the full scope and Rev 0 populates a subset.

Renumbered 2026-08-17 to the as-built annotation. The flat J1-J11 sequence this section used is not what the board carries; annotation is sheet-scoped, so the 36 V terminal is J201 and USB-C is J301.

Renumbered again 2026-08-27, and the no-renumbering policy is withdrawn. This paragraph used to say that gaps within each sheet's hundred were deliberate and that "renumbering later would break the silkscreen, the documentation, and the bring-up log for no benefit." A global re-annotation then happened by choice, when the analog output block gained its filter and the decision was taken to use KiCad's automatic annotation rather than hand-preserving numbers. Automatic annotation orders by sheet position, so inserting parts rotates everything after them.

Three connectors in the motor_encoder_switch sheet rotated:

Carries Was Now
Analog angle out, BNC J7 (placeholder) J1101
Stepper coils, 4-pos 3.5 mm J1101 J1102
Encoder, 5-pos 3.5 mm J1102 J1103
Limit switch, 2-pos 3.5 mm J1301 J1301 (unchanged)

The 4-, 5- and 2-position housings are not mechanically interchangeable, so no harness can be plugged into the wrong terminal. The hazard is documentary, not electrical.

Rev-0 boards in hand carry the old silkscreen. A technician holding a rev-0 board reads J1101 = motor while this document says J1101 = BNC. docs/bringup-log.md keeps one section set per board serial; record the designator scheme in each board's section rather than assuming the current table applies to older units.

The consequence for this document, and every other one: designator numbers are no longer stable across revisions. Name things by role and net — "the encoder terminal", "the ~{DAC_CS} pull-up" — and give the designator as a current-as-of-date convenience rather than as the identifier. Tables like the one below are the right place for designators; prose is not.

Designator warning, reconciled 2026-09-20. The whole J band was reassigned when the v1.0 board was rebuilt from design blocks; the numbers below are read from the exported netlist and supersede every earlier J number in this file. The netlist is authoritative. Same caution as the mcu sheet band above.

As built Connector Carries Rev 0 Docs used to call it
J201 USB-C receptacle (usb2_vertical block) Firmware load, USB CDC console, logic-rail power source yes J301
J301 RJ-45, OW-CONN-J1B1211CCD or OW-CONN-438600001 pyControl port: UART + GND only in Rev 0 yes J1901
J501 2-pos 5.08 mm pluggable terminal, Phoenix 1757242 (OW-CONN-1757242) 36 V motor supply in yes J201
J901 Single BNC, OW-CONN-HL-BNC-KHDA (vertical) TQ_AOUT — torque analog out yes new in v1.0
J1101 Single BNC, OW-CONN-HL-BNC-KHDA (vertical) ANGLE_OUT rev 1 unchanged
J1102 4-pos 3.5 mm terminal, OW-CONN-TBH-1X04-3P5-VERT Stepper coils yes unchanged
J1103 5-pos 3.5 mm terminal, OW-CONN-TBH-1X05-3P5-VERT Encoder: supply, return, A, B; pin 2 reserved yes unchanged
J1301 50-pin 0.5 mm FFC vertical ZIF, OW-CONN-FH12-50S-0-5SVA-54 InstrumentPort-Digital-50 yes J1501
J1601 2-pos 3.5 mm terminal, OW-CONN-TBH-1X02-3P5-VERT Limit switch, inside the debounced input block yes J1301
J1801 2-pos 3.5 mm terminal, OW-CONN-TBH-1X02-3P5-VERT Brake coil: COIL_LO and +36V_SW yes new in v1.0
J2001 5-pos 3.5 mm terminal, OW-CONN-TBH-1X05-3P5-VERT Torque bridge: SIG_N, SIG_P, VEXC, GND, TQ_SHLD yes new in v1.0
J8 Dual BNC, DOSIN-801-0095 PYC_PULL_EVENT, PYC_FAULT rev 1 placeholder
J9 Dual BNC, DOSIN-801-0095 PYC_RSYNC_IN, spare rev 1 placeholder
J10 SWD header Debug — not implemented, see below no placeholder

Eleven connectors populated in v1.0, three deferred (J8, J9, J10). J8 and J9 keep placeholder numbers until their sheets exist — expect them to renumber when they do. The brake and torque terminals (J1801, J2001) and the torque BNC (J901) are new in v1.0 and have no Rev 0 equivalent.

Earlier counts in this file are historical. "Seven connectors in Rev 0, three deferred" described the 2026-08-27 state, when the InstrumentPort FFC was J1501 and the analog BNC had just been placed as J1101.

The three terminals switched to the vertical OW-CONN-TBH-*-VERT family in the same pass that made the other connectors vertical; the horizontal Phoenix OW-CONN-1844236/1844249/1844210 parts this table used to name are no longer what the board carries.

AUX_ANALOG_OUT And The Dual BNC — Closed 2026-08-27

~~J7 Dual Versus Single — Reopened 2026-08-26.~~ Closed by construction.

The board carries a single vertical HL-BNC-KHDA as J1101 — neither of the two options this section debated. The dual DOSIN-801-0095 was specified when the analog output was going to be a dual DAC8563 and the second channel cost a footprint and two passives. The built block is single-channel, so AUX_ANALOG_OUT costs a second full instance — one more DAC80501, one more OPA197, and the passives — and it is not populated. The stocked single DOSIN-801-0050 was the fallback but was not what got placed; the vertical HL-BNC-KHDA matches the rest of the board's vertical connector direction and has four ground pins at ±5 mm around the centre pin, which is what lets D2001's ground via land on a connector ground pin.

AUX_ANALOG_OUT is therefore not on this board at rev 1, and AIN1_P/N stay no-connect. If it is ever wanted, it is a second block instance on its own hierarchical sheet plus a second jack, not a stuffing option.

The related question — whether a second channel would carry a validity signal — is in docs/encoder-analog-output.md. It was nearly free and is now not, so the argument for encoding validity into the angle signal's own out-of-band levels correspondingly got stronger.

The SWD Header Is Deferred, By Decision

Decided 2026-08-17. J10 is not on the board. SWCLK, SWD, and RUN on the RP2040 are left unconnected and explicitly NC-flagged, and the header waits for a future board revision.

This is an access limitation, not an electrical defect. RP2040 RUN has an internal pull-up of 50-80 kΩ and the datasheet permits leaving it floating or tying it to IOVDD, so the part boots correctly with nothing attached; the NC flags are there so ERC agrees that the omission is intentional rather than an unfinished net.

What it costs, stated plainly so bring-up does not discover it:

  • No debugger. No SWD means no breakpoints, no single-stepping, and no post-mortem of a hung core. Firmware debugging is USB CDC prints and the InstrumentPort capture, which is exactly why the port was pulled forward.
  • No hardware reset line. BOOTSEL recovery means holding SW501 while power cycles, and with the ORing diodes that requires both USB and pyControl unplugged before power returns — pulling the USB cable alone leaves the board alive on pyControl 5 V. Write that into the bring-up procedure.

The motor terminal (J1102), the encoder terminal (J1103), and the limit-switch terminal (J1301) are one stocked 3.5 mm screw-terminal family (OW-CONN-TBH-1X04-3P5-VERT, -1X05-, -1X02-), which keeps the harness story uniform and needs no purchase. Updated 2026-08-27: these are the vertical TBH parts, not the horizontal Phoenix OW-CONN-1844236/-1844249/-1844210 this paragraph used to name, and the motor and encoder designators rotated — see the renumbering note above.

They are screw terminals, not locking. The earlier plan called for a locking motor connector because the rig gets handled. Screw terminals unblock Rev 0 today; if the rig proves that insufficient, sourcing a locking 4-pos motor connector is a rev 1 change confined to the motor terminal. Decide before the harness is built, not after.

pyControl Port In Rev 0

Only the serial link is populated. See docs/pycontrol-port-pinout.md for the full pinout and its provenance.

RJ-45 pin Signal Rev 0
1 DIO-A RP2040 UART0 RX (GP1)
2 GND Ground
4 DIO-B RP2040 UART0 TX (GP0)
6 5V Footprint for the ORing branch; population gated on the port's 5 V budget
3, 5, 7, 8 POW-A, SPECIAL, POW-B, 12V Not connected

Series resistors and ESD protection on every populated port signal.

PYC_PULL_EVENT, PYC_FAULT, and PYC_RSYNC_IN are rev 1. Both DIO lines are consumed by the UART, so when those lines arrive they still land on BNCs — that analysis is unchanged and is why J8 and J9 keep their designators.

What Rev 0 Gives Up

Stated plainly so nobody discovers it during bring-up:

  • No independent encoder cross-check, ever. As of 2026-08-16 this is permanent rather than deferred: the encoder reaches the TMC5130A only, in every revision. ENCODER_MISMATCH in docs/uart-protocol.md has no second count to compare against and should be retired rather than left reserved. Encoder integrity rests on the driver's own count plus the index pulse. The InstrumentPort narrows this — ENC_A and ENC_B are captured alongside the SPI reads of X_ENC, so the decoding can be checked by hand. That is a bench capability, not a runtime fault, and it goes away at rev 2.
  • No MCU copy of the limit switch. Firmware learns a homing event from the driver over SPI, not from a GPIO. LIMIT_SW is on the InstrumentPort in Rev 0; the test pad it was also meant to have does not exist yet.
  • No analog angle output. The researcher-facing 0-5 V BNC is rev 1. Angle is available over the UART only.
  • Seven of the eighteen InstrumentPort digital Strands are unrouted, and the two rev-1 buffer packages were never placed at all. Rev 0 captures the TMC SPI bus, both directions of the pyControl serial link, the two firmware triggers, the two encoder channels, and the limit switch — enough for firmware development, which is why the port was pulled forward, but not the event, DAC, brake, or torque groups.
  • No pyControl TTL event lines. PULL_EVENT, FAULT, and RSYNC are rev 1, so there is no hardware-timed event or fault path and no time-alignment signal. Everything is in UART time, with UART latency, for Rev 0.
  • No synthetic homing stimulus. Permanently, not deferred — homing is exercised with the real switch.

None of these block the stated Rev 0 purpose, which is to exercise the interfaces. All of them matter for experiments with animals, and none of that is in Rev 0's scope either.

Test Points — Outstanding

None of this is on the board. As of 2026-08-17 there are no test points anywhere on mouse-joystick-pcb. Everything below is a requirement to satisfy, not a description. It is the largest single gap between this document and the board, and with no SWD header either it is also the board's only remaining bring-up access besides the InstrumentPort and USB CDC.

TMC_DIAG0 did not earn a slot because a driver fault reaches the capture through PYC_FAULT anyway, one firmware latency later. It goes to a test point.

Labelled test pads, not bare vias: TMC_DIAG0, TMC_DIAG1, ~{TMC_DRV_EN}, HV_EN, HV_SNS, REFL, and in later revisions TQ_DRDY. (~{INSTR_BUF_OE} left this list on 2026-09-16 with the net itself.) TQ_DRDY has no slot because the I2C capture already shows when the host reads a conversion.

Two more entries left this list on 2026-08-26. ~{DAC_LDAC} and ~{DAC_CLR} are moot for the same reason SWSEL is: the nets do not exist, because the DAC80501 has neither pin. What replaces them is better — the analog output block exposes VREFIO, AMP_VOUT and AOUT as distinct nets, so one probe each would verify the 2.500 V reference, the 1.32 gain, and the 249 Ω drop.

Corrected 2026-08-27: the block carries no test pads. This paragraph claimed it did. Checked against the block schematic and its layout fragment — there is no TestPoint symbol or pad anywhere in analog_output_0_3v3_dac80501, which agrees with docs/bringup-log.md's standing note that no test points exist anywhere on this board. The three nets are still the right ones to pad; they are simply not padded yet, and adding them is a block change, not a board change.

One thing is owed at board level for the analog section, contrary to the last sentence this paragraph used to carry: AIN0_N to AGND. The instrument tap's series 1 kΩ started out board-level (R1409) and moved into the block on 2026-08-27 as R2008 behind the new AOUT_MON port, so the isolation resistor now travels with the thing it protects.

Two entries left this list on 2026-08-17. SWSEL is moot: the TMC5130A's pin 28 is tied directly to GND with no net, so there is nothing to pad and nothing a jumper could change — SPI mode is strapped, not selected. ENC_N is moot: the net does not exist, J1103 pin 2 is a reserved no-connect, and ENCN is terminated at R1201.

Also pad ENC_A, ENC_B, and LIMIT_SW. They hold DIG28, DIG29 and DIG15 permanently now that the 32-channel map has room — the rev-2 eviction that used to threaten them is gone — but none of them reaches the MCU, so a pad is the only access that does not depend on the port being cabled.

Where Each Signal Is Tapped

Superseded in part, 2026-09-19. Every tap listed here is being split off its functional net by a series resistor. Which node is tapped does not change — the reasoning below still governs that — but the branch becomes its own net. Values, owning blocks and naming are in docs/instrument-port-tap-resistors.md.

Two tap points are decisions, not details, and getting either wrong makes the capture answer a different question than the one being asked.

pyControl serial: the block's existing PYC_DIO_A/PYC_DIO_B pins, the MCU side of R29001/R29002. No block change, no new boundary pins.

An earlier draft of this section put the tap on the connector side of those resistors. That was wrong and is recorded here so it does not get re-proposed. On a digital instrument channel the two nodes are indistinguishable by construction. The only current in the resistor is the 330 µA the 10 kΩ GP1 pull-up sinks when the host drives low, so the drop across 100 Ω is 33 mV — three orders of magnitude below any logic threshold. The two sides differ only when the resistor is open or something is actively fighting on the line, and both of those are hard faults a meter finds faster than a capture does. A logic analyzer cannot resolve the distinction the extra pin would expose.

Three reasons the MCU side is the better tap, not merely the cheaper one:

  1. It answers the question actually being asked. "Is what the RP2040 receives what the breakout board sent?" is a question about the signal presented to the pin. That is the MCU-side node. The capture is still fully independent of firmware — it is hardware observation, not a log — which is what "observe the link directly" needs to mean.
  2. The 100 Ω becomes series protection for the buffer. Tapping the connector side would put the SN74LVC125A input on the unprotected node, between the USBLC6-2P6 and the resistor, with no series element between it and the cable.
  3. It keeps the ESD node short. The block deliberately routes each line in one USBLC6 I/O pad and out the other so the clamp connection stays short. Hanging a stub off that node to reach a buffer elsewhere on the board adds capacitance and antenna to the one node that most wants neither.

Exposing both sides of the resistor would also present the board with a distinction it cannot use, which is the opposite of the discipline the rest of that block's interface follows. If a future board genuinely needs the raw cable node — a bias network, a different clamp, a transceiver — add the pin then, when there is a consumer to define it against.

TMC5130A: the A side of the bus switch, not the chip pins. Corrected 2026-08-17 — the A side is the TMC / switched side and B is the MCU side, the reverse of what this section originally assumed. The taps are on A, so the capture shows the driver's end of the link. When the bus switch is enabled, A and B are the same node through a few ohms, so the capture shows what the chip sees. When it is isolated, the taps sit at their terminated idle levels and go quiet — which still diagnoses the bus-switch state instead of hiding it, and that is exactly the failure mode a firmware author needs to recognise. It diagnoses it from the far end rather than the near one: the earlier wording promised MCU traffic with TMC_SPI_CIPO silent, and that is what a B-side tap would show.

This looks like the opposite of the pyControl decision above and is not. A 100 Ω resistor cannot make its two nodes read differently on a digital channel; a bus switch that can be open absolutely can. Where the series element has two distinguishable states, which side you tap is a real choice.

Reversed 2026-08-17; earlier guidance said to prefer the near side, "the one that stays in the always-on domain and stays driven", which on the as-built orientation means the B side. The 2026-08-17 schematic review raised the disagreement as finding 7 and offered two remediations — move the taps to the MCU-side nets, or keep the orientation and terminate the tapped nets. Resolution: the taps stayed on the A side and the nets were terminated. Three reasons. The A side is where the driver actually sees the signal, which is the question the capture is being asked. Termination is nine passives in board-authored sheets against re-pinning seven channels inside a proven kicad-libs design block that would then diverge from its source. And the terminations were wanted regardless of the instrument port — the TMC's SDO tri-states between transactions, its DIAG pins are open-drain, and its CSN/SCK/SDI are only weakly pulled internally.

What the old objection got right is that tapping the switched side puts always-on buffer inputs on nets that are dead whenever HV_EN is low, which is this board's normal idle state. That is the floating-CMOS-input problem the design avoids everywhere else, arriving by a new route — and closing it is exactly what the table below is for.

Pull And Termination Resistors — Moved

Superseded 2026-09-16, and the table that used to sit here is deleted rather than corrected. It listed R1104-R1601 in the instrument_port sheet, defining the inputs of buffers U1701/U1801 that no longer exist, and a motor_encoder_limit sheet that has since been renamed. The live list is Board-Level Input Biases: eight TMC biases now live in motor_encoder_switch as R1101-R1107, and nine more are owed by the new blocks. The reasoning below still stands and is the reason those resistors are sized and referenced the way they are.

Not a resistor, but it belongs with them: TMC5130A REFR_DIR (pin 9) is tied directly to GND as of 2026-08-27. It is a plain DI with no internal pull, unlike SD_MODE/SPI_MODE (pu) and SWSEL (pd), and it was floating before rev 1 — a floating right-stop input next to a 36 V bridge can trip a stop and draws ΔICC. A 10 kΩ pull-down (R1104) was fitted first and then replaced by the hard tie.

Why some reference +3.3V_SW and some GND. A pull that must read HIGH while the driver is powered — ~{TMC_CS}, both DIAG lines, ~{TMC_DRV_EN} — references +3.3V_SW, so it collapses to 0 V with the domain instead of injecting current into a dead rail. Pulls that may read LOW go straight to GND. Referencing +3.3V_MCU would be the wrong answer for the HIGH group: it would back-power +3.3V_SW to roughly 1.3 V through the TMC's protection diodes, defeating the HV_EN power-cycle and corrupting the rail sense.

+3.3V_SW is held at 0 V when off by R408/R409 — the rail-sense divider, 2 kΩ to GND — and that is what makes a pull-up to that rail a valid LOW in the off state. If the SW_RAIL_SENSE divider is ever removed, R1107, R1101, R1102, and R1103 stop defining their nets. Those two circuits are coupled; treat them as one decision.

Do not use 100 kΩ pull-downs on the TMC SPI lines. This is why the earlier prescription had to change rather than simply move. CSN, SCK, SDI, and SDO are marked (tpu) in the TMC5130A datasheet with RPU = 132/166/200 kΩ. A 100 kΩ pull-down against that parks the node at roughly 1.9 V — straddling the SN74LVC125A's 2.0 V VIH and sitting between the TMC's own 0.99 V V_INLO and 2.31 V V_INHI. It is the worst available answer: an indeterminate level on both sides. 10 kΩ gives 5 V × 10/176 = 0.28 V, an unambiguous LOW.

~OE does not gate the buffer input stage, so disabling the buffers is not a substitute for terminating their inputs. SCAS290T specifies ΔICC at 500 µA typical and 5 mA maximum per partially biased input, independent of OE, against 40 µA maximum for the whole device with valid levels on every input. A floating buffer input costs current and invites oscillation whether or not the outputs are high-Z.

Encoder: between the input conditioning and the TMC5130A. ENC_A and ENC_B are tapped on the board-local encoder section on the driver side of the chain, so the capture shows what the TMC5130A's ENCA/ENCB pins actually receive. That is the point of having them: the comparison is against the driver's own decoded count, so the tap belongs where the driver sees the signal, not where the cable delivers it. Corrected 2026-08-17: the chain is J1103, not J6, and it is filtering only — there is no ESD device anywhere in the encoder path, contrary to what this paragraph used to say. The taps need no pull-down of their own because U801 drives them and R805/R806 hold them low when it does not.

The conditioning is a real, placed design block, not a gap: orb_weaver_private:quadrature_encoder_input_5v_sn74lvc2g17. It imposes three constraints that the still-unchosen encoder has to satisfy, and they are the reason the encoder part remains the open decision that can invalidate the board:

  1. Push-pull, not open-collector. R801/R802 are 100 kΩ pull-downs on the harness side. An open-collector encoder needs them changed to pull-ups.
  2. 72 kHz filter corner. R803/R804 4.7 kΩ with C802/C803 470 pF gives τ = 2.2 µs. Check it against the encoder's maximum output frequency at the fastest sweep the rig will command.
  3. The 5-position pin order is fixed by the block. J1103 pin 1 = GND, pin 2 = reserved index no-connect, pin 3 = ENC_A_RAW, pin 4 = +5V_SW, pin 5 = ENC_B_RAW. A 5 V encoder is assumed. If the chosen part wants a different order or a different supply, the harness or the block changes.

Limit switch: the LIMIT_SW net between the debouncer and the TMC5130A REFL pin. Same reasoning — the capture should show what the driver's homing input receives. R1601 defines the buffer input.

With the ÷2 divider on SW_RAIL_SENSE, the nine resistors above are the whole passive cost of the instrumentation and of the isolation architecture's off-state behaviour together.

What Carries Forward To The Twin-Axis Board

If this board is later superseded by mouse-joysticks-pcb, these carry over unchanged:

  • the pyControl port pinout and signal set;
  • the UART protocol, with an axis argument added;
  • the analog output design and its calibration semantics, once rev 1 builds it;
  • the InstrumentPort-Digital-50 buffering pattern and its hardware-enabled default;
  • the GPIO isolation pattern, including the bypass group, which is the newest and most reusable thing this board contributes;
  • the power chain, sized for one motor instead of two.

What does not carry over: this GPIO table and this instrument overlay. The twin board re-derives both, and its overlay is already written.