exocortex

Hub board (EXO-430): reference, bias, clock, data, battery

One per helmet. It feeds 16 tiles (EXO-410 + EXO-420, docs/pcb.md) over the 12-pin FPC and links the helmet to a host. Source: exo/pcb/hub.py (circuit as code → out/pcb/hub_circuit.json) and exo/pcb/hub_check.py (checks → out/pcb/hub_report.json, BOM → out/pcb/hub/hub-bom-jlc.csv).

Status: circuit and budgets only. There is no land pattern for the big ICs, no outline, no placement, no routing, and nothing has been built. Every number is a datasheet value or a calculation, and says which.

uv run python -m exo.pcb.hub && uv run python -m exo.pcb.hub_check

Block diagram

 USB-C (sink, USB 2.0) ──USBLC6── D+/D- ───────────────────────────────┐
   │ VBUS ── 2N7002 interlock (JP1) ──► holds +3V6 off while plugged in │
   ▼                                                                    ▼
 BQ25895 charger / NVDC power path ◄── 1S Li-ion 4000 mAh       FT2232H (USB 2.0 HS, 480 Mb/s)
   │ VSYS 3.5–4.4 V          (JST PH: BAT+, NTC, BAT-;            ch A async FIFO   ch B async FIFO
   │                          DW01A + 8205A protection,              ▲ 8 bit          ▲ 8 bit
   │                          MAX17048 gauge)                        │                │
   ├─ TPS63020 ─► +3V3 hub logic                          RP2350B "A" (ports 1-8)   RP2350B "B" (ports 9-16)
   │                                                      8 PIO UART RX, 1 PIO 8-wide UART TX, FIFO, SPI, 8 EN
   └─ TPS63020 (EN = TILE_EN · /VBUS) ─► +3V6 ──┬─ 16 × TPS2553-1 (150 mA, latch-off) ─► VIN, ports 1-16
                                                └─ TPS7A20 ─► +3V3A (REF, DRL)          │SPI 24 MHz      │SPI
                                                                                        ▼                ▼
                                                          ESP32-S3-MINI-1: Wi-Fi link + system controller
                                                          (I2C: charger, gauge; ADC: CC1/CC2, REF/DRL, +3V6;
                                                           SWD + RUN to both RP2350s; TILE_EN)

 2.048 MHz XO ─star─┬─ LMK1C1108 ─33R×8─► CLK ports 1-8
   (OE = tile PG)   ├─ LMK1C1108 ─33R×8─► CLK ports 9-16
                    └─ 74LVC1G17 ─► CLK_MON ─► RP2350 A PIO: launches SYNC on a falling CLK edge
 SYNC (RP2350 A) ──► 2 × 74LVC244 ─33R×16─► SYNC ports 1-16   (B listens, for timestamps)

 REF contact ─100k─100k─► TLV333 follower ─► REF_BUF ─10k×16─► REF ports 1-16
                                              └─390k─► DRL integrator (1 nF) ◄─ set point = VMID 1.65 V
                                                       + 3.3 mVpp 125 Hz tone (RP2350 A PWM, 1M/1k)
                                                         │
 BIAS contact ◄─100k─100k──────────────────────────────┘

 per port (FPC, pin n → tile pin n): 1-2 VIN, 3 GND, 4 REF, 5 GND, 6 CLK, 7 GND, 8 SYNC,
                                     9 RXD (tile TX), 10 TXD (tile RX), 11-12 GND

Part choices

Data path: 2 × RP2350B and an FT2232H. The hub must receive 16 UARTs at 3 Mbaud, send to 16 more, and move 1.94 MB/s to a host.

option 16 RX + 16 TX at 3 Mbaud host link at 15.5 Mb/s verdict
1 × RP2350B 12 PIO SMs + 2 UARTs = 14 receivers, and nothing left over for TX USB FS only (12 Mb/s nominal, ~8–9 in practice) no
2 × RP2040 8 SMs each: 6 RX + 2 UART RX, 1 TX, 1 FIFO. Possible, but each chip has only 30 GPIO for 16 UART + 12 FIFO + SPI USB FS each no (pins)
iCE40UP5K 39 I/O in SG48, but 32 UART + 15 FIFO lines are needed needs a bridge no (pins)
iCE40HX4K-TQ144 + FT232H + MCU yes 40 MB/s sync FIFO works, but three chips, a 1.2 V rail, config flash and a second toolchain
2 × RP2350B (48 GPIO) + FT2232H per chip: 8 PIO RX (PIO0/PIO1), one PIO SM drives 8 TX lines in parallel (out pins, 8), and spare SMs for the FIFO and SYNC FT2232H HS, one async FIFO channel per RP2350, 8 MB/s each chosen: $2.28 of MCUs, same family and PIO as the tiles' RP2040

