Files
midi-router/docs/hardware-plan.md
T
nils dfd79eb2e3 Hardware: connection table + KiCad netlist
docs/hardware-plan.md §11: authoritative connection table (refdes, all
nets by block — power, RM2 radio, USB/flash/SWD, CV DAC, 8 bipolar CV
channels, MIDI TRS, display/UI/keypad, spares).

hardware/netlist.net: machine-importable KiCad netlist (S-expr format)
derived from the table — components with footprints + all nets/pins.
Import: KiCad > File > Import Non-KiCad Netlist (or open as a netlist
to populate a board). Pins are logical (GPIO names, DAC/op-amp function
names); map physical QFN pins when placing footprints.
2026-08-21 16:58:34 +02:00

16 KiB
Raw Blame History

Hardware Plan — flexible MIDI router (v1)

Status: draft 2026-08-21. Custom all-in-one PCB, desktop, USB-powered, bipolar ±10 V CV via onboard ±12 V rails, 3.5 mm TRS MIDI, 4× stereo CV, 64×32 OLED + tact buttons (no encoder, low profile).

MCU target: develop/test on RP2040 (Pico W dev board — the firmware already runs on it), product on RP2350. The RM2 radio module is compatible with both. The Rust firmware builds for either via embassy-rp features (rp2040 → thumbv6m-none-eabi; rp235xa for the RP2350A used in the Pico 2 W → thumbv8m.main-none-eabihf). Pin assignments below are RP2040/Pico-W; the RP2350 product PCB keeps the Pico-2-W-compatible RM2 bus and reuses the same peripheral choices.

See midi-router-architecture (session memory) for the software/firmware architecture this hardware serves.

1. Block diagram

USB-C 5V ──┬── 3.3 V buck ── RP2040 + W25Q16 flash + RM2 module (CYW43439, BLE/WiFi)
           │                   │
           │                   ├── PIO0 SPI ── RM2 (CYW43439 gSPI, Pico-W pinout)
           │                   ├── USB DP/DM ── (USB-MIDI composite, CDC+MIDI)
           │                   ├── UART0 ──── MIDI TRS in (6N138) / out
           │                   ├── I2C0 ───── Pimoroni RGB Keypad (buttons)
           │                   ├── SPI0 ───── Pimoroni RGB Keypad (APA102 LEDs)
           │                   ├── SPI1 ───── 8-ch 16-bit DAC (CV outs)
           │                   ├── I2C1 ───── 64×32 SSD1306 OLED
           │                   └── GPIOs ──── 4× tact buttons (UI)
           │
           └── ±12 V rails (boost + inverter) ── CV op-amps ── 4× stereo TRS (8 CV/gate)
                  │
   4.096 V ref ── DAC (0-4.096 V) ── op-amp gain ×4.88, offset −10 V ── ±10 V ── 1 k + bidir TVS ── TRS tip/ring

2. RP2040 pin map

Peripherals follow the Pico W reference for the RM2/CYW43 bus (so the cyw43 driver and our firmware work unchanged) and our existing keypad wiring.

Function RP2040 pins Notes
USB device GP14 (DM), GP15 (DP) USB-C; CDC + MIDI composite
RM2 (CYW43) bus GP29 (CLK), GP24 (DAT), GP25 (CS-out), GP23 (PWR), PIO0, DMA CH0/CH1 copy Pico W
RM2 onboard LED CYW43 WL_GPIO0 status/heartbeat
Keypad buttons GP4 (SDA), GP5 (SCL) I2C0, expander @0x20
Keypad LEDs GP17 (CS), GP18 (SCK), GP19 (MOSI) SPI0 tx-only, 4 MHz, APA102
DAC (CV outs) GP10 (SCK), GP11 (MOSI), GP13 (CS) SPI1 tx-only, 16-bit DAC; MISO unused
DAC LDAC/RESET GP20, GP21 GPIO (optional, immediate-update)
MIDI TRS UART GP0 (TX), GP1 (RX) UART0 @31250 baud
OLED GP2 (SDA), GP3 (SCL) I2C1, SSD1306 64×32
UI buttons GP6, GP7, GP8, GP9 4× low-profile tact: Up/Down/Select/Back
SWD debug header SWCLK, SWDIO 2×3 header for Picoprobe (first-flash)
Spare GP16, GP22, GP26-28 status LEDs / future analog in

