exocortex

Tile electronics: EXO-410 contact board + EXO-420 controller board

The functional tile (v1). The decorative v0 tile (EXO-2xx, SLA with snap cups) is unchanged; this replaces it when the helmet has to measure something. Source: exo/pcb/ (geometry, circuit as code, placement, checks) and pcb/tile.mjs (tscircuit: build, autoroute, Gerbers). Generated outputs: out/pcb/.

Status: designed and generated, not fabricated, not powered, nothing measured. Every number below is a design value from a datasheet or a calculation, and says which.

What changed from v0

v0 (visual) v1 (this)
tile curved SLA carrier, 64 snap cups two flat 6-layer PCBs stacked, identical on all 16 tiles
contact 3 mm brass ball snapped in a cup turned, gold-plated ball-pin with a spring plunger, screwed into an M1.6 SMT nut reflowed on the board
contacts per tile 64 60 EEG + 4 laser optical encoders (PAT9125EL, VCSEL) at the corners, in place of the corner pins
helmet 1,024 decorative 960 EEG channels + 64 laser optical encoders
pin length one six lengths, 7.5–12.5 mm in 1 mm steps (190 / 334 / 284 / 113 / 35 / 4 per helmet): the flat board is 6.4 mm farther from the scalp at its corners than at its centre
shell inner face 10.2 mm off the scalp 19.8 mm + the controller board behind it (≈ 22.5 mm). The flat boards cost ≈ 12 mm of helmet thickness

One design for all 16 tiles. The layout builds every tile frame so the contacts lie on the same regular grid in the tile's own x/y, so the PCBs are identical. Only the pin lengths differ, and the shell's standoffs carry each board at its tile's position.

Architecture

 scalp
   │  ball-pin (spring plunger, Ø3 gold ball) ── M1.6 SMT nut (F, scalp side of EXO-410)
   │  100 kΩ ── 100 kΩ  (F, between the nuts)               single fault: 3.3 V / 200 kΩ = 16.5 µA
   │  ── via ── MCP6V31 follower (B, behind the pin)         active electrode: the high-Z node is ~2 mm long
   │  1 µF ── AIN ── 2.2 MΩ to GND                          AC coupling: dry-contact DC offsets never reach the PGA
   │  ADS131M08 × 8 (B): gain 32, OSR 2048, 2.048 MHz CLKIN → 500 SPS, AINxN = buffered, high-passed REF
   │  4 corners (F): PAT9125EL laser optical encoder (lateral slip; lift from its image-quality registers)
   │                       │ 4 × 10-pin 0.4 mm DF40 pairs, one per corner (the only nut-hole-free spots on the back)
   └─ EXO-420 controller: RP2040 + 16 Mbit flash + 12 MHz crystal, digital LDO, 12-pin FPC to the hub
                           FPC: 3.6 V, GND, REF (driven), 2.048 MHz CLK, SYNC, UART 3 Mbaud TX/RX
 hub (one per helmet, next document): driven-right-leg + reference contact, the clock and SYNC for every converter,
                           16 UART receivers, battery, host link (exocortex-2026-05-A head-bridge protocol)

Why a buffer behind every pin. A dry contact is 100 kΩ–1 MΩ+. The ADS131M08's input impedance is 1 MΩ at gain ≥ 8 (datasheet), so without a buffer every contact would be a variable voltage divider and the common-mode rejection would follow the contact quality. The ADS1299 avoids this with ~1 GΩ inputs. A first-principles chain buys the same thing with a 21 µA zero-drift follower per pin, and gets an active electrode in the bargain: the high-impedance trace is the 2 mm from the nut to the follower, not a wire across the board.

Why AC coupling. At gain 32 the ADS131M08's range is ±37.5 mV. Dry contacts sit at offsets of hundreds of mV that drift with sweat and pressure. A 1 µF / 2.2 MΩ high-pass (0.07 Hz; ≈ 0.23 Hz with the converter's input impedance in parallel) removes them, and the AINxN side gets the same high-passed reference through its own follower, so each channel is HP(electrode − REF).