RP2350B runs at 144 MHz (12 MHz × 120 / 5 / 2), so the PIO UART runs at 8 clocks per bit with an integer divider of 6. The GPIO plan (GPIO in hub.py) uses all 48 pins: RX 0–7, TX 8–15, FIFO 16–27, CLK_MON 28, SYNC 29, SPI 32–36, FAULT 37, per-port EN 38–45, 125 Hz tone 46, LED 47. RP2350B PIO sees a 32-GPIO window, and the plan respects it. JLC's stock is stepping A4, where the E9 input-latch erratum is fixed.

Host link. USB-C carries USB 2.0 high speed through an FT2232H ($7.80, the most expensive part). The ESP32-S3 handles Wi-Fi. Measured throughput, from Espressif's iperf figures for the ESP32-S3: 20 Mb/s TCP and 30 Mb/s UDP over the air in a lab, 83 Mb/s TCP in a shield box. The ESP32 is also the system controller (charger, gauge, interlock, SWD to both RP2350s for updates over the air), so it is always fitted. The ESP32-C5 (5 GHz) would be better in a congested 2.4 GHz band, but all ~20 LCSC listings are at 0 stock.

Clock. One 2.048 MHz CMOS oscillator (YXC OT32252.048MJBA4SL, ±20 ppm) feeds a star of three equal 22 Ω branches: two TI LMK1C1108 1:8 buffers and a 74LVC1G17 monitor tap. The LMK1C1108 beats the CDCLVC1108 on part-to-part skew (280 ps vs 0.5 ns) and on price ($2.11 vs $2.32). Each output goes through a 33 Ω source terminator, one per port and equal on every port. The oscillator's OE is tied to the tile rail's power-good, so no clock reaches an unpowered tile.

SYNC. RP2350 A's PIO waits for a falling edge on CLK_MON and pulls SYNC low for between 1 and 2047 tCLKIN (ADS131M08: ≥ 2048 is a reset). Two 74LVC244s fan it out through 33 Ω. RP2350 B reads SYNC so that both halves number their frames from the same edge.

REF and bias. The reference contact and the bias contact are two more M1.6 SMT nuts with EXO-5LLL ball-pins on the hub's scalp face. A JST-SH lead connector accepts mastoid or ear-clip electrodes instead. Each contact sees 2 × 100 kΩ before silicon, the same as on the tiles. - The REF follower drives every tile through 10 kΩ per port, so one shorted REF wire does not pull the others. - The DRL is an integrator. It holds the body at VMID = 1.65 V, the middle of the tiles' rail-to-rail followers, which are DC-coupled to the scalp. Its unity gain is at 408 Hz (390 kΩ, 1 nF). - A 125 Hz tone (fs/4, above the EEG band and below Nyquist) is summed into the set point at 3.3 mVpp. Each channel's 125 Hz amplitude is about |Z_contact − Z_ref| · ω · C_in, which gives the contact-quality / skin-oil measurement that docs/pcb.md assigns to the hub. - The op-amp is the TI TLV333, an SC70 zero-drift part with 14k in stock. The tile's MCP6V31U has 4 in stock, and the TLV333 uses the same land pattern with a different pin map (OPA_DCK).