No conflicts; GP14/15 (USB) and GP23-25/29 (CYW43) kept clear of everything else.

3. Power tree (USB-only, with CV boost)

  • USB-C 5 V → polyfuse + ESD → VBUS_5V.
  • VBUS_5V → 3.3 V buck (RT6150B-33GQW) → RP2040, RM2, flash, DAC VDD, OLED, pull-ups.
  • VBUS_5V → +12 V boost (TPS61088 + LC filter) → +12 V CV op-amp rail.
  • VBUS_5V → −12 V inverter (TPS63700 + LC filter) → −12 V CV op-amp rail. Both rails feed the bipolar CV op-amps. Low current (~20 mA each), LC-filtered.
  • 4.096 V reference (REF3040AIDBZ) for the DAC, and a 2.048 V mid-ref (buffered divider or REF2040) for the bipolar offset — NOT the 3.3 V rail (too noisy for 1 V/oct pitch).

Power budget (BLE-only): we use Bluetooth only, never WiFi TX. The cyw43 driver must load the WiFi firmware to bring up BT (BT-only init isn't supported yet), but WiFi never transmits → power stays at BLE levels, not WiFi-TX levels.

Rail / consumer Typical Peak
RM2 (BLE TX, no WiFi TX) ~30 mA @3.3 V ~80 mA
RP2040 / RP2350 ~30 mA ~50 mA
DAC8568 ~2 mA ~2 mA
OPA4192 ×2 (quiescent) ~9 mA @±12 V ~9 mA
OLED SSD1306 ~20 mA ~40 mA
±12 V rails load (op-amps + DAC out) ~20 mA @12 V → ~50 mA @5 V (×2 converters, ~85% eff) ~60 mA @5 V
MIDI out LED (brief) — ~15 mA @3.3 V
Total from 5 V ~180-230 mA ~330-380 mA

Fits USB 2.0 (500 mA) with headroom; USB-C default (900 mA) is comfortable. If WiFi TX were ever enabled, RM2 spikes to ~350 mA and the total approaches ~650 mA — stay BLE-only or require a USB-C 1.5 A/3 A source.

Rationale: USB-only power (single cable, no external PSU) while delivering bipolar ±10 V CV via two small low-current rails. The LC filters + dedicated Vref keep pitch clean.

4. CV analog section (4 stereo = 8 channels, bipolar ±10 V)

  • DAC: 8-channel 16-bit SPI (DAC8568IAPW), Vref = 4.096 V → 0-4.096 V out. 16-bit over ±10 V (20 V span) ≈ 0.3 mV/step ≈ 0.003 semitone at 1 V/oct — plenty for pitch.
  • Bipolar scaling: each channel maps 0-4.096 V → −10 V…+10 V. Difference amp: Vout = 4.88·(Vdac − 2.048) where 2.048 V is the mid-reference (buffered divider from the 4.096 V ref, or REF2040). At Vdac=0 → −10 V, Vdac=2.048 → 0 V, Vdac=4.096 → +10 V. Per-channel gain/offset trimpot for pitch calibration (0.2 % ref → ~±20 mV untrimmed ≈ 0.02 semitone — trimmable to <1 mV).
  • Op-amps: OPA4192IPWR (quad, RRO, 25 µV offset) ×2 on the ±12 V rails. RRO needed to reach ±10 V cleanly on ±12 V (TL074 clips at ±~10.5 V — too close; OPA4192 swings to ±11.9 V).
  • Per-channel model: every CV channel is an independent (midi_channel, function, polarity) sink. The output stage is always bipolar-capable (±10 V); the function decides the mapping:
    • pitch → 0…+10 V (note/12, standard unipolar-positive on a bipolar output)
    • gate → 0/+10 V (or 0/+5 V)
    • velocity → 0…+10 V (vel/127·10)
    • CC (unipolar) → 0…+10 V; CC (bipolar) → −10…+10 V (cc/127·20 − 10) Gate = a DAC channel driven to 0/+10 V (keeps routing uniform).
  • Output protection: 1 kΩ series + bidirectional TVS per output (outputs swing negative) to TRS tip/ring.

5. MIDI TRS (3.5 mm) — 2 jacks (in + out), TRS-A wiring

  • In: tip → 220 Ω → 6N138 optocoupler LED → cathode to ring. Transistor: collector to 3.3 V via 1 k pull-up → UART0 RX (GP1); emitter to GND; base speed-up (1N4148 + 1 k). Isolated; powered from the input loop.
  • Out: UART0 TX (GP0) → 220 Ω → tip; ring to 3.3 V. (3.3 V/220 Ω ≈ 15 mA, fine for MIDI-out LED.)
  • TRS-A: tip = signal, ring = return (Korg / Make Noise convention).

6. Display + UI

  • OLED: SSD1306 64×32 (0.42"), I2C1. Small — UI is paginated (≈2 lines of small text); routing/CV assignments shown a few at a time.
  • Buttons: 4× low-profile SMD tact (Up / Down / Select / Back) on GP6-9. No rotary encoder (height constraint).

7. RM2 radio module — risk largely eliminated

Use the Raspberry Pi Radio Module 2 (RM2, part RMC20452T): a CYW43439-based module (16.5 × 14.5 mm, 21 castellated pads) with integrated inverted-F PCB antenna and internal PA/LNA/T-R switch. Full modular radio approval in US/EU/ UK/CA (FCC ID 2ABCB-RMC2GW4B52) — no radio design or certification needed on the host board. Software-compatible with the Pico W SDK, so our cyw43 firmware works unchanged.

Host PCB responsibilities (from the RM2 datasheet):

  • Mount the 21 castellated pads; route the gSPI host pins to RP2040 (Pico W pinout: CLK=GP29, DAT=GP24, CS=GP25, PWR=GP23, on PIO0 + DMA CH0/CH1).
  • 3.3 V supply (no other rails).
  • Antenna keepout area must remain free of metal per the datasheet for the module's PCB antenna to perform.

8. BOM (concrete parts)

Ref Part Package Notes
MCU RP2040 (dev) / RP2350A (product) QFN-56 / QFN-60 dual target via embassy-rp features
Flash W25Q32JVSSIQ SOIC-8 32 Mb (4 MB) — Rust firmware + blobs ~870 KB UF2, headroom for router code
Radio RM2 module RMC20452T 16.5×14.5 mm, 21-pad CYW43439 + PCB antenna, pre-certified
DAC DAC8568IAPW TSSOP-16 8-ch 16-bit SPI, external Vref mode (AD5668BRUZ-1 alt)
Op-amp OPA4192IPWR ×2 TSSOP-14 quad RRO, 25 µV offset, on ±12 V rails — pitch-accurate
Vref REF3040AIDBZ SOT-23 4.096 V DAC ref, 0.2%, 30 ppm/°C
Mid-ref REF2040AIDBZ (or buffered divider) SOT-23 2.048 V bipolar offset reference
Calib 8× trimpot (e.g. 3296W) THT/SMD per-channel gain/offset trim
MIDI opto 6N138 DIP-8 / SMD MIDI in isolation
3.3 V reg RT6150B-33GQW QFN buck-boost, 800 mA, Pico-proven (TPS62A01 alt)
+12 V boost TPS61088 QFN 5→12 V, low-noise + LC filter
−12 V inverter TPS63700 SOT-23-6 / QFN 5→−12 V, low-noise + LC filter (LM2611 alt)
Display SSD1306 64×32 OLED 0.42" I2C —
Jacks 6× 3.5 mm TRS SMD 2 MIDI + 4 CV stereo
USB USB-C receptacle SMD 5 V data + power
UI 4× low-profile tact SMD Up/Down/Select/Back (no encoder)
Debug 2×3 SWD header THT Picoprobe first-flash
— polyfuse, bidirectional TVS array, passives — bipolar output protection + rail protection

9. Risks / open items

  1. Bipolar ±10 V offset/gain calibration — the 2.048 V mid-ref and ×4.88 gain set the 0 V center and ±10 V span; 0.2 % ref → ~±20 mV untrimmed (≈0.02 semitone). Per-channel trimpots trim gain + offset to <1 mV; or calibrate in firmware (measure + store correction) to avoid trimpots.
  2. ±12 V rail noise on pitch — two switching converters (+12 V boost, −12 V inverter); LC filter both rails + 4.096 V/2.048 V refs are the pitch reference, not the rails, so op-amp PSRR + filtering keeps it clean.
  3. Custom RP2040 bring-up — needs a SWD header + Picoprobe; USB bootloader only works after first flash.
  4. 64×32 OLED UI — tight; menu must be paginated and minimal.
  5. Boost current budget — RM2 TX spikes + 8 CV op-amps + two converters on a single USB 5 V/500 mA port is tight; BLE-only keeps it ~330-380 mA peak (fits), but specify USB-C 5 V/900 mA for headroom.
  6. RM2 antenna keepout — follow the datasheet keepout; the one layout constraint the radio imposes.

10. Build order (hardware)

  1. Schematic (this doc is the spec).
  2. RM2 module footprint + antenna keepout.
  3. RP2040 + flash + USB + SWD.
  4. Power (3.3 V buck, +12 V boost, −12 V inverter, 4.096 V + 2.048 V refs).
  5. CV analog (DAC + difference-amp + offset + trim + bidir protection + TRS).
  6. MIDI TRS circuit.
  7. OLED + buttons.
  8. Keypad header (the Pimoroni keypad connects via a header or is integrated).
  9. PCB layout, DRC, review, fab.

11. Connection table (netlist source of truth)

Reference designators and net membership. RP2040 pins are logical GPIO names (map to physical QFN pins in KiCad). A machine-importable KiCad netlist lives at hardware/netlist.net.

Reference designators

Ref Part Role
U1 RP2040 MCU
U2 W25Q32JV flash
MOD1 RM2 (RMC20452T) BLE/WiFi radio
U3 RT6150B-33 3.3 V buck
U4 TPS61088 +12 V boost
U5 TPS63700 −12 V inverter
U6 REF3040 4.096 V DAC reference
U7 REF2040 2.048 V bipolar mid-reference
U8 DAC8568 8-ch 16-bit SPI DAC
U9, U10 OPA4192 quad RRO op-amp ×2 (8 CV channels)
U11 6N138 MIDI-in optocoupler
DISP1 SSD1306 64×32 OLED (I2C)
J1 USB-C 5 V + USB data
J2 2×3 header SWD (Picoprobe)
J3, J4 3.5 mm TRS MIDI in / out
J5–J8 3.5 mm TRS 4× stereo CV out
J9 2×5 header Pimoroni keypad
SW1–SW4 tact UI (Up/Down/Select/Back)
FB1 polyfuse 5 V input protection
L1, L2 inductors boost/inverter LC filters
D1–D8 bidirectional TVS CV output protection
R_cal1–R_cal8 — per-channel calibration (see §4)

Power nets

J1.VBUS  ── FB1 ── +5V
+5V ── U3 (RT6150B-33) ── +3V3
+5V ── U4 (TPS61088) + L1 LC ── +12V
+5V ── U5 (TPS63700) + L2 LC ── −12V
+3V3 ── U6 (REF3040) ── VREF4V096
+3V3 ── U7 (REF2040) ── VMID2V048
GND = J1.GND, all return paths

RM2 radio (Pico-W-compatible gSPI)

MOD1.CLK  ── U1.GPIO29 (PIO0 SPI CLK)
MOD1.DAT  ── U1.GPIO24 (PIO0 SPI DOUT/MOSI)
MOD1.CS   ── U1.GPIO25 (output, CS)
MOD1.PWR  ── U1.GPIO23 (output, power enable)
MOD1.IO0  ── (onboard LED, CYW43 WL_GPIO0 — status)
MOD1.VDD  ── +3V3
MOD1.GND  ── GND
MOD1 bus driven by U1 PIO0 + DMA CH0/CH1 (firmware copies Pico W)

USB + flash + SWD

J1.DP   ── U1.GPIO15 (USB_DP)
J1.DM   ── U1.GPIO14 (USB_DM)
U2.CS   ── U1.QSPI_CS
U2.CLK  ── U1.QSPI_SCK
U2.DQ0  ── U1.QSPI_SD0  ... DQ3 ── SD3   (QSPI flash)
U2.VDD  ── +3V3
J2.SWDIO ── U1.SWDIO   J2.SWCLK ── U1.SWCLK   J2.GND ── GND

CV DAC (SPI1)

U8.SCLK  ── U1.GPIO10
U8.DIN   ── U1.GPIO11
U8.CS    ── U1.GPIO13  (output)
U8.LDAC  ── U1.GPIO20  (output, immediate-update / strobe)
U8.RSTB  ── U1.GPIO21  (output, reset)
U8.VREF  ── VREF4V096
U8.VDD   ── +3V3
U8.VSS   ── GND
U8.VOUTA..VOUTH ── 8 CV difference-amp inputs (see below)

CV analog (8 channels, bipolar ±10 V)

Per channel n (n=1..8): subtractor Vout = (Rfb/Rin)*(Vdac − VMID2V048), Rin=10.0 k, Rfb=48.7 k (ratio 4.87 → ±9.98 V span; firmware calibrates exact). One op-amp = 1/4 of an OPA4192 (U9 = ch1-4, U10 = ch5-8).

Channel 1 (J5.tip = pitch, J5.ring = gate):
  U8.VOUTA ── Rin1a ── U9.1IN-        ; Vdac into subtractor V-
  VMID2V048 ── Rin1b ── U9.1IN+       ; mid-ref into V+
  U9.1OUT ── Rfb1 ── U9.1IN-          ; feedback
  Rgnd1 (48.7k) ── U9.1IN+ ── GND     ; balance resistor
  U9.1OUT ── Rser1 (1k) ── D1 ── J5.tip
  U9.1OUT ── (rail) +12V / −12V       ; OPA4192 supply
Channel 2 (J5.ring): U8.VOUTB → U9.2... → J5.ring
...channels 3,4 → U9.3/4 → J6.tip/ring
...channels 5-8 → U10.1-4 → J7.tip/ring, J8.tip/ring

Pattern repeats: VOUTx → Rin → subtractor → Rfb feedback, Rgnd on V+, output → 1 k series → TVS → TRS tip/ring. U9/U10 V+ = +12 V, V− = −12 V.

MIDI TRS (3.5 mm, TRS-A)

MIDI in (J3):
  J3.tip ── R_lim (220Ω) ── U11.LED-A (6N138 anode)
  U11.LED-K ── J3.ring
  U11.OC-collector ── +3V3 via R_pull (1k) ── U1.GPIO1 (UART0 RX)
  U11.OC-emitter ── GND
  U11.base ── 1N4148 + 1k speed-up ── GND/+3V3 (per 6N138 app note)
MIDI out (J4):
  U1.GPIO0 (UART0 TX) ── R_lim (220Ω) ── J4.tip
  J4.ring ── +3V3

Display + UI + keypad

DISP1.SDA ── U1.GPIO2 (I2C1 SDA)    DISP1.SCL ── U1.GPIO3 (I2C1 SCL)
DISP1.VDD ── +3V3                   DISP1.GND ── GND
SW1 ── U1.GPIO6   SW2 ── U1.GPIO7   SW3 ── U1.GPIO8   SW4 ── U1.GPIO9
(each SW: other side to GND; internal pull-up)
Keypad header J9 (Pimoroni RGB Keypad):
  J9.SDA  ── U1.GPIO4 (I2C0 SDA)     J9.SCL ── U1.GPIO5 (I2C0 SCL)
  J9.CS   ── U1.GPIO17               J9.SCK ── U1.GPIO18   J9.MOSI ── U1.GPIO19
  J9.VDD  ── +3V3                    J9.GND ── GND

Spare GPIOs

U1.GPIO16, GPIO22, GPIO26, GPIO27, GPIO28 ── unassigned (status LEDs / analog in / future)