
Front panel
TIGP-Experimental-Methods/class-board-2026
TIGP class instrument 2026: one board, five blocks - KiCad hardware, ESP32-S3 firmware, phone PWA, Python API, workbook

TIGP class instrument 2026: one board, five blocks - KiCad hardware, ESP32-S3 firmware, phone PWA, Python API, workbook
Class board V0.2 is an open-source printed circuit board by TIGP-Experimental-Methods, from the TIGP-Experimental-Methods/class-board-2026 repository on GitHub. It is a 4-layer board of 180 × 100 mm with 342 parts on 260 nets, routed with 2,823 tracks and 423 vias.
It is built around the 74HCT125PW,118-JSM, B0512S-2WR3 and OPA1612AIDRGR, with the ADS8688IDBTR, TS5A23157DGSR and 74HC74D,653-JSM among its other parts. Its parts include 126 resistors, 105 capacitors, 27 diodes, 13 ICs and 12 connectors.
The teaching instrument for Basic Skills for Experimentalists (TIGP, 2026): one ESP32-S3 board (the ESP32-S3 is the microcontroller we use — a small computer on a chip, with WiFi) with three student-designed sections, a phone web app, and a Python client. The board is a small NMR console: a 2 mT field, a bottle of water, a pulse at 89 kHz, and the free induction decay that comes back turned into a spectrum on your phone.
| Folder | What | Who edits it |
|---|---|---|
hardware/ | KiCad project and library (KiCad is the free program we draw the schematic and lay out the printed circuit board in; the library is the set of symbols and footprints for our parts) | students in their zone (the region of the board that is theirs), instructor merges (brings their changes into the main line) |
firmware/ | PlatformIO project (Arduino, ESP32-S3) — the firmware is the program that runs on the microcontroller; PlatformIO is the tool that builds it and writes it onto the board | students, in the driver folders their instrument needs, under src/blocks/ |
host/pwa/ | the phone app (plain HTML/JS, no build step) | students, in the panel files of those drivers, under panels/ |
host/instrument.py | Python command-line client (CLI) speaking the same protocol as the phone | instructor |
The three sections of the board (one design, made together in Workshop 2; each student added one section): A — inputs and the NMR receiver (the 8-channel 16-bit converter; the tuned coil, low-noise amplifier, blanking switch, I/Q mixer, IF filters and clock generator) · B — signal generation, timing and the NMR transmitter (±10 V outputs; the DDS synthesizer, reconstruction filter and power stage; TTL buffers, TRIG, the I²C expander) · C — the front panel board (the whole second board: the SMA field, the OLED, the LEDs, the TTL strip, the TX coil terminal and the Qwiic; the two isolated inputs; the module header with its 5 V buffer — seven outputs, MODULE OUT 1–7 plus a reserved spare pin, for driving opto-isolated modules outside the box). The instructor owns the base: the dev-board socket and the buses, the power entry and the rails, the external power input, the H-bridge and the polarizer switch.
Firmware drivers (the firmware calls each driver a block): base (dev board itself) · b1_inputs (ADS8688 8-ch ADC, analog-to-digital converter) · b2_power (rails) · b3_outputs (DAC8563 digital-to-analog converter + ±10 V) · b4_switching (7 module outputs, 2 isolated inputs) · b5_dio_trig (8 TTL out, 2 fast out, TRIG) · nmr (the console: pulse → free induction decay → spectrum). The drivers follow the sections: Section A b1_inputs and the receiver capture; Section B b3_outputs and b5_dio_trig; Section C b4_switching; b2_power is the instructor's. Protocol: firmware/PROTOCOL.md.
Until the class board arrives everything runs in SIM mode on a bare Jinhua ESP32-S3 N16R8 dev board (the development board: the microcontroller on a small board with a USB connector and pins): each driver fakes its hardware, so the app, the chart and the alarms all work from Workshop 1.
How this repository fits the course. Workshop 1: AI for experimentalists is Project 1 (a web app of your choice — a simulator or a game, built with the AI method) and Project 2 (your own phone app that controls the ESP32 over WiFi). The firmware and app in this repository are Step 1 of Project 2: every student flashes them onto their own board first — a known working hardware baseline, which separates hardware problems from software problems — then builds their own app in its own repository, reading this code for the access point, the WebSocket and the LED. They stay the fallback: flash them again if your own build stalls and you have a working app on the phone today. In Workshop 2: Designing printed circuit boards we design the class board together in KiCad — one board for everyone. They are also the base for Project 3 (from Workshop 3: Firmware and basic mechanical design (PCB housing)): your instrument — the firmware and the app that use the class board's capabilities, in a housing you design, presented on a great project website. You choose what instrument it is. The course ends with a project presentation and demonstration, 26–30 Oct.
| What | Where |
|---|---|
| Setup (before Workshop 1) | SETUP.md — paste the prompt from workbook ch. 0 into Claude Code and it installs the toolchain (the compiler and helper programs that turn source code into firmware) |
| Workbook — one chapter per workshop, each with a between workshops part ("Before the next workshop: complete the preparation, improve your apps, build and have fun."), the same text the tutor runs | workbook/: ch. 0 · ch. 1 · ch. 2 · ch. 3 · ch. 4 · A · B · C cheat-sheets |
| The section pages (documentation of the class board's hardware) | workbook/blocks/: A — inputs and the NMR receiver · B — signal generation, timing and the NMR transmitter · C — the front panel board |
| The tutor (self-updating: fetches the latest guide and workbook from GitHub at the start of every session) | /tutor L1 (etc.) in Claude Code — .claude/skills/tutor/, rules in tutor/COURSE-GUIDE.md |
| Slides | Workshop 1 as a PDF on the course site: https://tigp-experimental-methods.github.io/slides/W1-AI-for-experimentalists.pdf (Workshop 2 and 3 slides follow the same way; see slides/README.md) |
| Firmware + app (Step 1 of Project 2 — the hardware baseline — and its fallback; the base of your Project 3 instrument) | firmware/, host/ — flash it (write it onto the board over USB) in 5 commands below |
| The class project wall — one card per project (picture, title, student, two sentences, buttons Open the app · Project site · Video · Code), refreshed every minute, on the projector in class; every student pushes their own entries (workbook ch. 1, Put it on the class project wall). Project 3 — your instrument: its firmware, its app and the housing — is one repository, one project website and one card | https://tigp-experimental-methods.github.io/showcase-2026/ — repository https://github.com/TIGP-Experimental-Methods/showcase-2026 (its README documents the entry format) |
| KiCad library package | hardware/lib/ — project-local, nothing to unzip |
| Reference repositories for Project 1 | https://github.com/TIGP-Experimental-Methods/pendulum-example (cart-pole) · https://github.com/TIGP-Experimental-Methods/csv-scope-example (CSV → scope lab tool) — options, or a fork (your own copy of one of them on GitHub) if your session stalls |
| Course site | https://tigp-experimental-methods.github.io/ |
Prerequisites: VS Code with the PlatformIO IDE extension (installs its own Python and pio), git, the dev board on a USB-C data cable plugged into the port marked USB (native USB, not UART).
git clone https://github.com/TIGP-Experimental-Methods/class-board-2026.git
cd class-board-2026/firmware
pio run -e esp32s3-sim -t upload # 1st time: downloads the toolchain
pio run -e esp32s3-sim -t uploadfs # copies host/pwa/ to the board's LittleFS (its file store)
pio device monitor # watch it boot; note the AP name(In VS Code: PlatformIO sidebar → esp32s3-sim → Upload, then Upload Filesystem Image, then Monitor.)
If the upload cannot find a port: hold BOOT, tap RST, release BOOT, retry. Windows needs no driver for native USB.
If the upload says "No serial data received" (a factory-fresh board, USB ID 303A:4001): hold BOOT, tap RST, release BOOT — the board comes back as 303A:1001 on a new COM port; pio device list again and flash to that port. Once only; the port also disappears and reappears at every reset, which is normal. All flashing and serial symptoms, and the secrets rule, are on one page: workbook ch. C, §C.2.
AP "instrument-XXXX" password "instrument".instrument-XXXX, password instrument.http://; stay connected when the phone warns about no internet, mobile data off on Android). Tap Red on the Base tab: the LED changes. The chart plots base.counter; pick another key from the dropdown.To use the lab WiFi instead: copy firmware/include/secrets.h.example to secrets.h, fill in SSID/password, and, if you want OTA (the update over WiFi), remove the // before OTA_PASSWORD and type a password of your own (left commented out, OTA stays off) — type the passwords into that file yourself, never into a Claude chat; the file is git-ignored (git check-ignore -v firmware/include/secrets.h prints a line) — and re-flash. For uploads over WiFi, copy firmware/secrets.ini.example to firmware/secrets.ini (git-ignored too), type the board's name and the same OTA password there, and run pio run -d firmware -e esp32s3-ota -t upload. The LED turns green and the board is at http://instrument-XXXX.local (mDNS: the board announces its own name on the local network, so no IP address is needed; XXXX is the same suffix as its access-point name, so several boards can share one network). If joining fails within 10 s it falls back to the AP.
pip install -e host/ # or: uv pip install -e host/
instrument status
instrument send base led --args '{"r":0,"g":255,"b":0}'
instrument stream base.counter --seconds 5 --csv out.csv
instrument alarms listIt talks to 192.168.4.1 (the board's own access point); on a lab network give --host instrument-XXXX.local (XXXX = the last four hex digits of the MAC, printed in the boot log) or the IP address.
firmware/src/blocks/template/ → the driver folder of the block your instrument needs, e.g. firmware/src/blocks/b3_outputs/. Rename the class and name() → "b3".status() returns plausible fake values; handle() accepts your commands. Flash; your tab appears with a generic key/value view.host/pwa/panels/template.js → panels/b3.js, set id: 'b3', replace the example control. Add it to PANELS in app.js. pio run -t uploadfs; reload the phone.firmware/src/main.cpp (one registry.add(&b3) line — the stub drivers are already registered, so you only replace the stub of the driver you are writing).#ifndef SIM branches with the real driver using the pin constants in firmware/include/pins.h; the chip's protocol, the signal path and the limits are in docs/class-board-reference.md (one subsection per capability, with a minimal snippet).The rules: a driver talks only to its own hardware; all driver code runs from loop() (no tasks, no locks); every status key you show in the panel is one your status() emits.
hardware/release/ holds the two board models (class-board.stl, front-panel.stl meshes and the STEP files, exported from the final KiCad boards with kicad-cli, keeping the connectors, LEDs and modules) and nothing else: the housing is yours to design, from the housing brief with Claude Code, in docs/students/<name>/housing/, starting by opening each board on its own in OpenSCAD; chapter 3 §A.6 has the steps, and SETUP.md Step 11 installs OpenSCAD and Bambu Studio.
Rules are data: {block, key, op, threshold, action}, evaluated 20× per second on the same status the app sees; they fire once when the condition becomes true.
b1.ai1, gt, 9, action module 1 off → Add rule.api.addAlarm({ block: 'b2', key: 'v5_raw', op: 'lt', threshold: 4.9, action: 'notify' }) (see panels/template.js).instrument alarms add --block b1 --key ai1 --op gt --threshold 9 --action module:1:off.action is notify (toast on every phone, browser notification if allowed) or module:<n>:on|off (a module output on the front panel, Section C). Rules persist on the board in /alarms.json.
firmware/
platformio.ini envs esp32s3 (real), esp32s3-sim (-DSIM=1), esp32s3-ota (esp32s3 over WiFi)
secrets.ini.example OTA upload template (board name, OTA password)
include/pins.h GPIO map of the boards as ordered (docs/class-board-reference.md §3)
include/secrets.h.example WiFi and OTA password template
scripts/copy_pwa.py pre-build: host/pwa -> firmware/data
src/main.cpp WiFi, mDNS, OTA, HTTP + WebSocket, 20 Hz broadcast
src/blocks/Block.h the 5-method driver interface
src/blocks/Registry.* list of drivers
src/blocks/base/ LED, counter, temperature
src/blocks/template/ copy-me block
src/blocks/b1_inputs/ … b5_dio_trig/
src/alarm/AlarmEngine.* rules, block "alarms"
PROTOCOL.md
host/
pwa/ index.html app.js style.css sw.js manifest, panels/*.js
instrument.py, pyproject.toml
hardware/sections/ the main board split four ways BY AREA for routing (A rear right, B front right, C front left, D rear left); README.md there
hardware/scripts/main_sections.py areas / split / check / merge / report for the main-board areas
hardware/front-panel/sections/ the front panel split four ways for routing (A analog, B digital, C modules, D isolated + power); README.md there
hardware/scripts/panel_sections.py split / check / merge the four sections back into one board
docs/
references.md papers (NMR at low field), datasheets of every part, tools and suppliers
class-board-reference.md THE BOARD, PIN BY PIN: connectors, links, every chip's protocol, levels, limits, firmware state, known defects (netlist-derived, 2026-10-08)
references/ the two Creative-Commons papers as PDF
.github/workflows/build.yml both envs + ruff on every push/PR| Qty | Value | Footprint | References |
|---|---|---|---|
| 40 | 10k | Resistor_SMD:R_0603_1608Metric | R5, R6, R7, R8, R10, R102, R301, R303, R305, R307, R311, R706, R721, R724, R804, R805 +24 |
| 39 | 100nF | Capacitor_SMD:C_0603_1608Metric | C108, C202, C208, C209, C212, C215, C302, C304, C305, C308, C501, C502, C503, C504, C507, C703 +23 |
| 17 | 10uF | Capacitor_SMD:C_0805_2012Metric | C102, C106, C107, C203, C204, C211, C213, C216, C217, C303, C306, C307, C726, C727, C806, C811 +1 |
| 15 | BAV99 | class_board:SOT-23-3_L2.9-W1.6-P1.90-LS2.8-BR | D111, D112, D113, D114, D115, D116, D117, D118, D301, D302, D501, D905, D906, D907, D908 |
| 12 | 10nF | Capacitor_SMD:C_0603_1608Metric | C720, C804, C805, C817, C901, C902, C903, C904, C910, C911, C912, C913 |
| 10 | 1.00k | Resistor_SMD:R_0603_1608Metric | R714, R723, R910, R911, R912, R913, R914, R916, R918, R923 |
| 8 | 1k | Resistor_SMD:R_0805_2012Metric | R111, R112, R113, R114, R115, R116, R117, R118 |
| 8 | 1nF | Capacitor_SMD:C_0603_1608Metric | C111, C112, C113, C114, C115, C116, C117, C118 |
| 8 | 1uF | Capacitor_SMD:C_0603_1608Metric | C101, C103, C104, C210, C301, C704, C810, C930 |
| 8 | 40.2k | Resistor_SMD:R_0603_1608Metric | R302, R304, R306, R308, R915, R917, R919, R924 |
| 7 | 100 | Resistor_SMD:R_0603_1608Metric | R704, R707, R710, R713, R816, R905, R931 |
| 7 | C2937625_-_Conn_01x03 | DDS_Library:C2937625-HDR-TH_3P-P2.54-V-M | J3, J9, J10, J11, J12, J14, J15 |
| 6 | 2.2k | Resistor_SMD:R_0603_1608Metric | R203, R204, R205, R206, R207, R208 |
| 6 | 33 | Resistor_SMD:R_0603_1608Metric | R1, R2, R101, R511, R703, R705 |
| 6 | 600R@100MHz | class_board:L0603 | FB201, FB202, FB203, FB204, FB801, FB901 |
| 5 | 22uF | Capacitor_SMD:C_1206_3216Metric | C105, C201, C206, C207, C214 |
| 5 | 4.7k | Resistor_SMD:R_0603_1608Metric | R3, R4, R211, R212, R941 |
| 5 | GREEN | class_board:LED0805-R-RD | D203, D204, D205, D206, D933 |
| 4 | 10.0k | Resistor_SMD:R_0603_1608Metric | R712, R722, R901, R902 |
| 4 | 1k | Resistor_SMD:R_0603_1608Metric | R209, R210, R821, R822 |
| 4 | 49.9 | Resistor_SMD:R_0603_1608Metric | R309, R310, R509, R510 |
| 4 | M3 | class_board:MountingHole_3.2mm_M3 | H1, H2, H3, H4 |
| 3 | 100k | Resistor_SMD:R_0603_1608Metric | R811, R812, R942 |
| 3 | Fiducial | class_board:Fiducial_1mm_Mask2mm | FID1, FID2, FID3 |
| 3 | Panel link 2x20 female | Connector_PinSocket_2.54mm:PinSocket_2x20_P2.54mm_Vertical | J6, J7, J8 |
| 3 | SS54 | class_board:SMA_L4.4-W2.8-LS5.4-R-RD | D801, D802, D930 |
| 2 | +3V3 | class_board:TestPoint_Pad_D1.5mm | TP3, TP202 |
| 2 | 1.5A polyfuse | class_board:F1812 | F201, F202 |
| 2 | 100pF | Capacitor_SMD:C_0603_1608Metric | C711, C721 |
| 2 | 100uF 35V | class_board:CAP-SMD_BD8.0-L8.3-W8.3-LS9.3-FD | C921, C940 |
| 2 | 1N4148W | class_board:SOD-123F_L2.7-W1.6-LS3.8-RD | D703, D704 |
| 2 | 1k | Resistor_SMD:R_Array_Convex_4x0603 | RN501, RN502 |
| 2 | 200 | Resistor_SMD:R_0603_1608Metric | R802, R803 |
| 2 | 270pF | Capacitor_SMD:C_0603_1608Metric | C914, C915 |
| 2 | 3.9pF | Capacitor_SMD:C_0603_1608Metric | C701, C702 |
| 2 | 47k | Resistor_SMD:R_0603_1608Metric | R820, R940 |
| 2 | 5.1k | Resistor_SMD:R_0603_1608Metric | R201, R202 |
| 2 | 74HCT125PW | class_board:TSSOP-14_L5.0-W4.4-P0.65-LS6.4-BL | U302, U502 |
| 2 | B0512S-2WR3 | class_board:PWRM-TH_IBXX05S-2WR3 | PS201, PS202 |
| 2 | DNP-0R | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R715, R716 |
| 2 | KF301-5.0-2P | class_board:CONN-TH_P5.00_KF301-5.0-2P | J901, J903 |
| 2 | LM66100DCKR | class_board:SC-70-6_L2.0-W1.3-P0.65-LS2.1-BL | U201, U202 |
| 2 | OPA1612AIDR | class_board:SOIC-8_L5.0-W4.0-P1.27-LS6.0-BL | U705, U706 |
| 2 | SMF5.0A | class_board:SOD-123FL_L2.7-W1.8-LS3.8-RD | D201, D202 |
| 2 | TS5A23157DGSR | class_board:MSOP-10_L3.0-W3.0-P0.50-LS5.0-BL | U901, U902 |
| 1 | +12V | class_board:TestPoint_Pad_D1.5mm | TP203 |
| 1 | +5V | class_board:TestPoint_Pad_D1.5mm | TP4 |
| 1 | +5VA | class_board:TestPoint_Pad_D1.5mm | TP205 |
| 1 | -12V | class_board:TestPoint_Pad_D1.5mm | TP204 |
| 1 | 0 | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R717 |
| 1 | 1.2nF | Capacitor_SMD:C_0603_1608Metric | C710 |
| 1 | 10 | Resistor_SMD:R_0805_2012Metric | R814 |
| 1 | 10 | Resistor_SMD:R_0603_1608Metric | R932 |
| 1 | 10mR | Resistor_SMD:R_2512_6332Metric | R933 |
| 1 | 10uF | Capacitor_SMD:C_1206_3216Metric | C818 |
| 1 | 11k | Resistor_SMD:R_0603_1608Metric | R809 |
| 1 | 120R@100MHz 3A | class_board:L0603 | FB802 |
| 1 | 130pF | Capacitor_SMD:C_0603_1608Metric | C809 |
| 1 | 15uH | class_board:IND-SMD_L3.2-W2.5-LQH32CN1R0M53L | L801 |
| 1 | 1M | Resistor_SMD:R_0603_1608Metric | R711 |
| 1 | 1k | Resistor_SMD:R_0805_2012Metric_Pad1.20x1.40mm_HandSolder | R718 |
| 1 | 20pF | Capacitor_SMD:C_0603_1608Metric | C807 |
| 1 | 22pF | Capacitor_SMD:C_0603_1608Metric | C712 |
| 1 | 25 MHz | class_board:CRYSTAL-SMD_4P-L3.2-W2.5-BL | Y701 |
| 1 | 270 | Resistor_SMD:R_0603_1608Metric | R808 |
| 1 | 32k | Resistor_SMD:R_0603_1608Metric | R920 |
| 1 | 390pF | Capacitor_SMD:C_0603_1608Metric | C808 |
| 1 | 4.7 1W | Resistor_SMD:R_2512_6332Metric | R813 |
| 1 | 47pF | Capacitor_SMD:C_0603_1608Metric | C706 |
| 1 | 47uF 35V | class_board:CAP-SMD_BD6.3-L6.6-W6.6-FD | C815 |
| 1 | 5A fast | class_board:F1206 | F901 |
| 1 | 5V_RAW | class_board:TestPoint_Pad_D1.5mm | TP201 |
| 1 | 6.49k | Resistor_SMD:R_0603_1608Metric | R807 |
| 1 | 6.8k | Resistor_SMD:R_0603_1608Metric | R801 |
| 1 | 74HC74D,653 | class_board:SOIC-14_L8.7-W3.9-P1.27-LS6.0-BL | U702 |
| 1 | 78L05G-AB3-R | class_board:SOT-89-3_L4.5-W2.5-P1.50-LS4.2-BR | U203 |
| 1 | 910 | Resistor_SMD:R_0603_1608Metric | R815 |
| 1 | AD9834BRUZ | class_board:TSSOP-20_L6.5-W4.4-P0.65-LS6.4-BL | U801 |
| 1 | ADS8688IDBTR | class_board:TSSOP-38_L9.7-W4.4-P0.50-LS6.4-BL | U101 |
| 1 | AMS1117-3.3 | class_board:SOT-223-3_L6.5-W3.4-P2.30-LS7.0-BR | U204 |
The 313 most common of 342 parts.

TIGP-Experimental-Methods/class-board-2026
TIGP class instrument 2026: one board, five blocks - KiCad hardware, ESP32-S3 firmware, phone PWA, Python API, workbook

playduino/sheep-tester
Raspberry Pi 4B HAT for Playduino Sheep EOL factory test

sudo-junkie/Controller
ESP32-based replacement PCB for UCOM USB game controller, with LCD, IMU, IR TX/RX and more...




the78mole/jumpstarter-mono
This is a monorepo containing all jumpstarter tools and (hopefully) adds a standalone server soon.

DvidMakesThings/HW_PDNode-600-Pro
The PDNode-600 is a professional-grade, 600W USB Power Delivery Unit designed specifically for compact 10-inch rack systems, targeting embedded developers, homelab enthusiasts, test benches, and smart infrastructure.

uw-roamr/roamr
turn your phone into a robot running SLAM with two motors, two control boards, and a bluetooth chip
It measures 180 × 100 mm and has 4 copper layers, 1.61 mm thick.
342 parts, built around the 74HCT125PW,118-JSM, B0512S-2WR3, OPA1612AIDRGR and ADS8688IDBTR. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
From the TIGP-Experimental-Methods/class-board-2026 repository on GitHub, at the commit this page was made from: the board is hardware/V0.2/class-board_V0.2.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.
Its repository states no licence, so its author keeps all rights: ask TIGP-Experimental-Methods before reusing the design.