Exocortex Labs

Contact boards and pins

48 flat boards cover 89.5% of the scalp with 1,273 contacts. 16 are main boards, each stacked on its own controller; 32 are fillers in the gaps between them. No two have the same shape. Every board is a real 6-layer, 1.0 mm board: placed, routed and checked, with Gerbers, a BOM and a pick-and-place file for JLCPCB (ordering).

uv run python -m exo.poly_layout                  # the shapes        -> out/poly_layout.json
uv run python -m exo.pcb.poly                     # circuits, placement -> out/pcb/poly/placement.json, boards.json
PCB_OUT=out/pcb/poly ROUTE_OFF=1 node pcb/tile.mjs                         # build each board (tscircuit)
PCB_OUT=out/pcb/poly uv run python -m exo.pcb.route_all                    # Freerouting, every board, in parallel
PCB_OUT=out/pcb/poly uv run python -m exo.pcb.silk                        # silkscreen, then each board's Gerbers again
PCB_OUT=out/pcb/poly uv run python -m exo.pcb.fab_poly                     # checks, Gerbers zip, BOM, CPL
PCB_OUT=out/pcb/poly uv run python -m exo.pcb.panel                        # the JLC panels

One pin

Every contact is the same off-the-shelf part: the Mill-Max 0919-0-15-20-89-14-11-0, a surface-mount spring pin with a rounded gold-plated plunger. It is 9.63 mm tall free, has 3.0 mm of stroke to a hard stop at 6.63 mm, and pushes with 25 g at the start of its travel, 85 g half-way and 127 g at 2.54 mm. It is reflowed onto a Ø2.2 mm pad like any other SMD part.

The pin is on its stop at 6.63 mm from board face to scalp and still preloaded by 0.4 mm at 9.23 mm. So on every board the face-to-scalp distance under every contact has to stay inside 6.63–9.23 mm: a 2.6 mm span. That single rule sets the size of the boards. In the layout it reaches 6.6–9.2 mm.

Growing the boards

The scalp is the head model's ellipsoid (exo/head.py); each board sits on its own tangent frame with its contact face set so the shortest gap is 6.63 mm.

  • Main boards. Sixteen start as small squares at the 16 positions of an even tiling of the head and grow one edge at a time, each step allowed to turn the edge by up to ±4°, so they become trapezoids where that buys area. A step is kept only if every contact stays inside the pin's range, the board stack clears the shell's inner face, the corners stay above the beanie's cuff line, and nothing comes within 1 mm (stacks) or 0.6 mm (pins) of a neighbour in 3D.
  • Fillers. Then, repeatedly, the scalp point farthest from any board seeds a triangle or a trapezoid on its own frame, grown under the same rules; the one with more contacts is kept, if it is at least 280 mm² (smaller, a board can't carry its own converter, link connector and holes). Seeding stops when no gap is wider than 4.5 mm or holds fewer than four contacts.

Contacts sit on a 5.3 mm grid (offset and rotation searched per board) inset 2.4 mm from each edge. A main board gives up the few contacts under its corner sensors; a small filler with one contact over a multiple of eight gives up the one nearest its edge rather than carry a second converter for it. Each board's own x/y axes run along its grid, so every part sits at 0° or 90° (the converters at 45°): the router works in 45° steps.

On each board

per contact per board
spring pin on a Ø2.2 mm pad, scalp face 1
2 × 100k protection (scalp face, next to the pin) 2
TLV333 follower, 1 µF coupling, 2.2M return (back, behind the pin) 3
ADS131M08, 24-bit, 8 channels, at 45° in the grid cell nearest its eight one per eight contacts
reference chain (2 × TLV333, 100k + 100k, 1 µF, 2.2M), analog LDO, CLK and SYNC Schmitt buffers 1 set
main board: 4 × PAT9125 optical sensors in the corners, 4 × DF40 receptacles to its controller
filler: a digital LDO, a 74AHC138 for its chip selects (when it has two or more converters), a 12-way 0.5 mm FPC to its controller (16-way on the four fillers with five converters)
3 × M2 holes to the shell's standoffs

Every contact sees two 100k resistors before any silicon, so a fault anywhere on a board can drive at most 3.3 V / 200 kΩ = 16.5 µA into the scalp. exo.pcb.fab_poly walks every contact to its first semiconductor and fails the build if that is ever less.

The follower is TI's TLV333 (pin-for-pin with the MCP6V31U, which is not stocked at LCSC). Its 100 fA/√Hz input current noise across a 1 MΩ dry contact is 100 nV/√Hz, below the contact's own thermal noise (129 nV/√Hz at 1 MΩ); over 0.5–100 Hz the channel is then about 1.9 µVrms with the converter's 1.0 µVrms.

Each main board's controller is cut to the same outline and sits 1.5 mm behind it on the four DF40 pairs; its face toward the contact board carries nothing but the DF40 headers, everything else is on its outer face: an RP2040, 2 MB flash, a 12 MHz crystal, its LDO, a 12-way FPC to the hub and one 16-way FPC per filler it reads (four at most; each filler goes to the nearest main board with a free port).

The RP2040 reads its own board's converters on SPI0 (one chip select each, DRDY from the first) and its fillers on SPI1, shared with the optical sensors. A filler's 74LVC138 turns three address lines (FA0–FA2) and that filler's strobe (FCSn) into its converters' chip selects. CLK and SYNC from the hub are re-buffered on the controller for its fillers, so every converter on the helmet runs off one 2.048 MHz clock and starts on one SYNC.

16-way filler link pins
VIN (3.6 V from the hub port) 1, 2
GND 3, 5, 7, 9, tabs
REF, CLK, SYNC 4, 6, 8
SPI SCK, MOSI, MISO 10, 11, 12
FA0, FA1, FA2, FCS 13, 14, 15, 16

Routing

The circuits and placement are exo/pcb/poly.py; tscircuit builds each board from that placement (every land pattern is given explicitly, no library lookups). Freerouting routes it first (exo/pcb/route.py): top, inner 2, inner 3 and bottom are signal layers, inner 1 is a solid ground plane and inner 4 the board's supply. Ground and supply pads wide enough take a filled, capped via in the pad; narrower ones (QFN and connector pins) get a short stub to a via just past the pad before routing starts.

Freerouting always stops a few connections short on these dense boards, so exo/pcb/repair.py finishes each one: it removes any copper that breaks clearance, then routes every connection still open on a 0.05 mm grid (A*, 45° moves, through vias), ripping up and rerouting another net's wire where that is the only way through. Every new path is checked against the real copper geometry, not just the grid. Rules throughout: 0.09 mm track and space (JLCPCB's 6-layer minimum), 0.3/0.2 mm vias, copper 0.3 mm from the edge.

