Files
midi-router/docs/hardware-plan.md
T
nils 2d9c201a12 Hardware: add LiPo battery + power-path charger
BQ24075 (single-cell charger + power-path SYS): USB present -> powers
device + charges cell; USB absent -> battery takes over seamlessly.
System rail becomes SYS (~3.0-4.2V battery domain); existing converters
already accept it (3.3V is buck-boost RT6150B so survives sag). MAX17048
fuel gauge on I2C1 for SoC on OLED. Power budget updated (~200-400mA
from SYS, charge limited to ~500mA on USB 2.0). ~5-8h on a 1500-2000mAh
cell. Connection table, BOM, risks, build order updated.

NOTE: hardware/netlist.net still references +5V rails; needs the SYS/
BQ24075/BAT/MAX17048 nets added — regenerate on request.
2026-08-21 17:27:22 +02:00

372 lines
18 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 ── BQ24075 (charger + power-path) ── SYS (~3.0-4.2 V)
│ │
LiPo cell (charge/discharge) ├── 3.3 V buck-boost ── RP2040 + flash + RM2
│ ├── +12 V boost ──┐
│ ├── −12 V inverter┤ CV op-amps (±12 V) ── 4× stereo TRS
MAX17048 fuel gauge (I2C) └── 4.096 V/2.048 V refs ── DAC ── diff-amp → ±10 V
│
RP2040: PIO0 SPI ── RM2 | USB DP/DM ── USB-MIDI composite | UART0 ── MIDI TRS
I2C0 ── keypad | SPI0 ── keypad LEDs | SPI1 ── DAC | I2C1 ── OLED + fuel gauge
GPIOs ── 4× tact + charge-status LED
```
## 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 + LiPo battery with power-path)
- USB-C 5 V → polyfuse + ESD → **VBUS_5V** → **BQ24075** charger/power-path IC.
- VBUS present → device runs from VBUS *and* charges the cell; SYS output
is the system rail.
- VBUS absent → battery powers SYS seamlessly (no glitch).
- **SYS rail (~3.0-4.2 V)** — the battery-voltage domain, the new system input.
- SYS → **3.3 V buck-boost** (RT6150B-33GQW — already a buck-boost, so it
holds 3.3 V through the whole SYS range, including battery sag) → RP2040,
RM2, flash, DAC VDD, OLED, pull-ups.
- SYS → **+12 V boost** (TPS61088) → +12 V CV op-amp rail (input 3.0-4.2 V
when on battery is fine; just draws more current at low voltage).
- SYS → **−12 V inverter** (TPS63700) → −12 V CV op-amp rail.
- **4.096 V reference** (REF3040AIDBZ) and **2.048 V mid-ref** (REF2040) —
powered from +3V3 (post-buck, clean), NOT from SYS (sags with battery) or
the 3.3 V rail directly (too noisy for 1 V/oct pitch).
- **LiPo cell** (single, 3.7 V nominal, ~1500-2000 mAh, integrated protection
PCB) on BAT. Charge rate set by BQ24075 PROG pin (~500 mA to stay within USB
2.0; raise if using USB-C 1.5/3 A).
- **MAX17048 fuel gauge** (I2C1, shared with OLED) → battery state-of-charge
on the OLED + low-battery warning in firmware.
**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 | ~20 mA @±12 V → ~60 mA @SYS (×2 converters, ~80% eff) | ~75 mA @SYS |
| MIDI out LED (brief) | — | ~15 mA @3.3 V |
| **System draw from SYS** | **~200-260 mA** | **~340-400 mA** |
Battery life: a 2000 mAh cell at ~250 mA avg → ~7-8 h (peak BLE + display); a
1500 mAh cell → ~5-6 h. SYS sags to ~3.3 V at end of discharge, all converters
stay in regulation. When USB present, SYS = charge voltage (~4.2 V max); charge
current limited to ~500 mA by default to respect USB 2.0 (battery tops up in
~3-4 h for a 1500 mAh cell).
Rationale: USB *or* battery operation via one power-path IC; the existing
converters already accept the SYS voltage range (buck-boost on 3.3 V is the key
— a plain buck would drop out near end-of-discharge). BLE-only keeps the budget
comfortable; WiFi TX would push ~600 mA peak and halve battery life.
## 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 |
| Charger | BQ24075DRCR | MSOP-10 | single-cell LiPo charger + power-path (SYS pin) |
| Fuel gauge | MAX17048G+T10 | TDFN-8 (or bare module) | LiPo SoC over I2C1 (optional) |
| Battery | LiPo 3.7 V, 1500-2000 mAh | cell w/ protection PCB | integrated DW01/FS8205A protection |
| 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;
BLE-only keeps the system draw ~340-400 mA peak from SYS, plus up to ~500 mA
battery charge from USB → USB-C 5 V/900 mA-1.5 A recommended (USB 2.0 500 mA
only if charging paused or BLE-only/low charge rate).
6. **Battery sag at end-of-discharge** — SYS can sag to ~3.3 V; the 3.3 V path
must be a buck-boost (RT6150B is) or it drops out. Fuel gauge drives a
low-battery warning in firmware; cut off at ~3.3 V to protect the cell.
7. **Charge thermal** — BQ24075 thermal-regulates; keep it away from the CV
references / DAC and away from the LiPo for thermal separation.
8. **LiPo shipping / safety** — LiPo cells have shipping (UN3481) and safety
considerations; use a protected cell, add a charge LED, follow charge-IC
layout guidance.
9. **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 (BQ24075 power-path + LiPo, 3.3 V buck-boost, +12 V boost,
−12 V inverter, 4.096 V + 2.048 V refs, MAX17048 fuel gauge).
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 |
| U12 | BQ24075 | single-cell LiPo charger + power-path |
| U13 | MAX17048 | LiPo fuel gauge (I2C) |
| BAT1 | LiPo cell | ~1500-2000 mAh, integrated protection |
| 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 (with battery power-path)
```
J1.VBUS ── FB1 ── VBUS_5V
VBUS_5V ── U12 (BQ24075 VBUS) ; USB input to charger/power-path
BAT1 ── U12 (BAT) ; LiPo cell
U12 (SYS) ── SYS rail (~3.0-4.2 V) ; system supply (USB or battery)
SYS ── U3 (RT6150B-33) ── +3V3 ; buck-boost: holds 3.3 V across SYS range
SYS ── U4 (TPS61088) + L1 LC ── +12V
SYS ── U5 (TPS63700) + L2 LC ── −12V
+3V3 ── U6 (REF3040) ── VREF4V096 ; refs from clean +3V3, not saggy SYS
+3V3 ── U7 (REF2040) ── VMID2V048
U13 (MAX17048) SDA/SCL ── I2C1 (shared with OLED) ; fuel gauge
U12 (STAT) ── U1.GPIO16 ; charge status (→ LED + firmware)
GND = J1.GND, BAT1.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)
```