Power. - BQ25895: I²C control and NVDC power path, so the system runs while charging. Its hardware input limit is 2.0 A (180 Ω, KILIM 355). Its D+/D− pins are left open so they don't put a stub on the HS pair. Firmware sets IINLIM from CC1/CC2 and clears HVDCP/MaxCharge so that VBUS stays at 5 V. - Two TPS63020 buck-boosts, because the cell spans 3.0–4.2 V on both sides of 3.6 V. Both run in forced PWM, a fixed 2.4 MHz with no burst noise in the EEG band. - 16 × TPS2553DBVR-1: 180 kΩ sets 129/150/174 mA (min/typ/max) against a 36 mA tile. The switch latches off on a fault, the RP2350 retries once and then reports the port. Each port has its own EN and FAULT# is wired-OR per chip, so one shorted tile or cable can't take the helmet down. The -1 latch-off part ($0.24) is also cheaper than the auto-retry version. - Battery protection: PUOLOP DW01A plus an HXY 8205A, because the Fortune originals are at 0 stock.

Isolation and faults (head-worn, IEC 60601-1 type BF intent)

  • Battery only while worn, and no mains connection while worn. The hub has no mains input. When VBUS is present, a 2N7002 holds the tile rail's buck-boost EN low, so the helmet cannot measure while a cable is plugged in. Charging and USB data are bench and off-head activities. Solder jumper JP1 (bridged by default) defeats the interlock for bench work on a phantom only, never on a person. This is a functional interlock, not a rated isolation barrier.
  • While worn, data goes over Wi-Fi. USB data to a person-worn helmet would need a 2 MOPP USB 2.0 HS isolator between the helmet and the PC. That could be external, or an on-board ADuM4165-class part plus an isolated charger, which would not fit 8 mm creepage on this board. It is an open decision, below.
  • Contacts. Every scalp contact (960 EEG, REF, BIAS) reaches silicon only through ≥ 200 kΩ. The hub's highest rail while worn is VSYS at 4.4 V, which gives a 22 µA worst single fault (check faults). The DC limit used is 50 µA single fault for type BF, from IEC 60601-1 table 3; verify against the standard. The tile check uses 3.3 V.
  • Radio near the head. Point the antenna away from the scalp, at the board edge. Cap the ESP32 TX power at about 13 dBm until SAR is assessed.
  • Battery. Use an IEC 62133-2 cell with its own PCM, plus the hub's DW01A protection, the BQ25895 BATFET and NTC (JEITA via the TS divider), and ship mode via /QON. Applied-part and enclosure temperature limits (≈ 41–43 °C) need the built helmet.

Power budget

load current power basis
16 tiles at 36 mA (tile LDO: I_in = I_out) 0.58 A at 3.6 V 2.08 W docs/pcb.md per-tile budget
16 × TPS2553 quiescent 1.9 mA 7 mW datasheet
2 × RP2350B at 144 MHz 60 mA at 3.3 V estimate
FT2232H, USB HS active 70 mA datasheet class value (≈ 0 when unplugged, held in reset)
ESP32-S3 streaming over Wi-Fi (average / peak) 180 / 350 mA estimate (TX bursts)
flash, clock tree driving 16 FPC lines, LEDs, op-amps 28 mA 60 pF × 3.3 V × 2.048 MHz × 16 = 6.5 mA for the clock lines
hub logic 0.34 A 1.12 W
from the battery (η = 0.90) 0.96 A at 3.7 V, 1.39 A peak at 3.0 V 3.55 W

Check results (hub_check, power): - The +3V6 rail runs at 29 % of the TPS63020's 2 A boost rating, or 0.73 A with one port shorted at its limit. The +3V3 rail peaks at 25 %. - Battery: 4000 mAh, 13.3 Wh usable, which gives ≈ 3.8 h at 3.55 W. - The peak battery current of 1.39 A is 35 % of a 1C rating, and the DW01A trip (≈ 3.75 A) is 2.7 × that peak. - Charge time is about 3.2 h at 1.5 A. - WARN: all 16 ports at their limit at once (2.4 A) would exceed the rail, but each switch latches off, so it can't persist. - Turning the radio off (USB on the bench) saves about 0.6 W.

Data budget

