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.
342 lines
16 KiB
Markdown
342 lines
16 KiB
Markdown
# 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)
|
||
```
|
||
|