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.fab_poly # checks, Gerbers zip, BOM, CPL
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.
Controllers and links
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).
| 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 |