value
EEG payload 960 ch × 500 SPS × 24 bit = 11.52 Mb/s
tile frame per sample 180 B EEG + 24 B optics (4 × dx, dy, quality, shutter) + 32 B header + 4 B CRC32 + 2 B COBS = 242 B
per tile 121 kB/s → 1.21 Mbaud of 3 Mbaud on the line (8N1): 40 % used, margin 2.5×
aggregate 1.94 MB/s = 15.5 Mb/s
per RP2350 → FT2232H async FIFO 0.97 MB/s vs 8 MB/s: margin 8.3×
USB 2.0 HS (≈ 35 MB/s bulk in practice) margin 18×
RP2350 → ESP32 SPI at 24 MHz 3 MB/s vs 0.97 MB/s: margin 3.1×
Wi-Fi, raw 15.5 Mb/s vs 20 Mb/s in-air TCP: margin 1.3×, not enough
Wi-Fi, lossless delta + Rice (assumed 0.55: ~220 LSB of noise in 24 bits) 8.5 Mb/s: margin 2.35× TCP / 3.5× UDP

USB FS (an RP2350 on its own) would be 12 Mb/s nominal against 15.5 Mb/s needed, which is why the hub uses the FT2232H. The Wi-Fi compression ratio is an assumption; measure it on real recordings. The fallback is decimating to 250 SPS for the radio link only.

Clock skew budget

term ns basis
LMK1C1108 part-to-part + output-to-output 0.28 + 0.05 datasheet max
hub trace mismatch, 20 mm budget (route to 5 mm) 0.13 6.5 ps/mm
FPC length mismatch, 0.08–0.35 m 1.57 5.8 ns/m
tile 74LVC1G17, tpd 1.0–5.0 ns 4.0 datasheet, 3.3 V
tile-to-tile sample skew 6.0 target tCLKIN/10 = 48.8 ns; 3.8 µrad at 100 Hz

SYNC capture: CLK and SYNC share each cable, so cable length cancels. SYNC changes between 19.6 ns before and 51.0 ns after the falling CLK edge at the converter. That covers PIO launch latency (0–28 ns), the LVC244 vs LMK delay, the tile buffer spread, edge time (33 Ω × 60 pF) and an assumed 10 ns ADC setup. The result is 193 ns from any rising edge, 79 % of the half period. If the ADS131M08 samples SYNC on the falling edge instead, the PIO launches on the rising edge (firmware).

BOM (one hub, JLC/LCSC prices @100, verified 2026-10-27)

part qty LCSC unit $ ext $
FT2232HL-REEL (USB HS) 1 C27882 7.80 7.80
LMK1C1108PWR (1:8 clock buffer) 2 C2868241 2.11 4.22
TPS2553DBVR-1 (port switch) 16 C111738 0.24 3.84
ESP32-S3-MINI-1-N8 1 C2913206 3.37 3.37
RP2350B 2 C42415655 1.14 2.28
W25Q16JVSSIQ 2 C131025 0.84 1.68
MAX17048G+T10 1 C2682616 1.50 1.50
HC-FPC-05-10-12RLTAG (ports) 16 C5213732 0.089 1.42
BQ25895RTWR 1 C80200 1.38 1.38
TPS63020DSJR 2 C15483 0.59 1.18
TLV333IDCKR 3 C181502 0.24 0.72
SN74LVC244APWR 2 C7668 0.20 0.40
OT32252.048MJBA4SL (2.048 MHz) 1 C2831386 0.32 0.32
93LC56BT-I/OT 1 C190271 0.28 0.28
JST S3B-PH-SM4-TB / SM04B-SRSS-TB 1 + 1 C265101 / C160404 0.21 / 0.17 0.38
X322512MSB4SI 12 MHz (basic) 3 C9002 0.067 0.20
WPN4020H1R5MT / FXL0530-2R2-M / MPL2016S3R3MHT 2 / 1 / 2 C98360 / C177247 / C98336 0.44
TPS7A2033PDBVR, TYPE-C-31-M-12, USBLC6-2SC6, SN74LVC1G17DCKR 1 each C2862740, C165948, C7519, C10425 0.45
DW01A, 8205A, 2N7002 (basic), TS-1187A (basic), KT-0603G × 4, BLM15PD121SN1D × 2, SMT nut × 2 C351410, C5148694, C8545, C318884, C12624, C76891, C2928168 0.43
passives (generic 0402/0603/0805: 172 R, 99 C) 271 JLC basic ≈ 1.1
parts per board 360 placements, 68 lines ≈ $33.3

Plus, per order: about 29 extended part types at JLC's ~$3 loading fee each (≈ $87 per order, not per board), and a 4-layer 72 × 56 mm board. 4 layers are enough: signal / GND / power / signal, with a 90 Ω differential pair for USB. Only the 12 MHz crystal, the 2N7002 and the switch are basic parts. Full list: out/pcb/hub/hub-bom-jlc.csv.