Why two boards. A follower, coupling capacitor and bleed resistor per pin, plus 8 converters, fill the contact board's back (the placer reports 63 % courtyard fill with 0201 passives, 58 % on the front). The MCU, flash, crystal, digital LDO and FPC do not fit as well. Splitting gives an analog-only board facing the scalp, shielded by its ground planes from a digital board behind it. The link is four 10-pin DF40 pairs, one per corner: with a Ø2.5 pilot hole at every pin, the corners are the only places on the back with room for a connector, and four pairs also hold the stack.

What came from CommandAGI-EEG-001 (CommandAGI/commandagi, seed/drive/platform/user-commandagi/hardware/CommandAGI-EEG-001): - 2 × 100 kΩ 0.1 % in series on every signal and reference contact (theirs: ee/bank.circuit.ts). - One shared converter clock (2.048 MHz, the same frequency) and one SYNC for every converter on the head. - One bias drive and one reference contact per head, not per tile. - The data frame (32-byte header with EEG! magic, sequence, µs timestamp, flags; CRC32; latched fault causes), the 128 ms bounded queue, and "overflow is a fault, never a silent drop". - The faults check (walk every contact to its first semiconductor pin and fail the build if the series resistance is short of the table), ported to exo/pcb/check.py. - What not to copy: its leakage argument compares a DC current with the 100 µA AC limit. The DC limits for a type BF applied part are lower (from memory of IEC 60601-1 table 3: 10 µA normal, 50 µA single fault; verify against the standard). Here the single-fault bound is 3.3 V / 200 kΩ = 16.5 µA.

Noise budget (0.5–100 Hz, per channel)

source density in-band rms basis
dry contact, 1 MΩ (thermal) 129 nV/√Hz 1.29 µV √(4kTR), R typical for dry gold on hair-covered scalp; the contact dominates
2 × 100 kΩ protection 57 nV/√Hz 0.57 µV √(4kTR)
MCP6V31 voltage noise ~50 nV/√Hz 0.50 µV datasheet class value (confirm: datasheet agent)
MCP6V31 current noise × 1 MΩ small (chopper ~fA/√Hz) < 0.1 µV confirm
ADS131M08, gain 32, OSR 2048 — 1.00 µV over its 250 Hz band, ≈ 0.63 µV in 100 Hz datasheet table 7-1
total ≈ 1.6 µVrms the same chain with an ADS1299 would be ≈ 1.4 µVrms: the contact sets the floor either way

Power budget (per tile)

current at 3.3 V basis
8 × ADS131M08, very-low-power mode 8 × (1.9 + 0.18) mA = 16.6 mA datasheet: noise depends only on OSR and gain, not on the power mode
62 × MCP6V31 1.3 mA 21 µA each
RP2040 at 48 MHz ≈ 15 mA typical
4 × PAT9125EL, run mode 4 × 0.7 mA datasheet v1.2
tile ≈ 36 mA ≈ 120 mW 16 tiles ≈ 1.9 W plus the hub

The same 960 channels on ADS1299s would draw ≈ 5 W for the converters alone. 2.4 W spread over the head is warm, not hot; the thermal check needs the built helmet.

Optical corners: what they can and cannot do

