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

342 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```