Proposed board size and connector placement

72 × 56 mm, 4-layer, 1.0 mm. It sits at the back of the head on the midline, just above the inion, in a pocket in the v1 shell. The battery (a ~4000 mAh pouch, ≈ 60 × 50 × 8 mm, ≈ 75 g) goes in its own pocket below it at the nape, which keeps it off the crown and balances the frontal tiles.

                        crown (most tiles)
          ┌──[P1]─[P2]─[P3]─[P4]─[P5]─[P6]──┐     top edge: 6 FPC at 11 mm pitch
          │                                 │
   [P7]   │   RP2350 A      FT2232H         │  [P11]
   [P8]   │                                 │  [P12]    side edges: 4 FPC each,
   [P9]   │   RP2350 B      clock/SYNC      │  [P13]    toward the lateral tiles
   [P10]  │                                 │  [P14]
          │   power, charger    ESP32 ant.→ │
          └──[P15]─[P16]──[USB-C]──[BAT]────┘     bottom edge: 2 FPC, USB-C, battery JST
                        nape (battery pocket below)
   scalp face: REF + BIAS ball-pin nuts, analog section next to them; FPC connectors face the shell
   outer face: MCUs, FT2232H, power, ESP32 (antenna in the bottom-right corner, overhanging copper-free)
  • USB-C sits at the bottom edge, reachable through the shell's pocket opening. It is used only when the helmet is off the head (interlock).
  • FPC lengths. From the occiput, the farthest frontal tile is about 35 cm away. Flip-lock 0.5 mm FPC jumpers run over the tile backs inside the shell, and the skew budget above assumes 0.08–0.35 m.
  • Pin reach. For the REF/BIAS ball-pins to reach the scalp, the pocket must hold the hub's scalp face at about the contact board's offset (the 7.5–12.5 mm EXO-5LLL range). If it can't, use the JST-SH leads (mastoids).

Open items

  • USB while worn. Decide between an external 2 MOPP USB HS isolator and Wi-Fi only. Until then the hardware interlock forbids measuring on USB power.
  • Land patterns. Draw RP2350B (QFN-80), FT2232H (LQFP-64), ESP32-S3-MINI-1, TPS63020 DSJ and BQ25895 RTW, and map the RP2350B / FT2232H / ESP32 pin names to pad numbers. They are by name in hub.py for now. Also verify the pinouts flagged "verify": DW01A, the HXY 8205A, 93LC56B OT, MAX17048, and pad 1 = OE on the YXC oscillator.
  • Swapped parts to re-check.
  • The Sunlord MPL2016 is a multilayer part in place of Raspberry Pi's shielded AOTA-B201610S3R3 (0 stock) for the RP2350 VREG. Check its EMI and orientation.
  • TLV333 for MCP6V31: 100× more current noise × 200 kΩ is still negligible at REF.
  • ESP32 strapping/IO. Check the ESP32-S3-MINI-1 IO33–37 availability on the N8 variant (vs octal-PSRAM variants) and the strapping pins (IO0, IO3, IO45, IO46 left alone).
  • DRL tuning. Tune the loop (390 kΩ / 1 nF) on a head: stability with the electrode and cable capacitance, and mains rejection. Also check the 125 Hz contact-quality tone amplitude against the tiles' input capacitance.
  • Measure:
  • Wi-Fi throughput in the helmet next to a head.
  • The lossless compression ratio on real EEG.
  • RP2350 PIO RX error rate at 3 Mbaud over 35 cm of FPC.
  • Clock edges and ringing on the FPC (33 Ω may need to grow) and EMC from 16 × 2.048 MHz lines.
  • Per-port ESD. None is fitted: the FPCs are inside the shell. Add TPD4E05-class parts if the cables become user-accessible.
  • FPC cable type. Choose contact type A or B at layout so that pin n → pin n, given each connector's orientation.
  • Tile-side note (for the tile owner, not changed here). circuit.py gives SN74LVC1G17DCKR as LCSC C7836, which is the SOT-23-5 (DBV) part. The SC70 part is C10425.