Each corner has a PAT9125EL: a laser (850 nm VCSEL) optical encoder with no lens, 1–30 mm working distance, 0.7 mA. It reports lateral slip (Δx, Δy) of whatever surface it sees, and its image-quality / shutter registers drop when the surface lifts away, a distance proxy. Four per tile give reference signals for adaptive motion-artifact cancellation (the "closed loop" is a filter, not an actuator). A separate ToF/proximity part was dropped: the corner has room for one sensor, and the VCNL36826S is marked not for new designs.

  • Through hair they track the hair, not the scalp. They work where the corner sits over skin or thin hair; elsewhere they report hair motion, which still correlates with cap slip. Nothing here is proven: bench it on a head first.
  • Skin oils change the contact's electrical impedance, which optics cannot see. The right sensor for that is the contact itself: inject a small known current at a frequency outside the EEG band through the reference/bias path and measure each channel's response (the ADS131M08 has no built-in lead-off; the hub's bias driver injects, the tiles measure). That is the oil/contact-quality compensation; the optics handle motion.

Pins and nuts

  • Nut: M1.6 SMT nut, hex 3.18 A/F, 1.5 mm high (Sinhoo SMTSO1615MTJ, LCSC C2928168; PEM SMTSO-M1.6 class), Ø4.2 pad, Ø2.5 unplated pilot hole through the board (PEM drawing). These 60 holes are why the back is laid out in 45° cells.
  • Pin (EXO-5LLL, CAD in exo/pcb/pin.py, STEP in out/pcb/pins/): M1.6 × 0.35 thread into the nut, 2.5 A/F hex collar, Ø2.0 barrel (≥ 2.35 mm for the spring), Ø1.3 plunger with 1.5 mm travel, Ø3.0 ball head; brass, gold over nickel. Six lengths, 7.5–12.5 mm. A custom part from a pogo-pin or turned-part shop (JLCCNC does not list brass turning; quote PCBWay CNC and pogo-pin makers).

Toolchain

Circuit, placement and checks are Python (exo/pcb/); the boards are built and exported with tscircuit in plain Node (pcb/tile.mjs, no KiCad install). EXO-420 is autorouted there. EXO-410 is not: tscircuit's autorouter runs out of iterations on its 1,295 pads at every effort level, even with GND and the supply left out, so tile.mjs builds it with routing off and exo/pcb/route.py routes it with Freerouting (local, headless): placement → Specctra DSN → SES → traces and vias back into circuit.json, pours recomputed around them, then its own DRC (every net one piece of copper, no two nets closer than the clearance) and the Gerbers through pcb/export.mjs. GND and supply pads that can take one get a filled, capped via in the pad (164 of them), so they reach their plane without a fanout. exo/pcb/check.py runs the faults walk, the cross-board connector check, reads both boards' route.json and writes the JLCPCB BOM/CPL; exo/pcb/render.py draws the routed copper and checks that the stacked connectors' pads coincide.

uv run python -m exo.pcb.geom && uv run python -m exo.pcb.circuit && uv run python -m exo.pcb.footprints
uv run python -m exo.pcb.place          # ~1.5 min
cd pcb && npm install --allow-git=all --allow-remote=all && cd ..   # npm 12 blocks tscircuit's git/tarball deps
node pcb/tile.mjs                       # EXO-420 build + autoroute (~1 min), EXO-410 build (~3 min)
uv run python -m exo.pcb.route EXO-410  # Freerouting: hours; needs Java 25 + freerouting-2.4.1.jar (JAVA, FREEROUTING)
uv run python -m exo.pcb.render && uv run python -m exo.pcb.check

route.py reads its rules from the environment: TRACE, CLEAR (mm), VIA (pad/drill), SIG4=1 (inner4 as a fifth signal layer with the supply as a fill), FANOUT, PASSES, FR_ARGS (passed to Freerouting). Freerouting's router uses one core, so variants run side by side.

Board stack-up (JLCPCB 6-layer, 1.0 mm, ENIG)

layer contact board controller board
L1 F.Cu nuts, protection resistors (0201), optics (faces the scalp) 4 corner DF40 headers
L2 GND plane (one solid ground) GND
L3 signal signal
L4 signal signal
L5 +3V3A plane +3V3D plane
L6 B.Cu followers, coupling (0201), converters at 45°, analog LDO, 4 corner DF40 receptacles; GND pour RP2040, flash, crystal, LDO, FPC, debug pads; GND pour

Rules: tscircuit's jlcpcb_standard preset; 0.1 mm default track. Plugged and capped vias in pads are free on 6 layers at JLCPCB.

Still open (needs a bench, not more design)

Contact impedance and noise on a real head; motion artefacts and whether the corner optics help; pin force and comfort (60 springs per tile); leakage and fault injection; clock skew across 16 tiles; thermal; EMC; the hub board.