After routing, every pour is recomputed around the copper and the board is checked on its own: every net one connected piece of copper, nothing of two nets closer than 0.09 mm, and every contact behind 200 kΩ (exo.pcb.fab_poly).

Silkscreen (exo.pcb.silk). Every board gets its name, and "EXOCORTEX LABS" where it fits (40 of 64). There's a pin-1 dot beside each IC with room for one, and at each cable connector, the board its cable goes to (HUB for a controller's J100). It uses one single-stroke font: 1.0 mm text (1.2 mm for the name) at 0.15 mm, JLC's recommended size. Every mark sits 0.2 mm off every pad, via, hole and part outline and 0.5 mm in from the edge. Bottom-face text is mirrored, so it reads from below. The paths are written into each board's circuit.json, so the Gerbers, the panels and the 3D view all draw the same strokes; the boards with no room for a mark are listed in out/pcb/poly/silk.json. The exporter runs with node --stack-size=60000: without it, the biggest pours overflow the stack. Eight boards' own Gerber zips (T03, T05, T06, T09, T13–T16) had been written without their pours that way; the panels, which are what gets ordered, never were.

board kind parts traces vias routed faults ≤ 16.5 µA
F01 contact 245 1950 390 yes yes
F02 contact 269 2643 424 yes yes
F03 contact 275 2188 448 yes yes
F04 contact 245 1807 393 yes yes
F05 contact 216 1679 329 yes yes
F06 contact 228 1779 367 yes yes
F07 contact 222 1790 338 yes yes
F08 contact 181 1400 277 yes yes
F09 contact 175 1381 290 yes yes
F10 contact 139 1163 255 yes yes
F11 contact 110 992 182 yes yes
F12 contact 116 943 211 yes yes
F13 contact 151 1219 262 yes yes
F14 contact 128 1135 217 yes yes
F15 contact 139 1141 244 yes yes
F16 contact 128 1018 232 yes yes
F17 contact 116 924 204 yes yes
F18 contact 145 1199 233 yes yes
F19 contact 122 970 216 yes yes
F20 contact 104 756 175 yes yes
F21 contact 116 931 199 yes yes
F22 contact 110 1396 208 yes yes
F23 contact 104 1289 215 yes yes
F24 contact 73 431 122 yes yes
F25 contact 73 451 132 yes yes
F26 contact 73 610 130 yes yes
F27 contact 61 523 105 yes yes
F28 contact 66 572 122 yes yes
F29 contact 66 495 123 yes yes
F30 contact 66 472 117 yes yes
F31 contact 98 645 186 yes yes
F32 contact 72 426 128 yes yes
T01 contact 264 2319 448 yes yes
T02 contact 323 3266 535 yes yes
T03 contact 341 3085 565 yes yes
T04 contact 264 2377 485 yes yes
T05 contact 329 2697 537 yes yes
T06 contact 323 2894 554 yes yes
T07 contact 276 2501 437 yes yes
T08 contact 317 2659 521 yes yes
T09 contact 400 3788 624 yes yes
T10 contact 417 4548 680 yes yes
T11 contact 329 2722 525 yes yes
T12 contact 276 2626 437 yes yes
T13 contact 329 2712 548 yes yes
T14 contact 417 3983 656 yes yes
T15 contact 417 4341 717 yes yes
T16 contact 335 2668 546 yes yes
T01K controller 44 1158 277 yes yes
T02K controller 43 324 248 yes yes
T03K controller 42 305 235 yes yes
T04K controller 44 930 249 yes yes
T05K controller 42 301 239 yes yes
T06K controller 41 254 208 yes yes
T07K controller 43 737 223 yes yes
T08K controller 42 672 236 yes yes
T09K controller 34 217 184 yes yes
T10K controller 41 265 215 yes yes
T11K controller 42 287 228 yes yes
T12K controller 43 744 229 yes yes
T13K controller 41 270 219 yes yes
T14K controller 42 302 244 yes yes
T15K controller 34 219 181 yes yes
T16K controller 42 294 228 yes yes