glauber/sois-controller-rev-c

Source code for the SOIS Controller PCB, featuring an Adafruit Feather microcontroller, two EC11 rotary encoders, multiple push buttons and a slide switch. View more at glauber.org

Version
0.1.3
License
UNLICENSED
Stars
0

Files

README.md

# sois controller β€” rev C

The carrier PCB for the **sois controller**: a handheld box of knobs and
buttons for composing variations of [sois](https://glauber.org/archive/sois), a
generative artwork that runs continuously on a small wall-mounted display.

- The artwork β€” <https://glauber.org/archive/sois>
- The controller β€” <https://glauber.org/archive/sois-controller>

The controller is held in both hands, about 16 Γ— 7 cm, and drives a 720 Γ— 720
non-touch screen. Physical controls are the point of it: they keep the artwork's
interface minimal, because not everything has to be on the screen at once.

## The board

154 Γ— 64 mm, 2 layers, black mask / white silk. Everything on it is one idea β€”
each input shorts a GPIO to ground through a contact and is read
`INPUT_PULLUP` β€” so there is not a single passive component.

| | |
| --- | --- |
| **U1** | Adafruit Feather ESP32-S3, soldered flush (not socketed) |
| **K1, K2** | Alps EC11E15244G1 click-encoders on a 50 mm pitch, carrying Ø48.2 mm caps |
| **SW1–SW4** | 12 mm tactile switches in a 2Γ—2 block, 24.1 mm caps |
| **SW5** | C&K OS102011MA1QS1 right-angle slide, pulling the Feather's `EN` low |

The layout is built around the **caps**, not the parts: the two Ø48.2 knob caps
and the 2Γ—2 button block are the fixed geometry, and everything else is placed
to serve them.

## Provenance

This is a **tscircuit port of a KiCad board**. The original β€”
[`../controller-rev-c/`](../controller-rev-c/), generated by a Python script β€”
is the one that was fabricated, assembled, and passed bring-up on 2026-08-21;
it remains the source of truth. This package was written to see how tscircuit
handles a board whose correct answer is already known.

The port was verified against the KiCad board by matching every drilled hole
in both files: **all 70 agree to 0.0000 mm**, and no routed copper enters the
keep-out under the Feather's PCB antenna.

A few things could not be reproduced exactly. Pads are centred on their holes
rather than offset the 0.3 mm the Feather library uses; the encoders' frame
tabs are pills rather than rectangular pads over slots; GND is both poured and
routed, where the KiCad board only pours it; and the silkscreen wordmarks are
scanline-filled, since tscircuit has no filled polygon on that layer.

## Using it

`tsci` and `bun` are local dependencies here rather than global installs, so
commands run through the `./tsci` wrapper from this directory.

```bash
npm install
./tsci build --pcb-png --3d-png --schematic-png   # β†’ dist/index/
./tsci check netlist
./tsci check shorts        # run this: build alone does not catch shorts
./tsci export index.circuit.tsx -f gerbers -o dist/gerbers.zip
```

`./tsci check placement` reports three errors on this board. They are false β€”
the Ø6.2 mm mounting-hole rings are tested as bounding boxes against a rounded
outline. The rings themselves clear it by 0.607 mm, and KiCad's DRC passes the
same geometry 0/0/0.

## Layout

| file | what |
| --- | --- |
| `index.circuit.tsx` | the board: placement, nets, silkscreen, pour, keep-out |
| `src/geometry.ts` | the fixed dimensions, and the KiCad ↔ tscircuit frame conversion |
| `src/AdafruitFeather.tsx` | Feather footprint, from KiCad's `Module:Adafruit_Feather_WithMountingHoles` |
| `src/EC11Encoder.tsx` | Alps EC11, from `Rotary_Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm` |
| `src/PushButton12mm.tsx` | 12 mm tactile, from `Button_Switch_THT:SW_PUSH-12mm` |
| `src/SlideSwitchCK.tsx` | C&K slide β€” no library part exists; drawn from vendor CAD, slots and all |
| `src/MountingHoleM3.tsx` | Ø3.2 drill in a Ø6.2 plated ring |
| `src/silkArt.tsx` | the silkscreen, from the same `silk.json` the KiCad generator uses |
| `src/models.ts` | where the 3D models are fetched from, and why it has to be an absolute url |
| `models/` | symlinks to the four STEP models |

**Coordinates.** The KiCad generator works with the origin at the board's
top-left and **+y down**; tscircuit puts the board's centre at the origin with
**+y up**. `bx`/`by` in `src/geometry.ts` convert, and footprint-local y is
negated from each `.kicad_mod`. Rotations survive the flip unchanged, which is
why the Feather is placed at the same `pcbRotation={90}` KiCad uses.

**3D models.** `models/` and `src/silk.json` are symlinks to the real files
elsewhere in the repo; `tsci push` follows them, so the published package
carries its own models. They are referenced by absolute registry url
(`src/models.ts`) because that is the only form that resolves on tscircuit.com,
in `tsci dev` and in `tsci build` alike. None of KiCad's model transforms carry
over; the offsets here are tuned for the browser viewer.

### Where the models come from

`feather-5323.step` is Adafruit's own model for the Feather ESP32-S3 (5323),
`sw_push_12mm_wuerth.step` is KiCad's WΓΌrth 430476085716,
`ck_os102011_switch.step` is C&K's via LCSC/EasyEDA, and `ec11_encoder.step` is
generated by `../controller-rev-c/3dmodels/make_models.py` from the Alps EC11
catalogue drawing.