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

18 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 ── 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)