
Tspico v2.6
timex-sinclair-projects/tspico-hardware
Status 2026-10-19: prototype 3 (v3.0-proto3, changes below). Proto1 (v3.0-proto1) was built and brought up; proto2 added its fixes (below).

Status 2026-10-19: prototype 3 (v3.0-proto3, changes below). Proto1 (v3.0-proto1) was built and brought up; proto2 added its fixes (below).
Tspico v3 is an open-source printed circuit board by timex-sinclair-projects, from the timex-sinclair-projects/tspico-hardware repository on GitHub, published under the CERN-OHL-S-2.0 licence. It is a 4-layer board of 130 × 64.3 mm with 156 parts on 194 nets, routed with 2,125 tracks and 373 vias.
It is built around the RP2350B, 74LVC245APW and ESP32-C3-WROOM-02-N4, with the GD25Q128ESIGR, MCP6002-I/P and 74LVC06AD among its other parts. Its parts include 63 resistors, 52 capacitors, 15 ICs, 6 connectors and 4 switches. It belongs with the ESP32, RP2350 and RISC-V boards of this library.
Status 2026-10-19: prototype 3 (v3.0-proto3, changes below). Proto1 (v3.0-proto1) was built and brought up; proto2 added its fixes (below). Both proto2 and proto3 were routed by scripts/flow.py with no hand edits, DRC clean at every severity. ERC: one warning, U4's unused input tied to GND.
To order: the JLCPCB upload files (Gerber zip, BOM, placement), ordering notes, schematic PDF and renders are on the v3.0-proto3.1 release: proto3 with the board labels, and L1 assembled by JLCPCB again. U22, J3 and J5 are fitted by hand. v3.0-proto3 is the same copper without the labels, with L1 fitted by hand. Proto2: v3.0-proto2.1 (L1 by hand) and v3.0-proto2 (L1 assembled by JLCPCB), same Gerbers. To regenerate them from this folder, run python3 scripts/gen_fab.py (output in fab/jlcpcb/).
| Change | Parts | Why |
|---|---|---|
| Male DE-9 | J5 CONNFLY DS1037-09MNAKT74-0CC (C75750), footprint DSUB-9_Pins_Horizontal_… | Atari sticks (CX-40) end in a female plug, like the 2068's own male ports expect. Proto1/2 had a female footprint; a male connector on it mirrors every pin, putting the stick's ground (pin 8) on pin 7 (JOY_5V, open) so nothing reads. Same body position and holes as proto2. On a proto1/2 board, mount a male connector on the back of the board: turning it over puts each pin on its own pad. |
| Buttons and LED moved | SW3 under SW1, SW4 under SW2; D4/R5 left of the ESP32 | A 1.54 in module on J3 covered BOOTSEL and crowded RUN. |
| I2C OLED header | J3 1x4 (1 GND, 2 +3V3, 3 SCL, 4 SDA), R44/R45 4k7 pull-ups | For a 1.54 in 128x64 SSD1309 I2C module, on I2C1 (GP43 SCL, GP42 SDA). Replaces the 7-pin header for the 1.27 in SSD1351 SPI module. GP34, GP41 and expander P0.2 (with its pull-down R11) are freed. |
| Change | Parts | Why |
|---|---|---|
| nBE pull-up to +5V_EDGE | R14 2k2 | The 74LVC06 only pulls /BE low; without a pull-up it rose too slowly and a RAM opcode fetch after a served byte read wrong bits. Proto1 boards carry a 2.2k bodge from A29 to +5V. |
| GP8-GP10 pull-ups | R15-R17 10k | nAEN_LO, nAEN_HI, nDATA_OE: the RP2350 resets its pads with pull-downs, so in reset and BOOTSEL all three buffers were enabled and U1/U2 fought on MD. |
| SD socket power switch | U32 SY6280AAC, R42 15k (0.45 A), R43 10k, C52 1u | Socket VDD is now +3V3_SD, switched from XL9555 P0.6 (SD_PWR_EN, off at power-up). A latched card hits the current limit instead of resetting the RP2350B; the firmware can power-cycle a card. R34-R37 moved to +3V3_SD; R38 (detect) stays on +3V3. |
| U22 footprint | SOIC-8 3.9 x 4.9 mm | The APS6404L-3SQR-SN is SOP-8 150 mil (AP Memory package code SN); proto1 had the 208 mil footprint. Still DNP; see docs/bom-cost-optimization.md for PSRAM options. |
| L1 fitted by hand | L1 DNP at JLCPCB | The RP2350 core-regulator inductor (Abracon AOTA-B201610S3R3-101-T) was out of stock at JLCPCB and LCSC on 2026-10-12. Raspberry Pi names no substitute: it is the only polarity-marked 0806 part. Fit it with the dot on pad 2 (DVDD, silkscreen dot), the end away from the top edge; the core has no supply until it is on. Gerbers unchanged. From proto3.1 (2026-10-22, 326 in stock) JLCPCB assembles it again. |
Firmware for proto2: set SD_PWR_EN (expander port 0 bit 6) after the detect switch reads a card and before any SPI; return the SD pins to inputs before clearing it.
Sheets are generated by scripts/gen_<sheet>.py on top of scripts/schlib.py, netlist-style (labels on every pin, no wires). Regenerating a sheet overwrites hand edits, so once a sheet is being edited in KiCad, retire its generator. Spec: ../docs/v3-architecture.md.
| File | Purpose |
|---|---|
tspico-v3.kicad_pro | Project. Design rules are JLCPCB 4-layer capable but conservative: 0.127 mm min clearance and track, 0.45/0.2 mm min via, 0.3 mm copper-to-edge. Net classes: Default 0.2 mm track / 0.6-0.3 via, Power 0.5 mm / 0.8-0.4. |
tspico-v3.kicad_sch | Root sheet with eight hierarchical sheets: interface, memory, mcu, power, storage_display, wifi, audio, joystick. |
tspico-v3.kicad_pcb | 4-layer board (F.Cu, In1 GND, In2 PWR, B.Cu), 1.6 mm, ENIG. Outline and connector generated by scripts/gen_board.py. |
lib/tspico.pretty/TS2068_EdgeConnector_2x32.kicad_mod | The 2068 edge connector (generated). |
lib/tspico.kicad_sym | Project symbols (empty so far; ESP32-C3 module and anything not in the stock library go here). |
scripts/gen_board.py | Regenerates the edge-connector footprint and the bare board skeleton (outline, connector, holes, fiducials). Called by gen_layout.py. |
scripts/gen_layout.py | Builds the placed board: exports the netlist, loads every footprint from the KiCad or project library, assigns nets, places parts from its P table, adds the GND and +3V3 zones. Run with KiCad's bundled Python (/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/Current/bin/python3). It overwrites tspico-v3.kicad_pcb, so once routing starts in KiCad, stop running it and treat the placement table as history. |
scripts/check.sh | kicad-cli ERC and DRC. |
scripts/schlib.py, scripts/gen_edge_symbol.py, scripts/gen_interface.py, scripts/gen_memory.py, scripts/gen_mcu.py, scripts/gen_power.py, scripts/gen_storage_display.py, scripts/gen_wifi.py, scripts/gen_audio.py, scripts/gen_joystick.py | Schematic generators (see above). |
scripts/parts.py | Orderable part table: (Value, footprint) to MPN and LCSC number. schlib.place() stamps MPN and LCSC fields on every symbol from it. |
bom/tspico-v3-jlcpcb-bom.csv | JLCPCB-format BOM (Comment, Designator, Footprint, LCSC Part #), regenerated by the command below. |
bom/tspico-v3-bom-full.csv | Same grouping with MPN and quantities. |
docs/tspico-v3-schematic.pdf | Schematic, all nine sheets. |
docs/tspico-v3-pcb.pdf | PCB plot, one page per layer: F.Cu, In1.Cu, In2.Cu, B.Cu, F/B silkscreen, F/B fab (DNP parts crossed out). Bottom layers are not mirrored. |
renders/ | 3D renders (top, bottom, isometric). |
fab/ | Plot output directory (ignored by git). |
Measured from the v2.2 fab Gerbers (../old/tsPico2_2Fab/Gerber/tsPico.GTL, .GBL, .Outline):
| Item | Value |
|---|---|
| Board | 130.00 x 64.25 mm, 1.6 mm (v2.2 was 100 mm tall; v3 body is 56 mm plus the 8.25 mm tongue) |
| Body | 130.00 x 64.00 mm |
| Tongue | 83.66 mm wide, 8.25 mm deep, left edge 18.48 mm from the board's left edge, 1.0 mm corner chamfers |
| Positions | 32 at 2.54 mm pitch, 31 fingers per side |
| Keying slot | position 6, 2.03 mm wide, 8.26 mm deep |
| Fingers | 1.651 x 8.255 mm (65 x 325 mil), first finger centre 2.52 mm from the tongue's left edge, bottom edge 0.32 mm above the tongue edge |
Pad names are A1..A32 on F.Cu and B1..B32 on B.Cu (no A6/B6), following the TS2068 Technical Manual table 2.4.1-1. Position 1 is at the left when the board is viewed from the top with the connector at the bottom.
Side A is the top (component) side, confirmed: side A (GND, EAR, D7, ..., SOUND) is on the top and side B (+15V, +5V, address bus, video) on the bottom. Proto1 proved it in a 2068 on 2026-10-07 (firmware/bringup/README.md). In shadow mode it logged every address the Z80 read through the side-B fingers, the boot log matching the ROM exactly. It then served the HOME ROM onto side A, with /MREQ and /RD timing each read, /BE on A29 and the data on D0-D7, and the 2068 booted to the card's "TS-Pico v3 ROM" banner. With the sides swapped, the address fingers would sit on data and control lines and none of that could work. This also matches how the v2.2 layout routes +5V (bottom trace from the fourth finger).
Fab notes for the fingers: hard gold over the fingers if the fab offers it (ENIG otherwise), 30 degree bevel on the tongue edge, no copper within 0.3 mm of the slot.
RP2350B symbol (MCU_RaspberryPi), Package_DFN_QFN:QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mm, TSPico:microSD_TF-01A (to be drawn from the HRO TF-01A drawing), Connector_Dsub:DSUB-9_*, Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12, SOIC/TSSOP logic packages, Button_Switch_SMD:SW_SPST_PTS645Sx43SMTR92, Connector_Audio:Jack_3.5mm_CUI_SJ-352x-SMT_Horizontal, Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm.
All parts used so far have stock symbols and footprints (the APS6404L PSRAM is in Memory_RAM, the ESP32-C3-WROOM-02 in RF_Module).
cd hardware
KC=/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli
$KC sch export bom --output bom/tspico-v3-jlcpcb-bom.csv --fields "Value,Reference,Footprint,LCSC" --labels "Comment,Designator,Footprint,LCSC Part #" --group-by "Value,Footprint" --exclude-dnp tspico-v3.kicad_schPart choices are priced against JLCPCB's catalog with python3 scripts/jlc_price.py --boards 10; the reasoning for each substitution is in docs/bom-cost-optimization.md (2026-09-17: parts + fees per board went from $32.95 to $14.93 on a 10-board run, to $14.48 after the DMA option was dropped; fitted logic packages from 16 to 7). The board was laid out again for these parts on 2026-09-17 (see the routing section).
python3 scripts/gen_fab.py writes fab/jlcpcb/: tspico-v3-gerbers.zip (4-layer Gerbers + Excellon drill), tspico-v3-bom.csv and tspico-v3-cpl.csv (JLCPCB assembly format), and refreshes the two BOMs in bom/. fab/NOTES.md lists what to check on upload (L1 has no LCSC number yet; rotations of QFN/SOT/connector parts need checking in JLC's placement preview; J3 and J5 are through-hole).
Regenerate after a board or schematic change (from hardware/):
KC=/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli
$KC sch export pdf -o docs/tspico-v3-schematic.pdf tspico-v3.kicad_sch
$KC pcb export pdf --mode-multipage --ibt --scale 0 --cdnp -l F.Cu,In1.Cu,In2.Cu,B.Cu,F.Silkscreen,B.Silkscreen,F.Fab,B.Fab --cl Edge.Cuts -o docs/tspico-v3-pcb.pdf tspico-v3.kicad_pcb
$KC pcb render --side top --quality high -w 2384 -h 1376 --zoom 1.1 -o renders/tspico-v3-top.png tspico-v3.kicad_pcb
$KC pcb render --side bottom --quality high -w 2384 -h 1376 --zoom 1.1 -o renders/tspico-v3-bottom.png tspico-v3.kicad_pcb
$KC pcb render --side top --quality high -w 2384 -h 1568 --rotate '-45,0,45' --perspective -o renders/tspico-v3-iso.png tspico-v3.kicad_pcbParts without a 3D model in the KiCad library (the four buttons, USB-C, micro SD socket, audio jack) show as bare pads in the renders; U22 is DNP.
lib/tspico.pretty/microSD_TF-01A and L_Abracon_AOTA-B201610 against the parts' drawings (both were generated from LCSC's land patterns). (TF-01A card detect: low = card in, confirmed on proto1.)cd hardware
PY=/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/Current/bin/python3
$PY scripts/gen_layout.py # placement, keepouts, planes (discards all routing)
$PY scripts/preroute.py # locked hand routes: regulator, crystal, DVDD spine, inner supply links, bottom-row fan
$PY scripts/autoroute.py --passes 100 # jar from $FREEROUTING_JAR (or --jar); about 25 minutes
$PY scripts/finish.py # widen necked tracks, join orphan supply islands, retry open nets, drop unused vias and stubs
$PY scripts/silk.py # place reference designators clear of pads, silk and the edge; trim outlines at the edge
$PY scripts/labels.py v3 # board name on top; name, URL, licence and OSHW logo on the bottomOr all five in one command, which is the way to run it:
$PY scripts/flow.py # jar from $FREEROUTING_JAR (or --jar); 10 to 15 minutes per attempt, up to 3 attemptsFreerouting does not give the same result twice from the same input. Before U1's address pins had their pre-routed via fan (below), about one run in three left one of them (A3, A5) sealed in behind its neighbours' vias; with the fan, three runs out of three closed on the first attempt. The safety net stays: if a signal net is left open, finish.py tries lifting the neighbours' copper around the pin, routing the open net and putting the neighbours back, but refuses a result that needs more than three vias per net (the only way out of such a pocket has been a 9-via, 70 mm detour). If the board still is not clean finish.py exits non-zero and flow.py routes again from the placement, keeping the failed board as fab/route/attempt-N.kicad_pcb.
Rules: 0.15 mm signal tracks (the router may neck down to 0.11 mm in a squeeze; board minimum 0.1 mm, JLCPCB's is 0.09), 0.4 mm on the 5 V rails and +3V3_WIFI, 0.127 mm clearance, every via 0.6/0.3 mm (JLCPCB's no-surcharge size; the ESP32 footprint's 0.2 mm thermal vias are opened to 0.3 mm by gen_layout.py). The pre-stitch step skips the RP2350B's pins: a via 0.9 mm out of a 0.4 mm pitch pin blocks both neighbours' escape lanes. Its exposed pad gets five ground vias.
What preroute.py fixes in place before the autorouter runs, because the router handles a fully used 0.4 mm pitch QFN badly (whichever pins it leaves for last end up sealed behind its own vias):
State on 2026-09-17 (DMA option dropped, address buffers delivering plain bytes, audio and joystick passives respaced for silkscreen, U1 address fan pre-routed): fully routed, DRC clean at every severity (no errors, no warnings, no unconnected items), no tracks on either plane layer. 2073 track segments, 378 vias, all 0.6/0.3 mm, no track under 0.13 mm. Produced by flow.py on its first attempt with no hand edits. (Run DRC on the board where it sits: a copy outside the project folder is checked against KiCad's default rules and reports hundreds of false clearance errors.)
Silkscreen: silk.py sets designators to 0.8 mm text and tries positions around each part until one clears pads, other silk, other parts and the board edge; rows of equal parts all get the label on the same side (above a flat part, to the right of an upright one), and a hidden designator gets a second chance by moving a neighbour's. The layout leaves room for this: passive rows are 3.2 mm apart in the audio block, 2.7 mm in the SD pull-ups, and the joystick RC columns are 2.6 mm apart. 139 of 140 designators are on silk; only C39 (the VREG_AVDD capacitor, boxed in by the design guide's regulator layout) is hidden, and it is on F.Fab with all the others. Outlines of edge connectors that overhang the board are trimmed where they cross the edge.
Board labels: labels.py (also run by flow.py) puts "TS-Pico v3" on the top silkscreen above the left end of the fingers. On the bottom it puts the name, the repository URL (the CERN-OHL-S Source Location), the licence and the OSHW logo. It only touches its own group of silkscreen items, so it can be rerun on a routed board, and it refuses to save if a label would land on a pad, a courtyard or other silk. labels.py v2.6 does the same for ../hardware-v2.6/.
Rerunning autoroute.py continues from the current routing (it exports existing tracks); --fresh starts over. Do not rerun gen_layout.py: it rebuilds the board from placement and discards routing.
| Qty | Value | Footprint | References |
|---|---|---|---|
| 26 | 10k | R_0603_1608Metric | R10, R12, R13, R15, R16, R17, R21, R22, R34, R35, R36, R37, R38, R40, R100, R101 +10 |
| 24 | 100n | C_0603_1608Metric | C1, C2, C3, C10, C21, C23, C24, C25, C26, C27, C28, C29, C30, C31, C37, C38 +8 |
| 9 | 100 | R_0603_1608Metric | R5, R80, R81, R90, R91, R92, R93, R94, R95 |
| 8 | 10u | C_0603_1608Metric | C42, C43, C44, C45, C50, C60, C80, C81 |
| 7 | 1k | R_0603_1608Metric | R3, R20, R23, R71, R72, R75, R76 |
| 7 | 33 | R_0603_1608Metric | R26, R27, R28, R30, R31, R32, R33 |
| 6 | 100p | C_0603_1608Metric | C90, C91, C92, C93, C94, C95 |
| 6 | 4k7 | R_0603_1608Metric | R1, R2, R6, R7, R44, R45 |
| 4 | 10n | C_0603_1608Metric | C71, C72, C75, C76 |
| 4 | 74LVC245APW | TSSOP-20_4.4x6.5mm_P0.65mm | U1, U2, U3, U4 |
| 4 | MountingHole_3.2mm_M3 | MountingHole:MountingHole_3.2mm_M3 | H1, H2, H3, H4 |
| 3 | 220u | CP_Elec_6.3x5.4 | C40, C41, C46 |
| 3 | 4.7u | C_0402_1005Metric | C20, C22, C39 |
| 3 | Fiducial_1mm_Mask2mm | Fiducial:Fiducial_1mm_Mask2mm | FID1, FID2, FID3 |
| 2 | 15p | C_0603_1608Metric | C35, C36 |
| 2 | 1u | C_0603_1608Metric | C52, C70 |
| 2 | 22k | R_0603_1608Metric | R74, R78 |
| 2 | 5k1 | R_0603_1608Metric | R24, R25 |
| 2 | SS34 | D_SMA | D2, D3 |
| 2 | TLV1117LV33 | SOT-223-3_TabPin2 | U30, U31 |
| 1 | 0 | R_0603_1608Metric | R39 |
| 1 | 100k | R_0603_1608Metric | R70 |
| 1 | 12MHz | Crystal_SMD_3225-4Pin_3.2x2.5mm | Y1 |
| 1 | 15k | R_0603_1608Metric | R42 |
| 1 | 2k2 | R_0603_1608Metric | R14 |
| 1 | 3.3uH | L_Abracon_AOTA-B201610 | L1 |
| 1 | 3.5mm stereo | Jack_3.5mm_PJ320D_Horizontal | J4 |
| 1 | 74LVC06AD | SOIC-14_3.9x8.7mm_P1.27mm | U7 |
| 1 | 74LVC32APW | TSSOP-14_4.4x5mm_P0.65mm | U6 |
| 1 | APS6404L-3SQR-SN | SOIC-8_3.9x4.9mm_P1.27mm | U22 |
| 1 | BOOTSEL | SW_Push_1P1T_XKB_TS-1187A | SW4 |
| 1 | ESP32-C3-WROOM-02-N4 | ESP32-C3-WROOM-02 | U40 |
| 1 | GD25Q128ESIGR | SOIC-8_5.3x5.3mm_P1.27mm | U21 |
| 1 | GND | TestPoint_Pad_1.5x1.5mm | TP3 |
| 1 | JOY +5V | SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP10 |
| 1 | Joystick | DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm | J5 |
| 1 | MCP6002 | SOIC-8_3.9x4.9mm_P1.27mm | U50 |
| 1 | NMI | SW_Push_1P1T_XKB_TS-1187A | SW2 |
| 1 | OLED 1.54in 128x64 I2C | PinHeader_1x04_P2.54mm_Vertical | J3 |
| 1 | RP2350B | QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mm | U20 |
| 1 | RUN | SW_Push_1P1T_XKB_TS-1187A | SW3 |
| 1 | SWCLK | TestPoint_Pad_1.5x1.5mm | TP1 |
| 1 | SWDIO | TestPoint_Pad_1.5x1.5mm | TP2 |
| 1 | SY6280AAC | SOT-23-5 | U32 |
| 1 | TS Reset | SW_Push_1P1T_XKB_TS-1187A | SW1 |
| 1 | TS2068_Edge | tspico:TS2068_EdgeConnector_2x32 | P1 |
| 1 | USB-C | USB_C_Receptacle_HRO_TYPE-C-31-M-12 | J1 |
| 1 | XL9555 | TSSOP-24_4.4x7.8mm_P0.65mm | U15 |
| 1 | green | LED_0805_2012Metric | D4 |
| 1 | microSD | microSD_TF-01A | J2 |

timex-sinclair-projects/tspico-hardware
Status 2026-10-19: prototype 3 (v3.0-proto3, changes below). Proto1 (v3.0-proto1) was built and brought up; proto2 added its fixes (below).

timex-sinclair-projects/tspico-hardware
Status 2026-10-19: prototype 3 (v3.0-proto3, changes below). Proto1 (v3.0-proto1) was built and brought up; proto2 added its fixes (below).

timex-sinclair-projects/TS-2068-Megabank
Battery-backed 4 MB expansion-bank RAM board for the Timex Sinclair 2068 (TS-Pico v3)

embeddedci-com/benchpod
An open hardware bench tool that plugs into your CI: sensor sim, CAN, analog I/O, power control, and a Python SDK with pytest integration

eelstretching/led-processor-board
A board with a processor and a couple of other things that can talk to an LED output board.



aniranaway/Drone_Swarm
A fully custom, low-level embedded hardware and firmware platform designed for autonomous multi drone swarms. The system integrates custom STM32-based flight controller PCBs, coreless DC motor drive arrays for a micro…

Tinkerbug-Robotics/TinkerRocket
Model rocket flight computer, ground station, iOS app, and simulation

eelstretching/rp2350b-rm2-reference
Raspberry Pi's RP2350B minimal design (RPI-RP2350B-MINIMAL R4-S1, KiCad 10) with these additions:
It measures 130 × 64.3 mm and has 4 copper layers, 1.6 mm thick.
156 parts, built around the RP2350B, 74LVC245APW, ESP32-C3-WROOM-02-N4 and GD25Q128ESIGR. 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 timex-sinclair-projects/tspico-hardware repository on GitHub, at the commit this page was made from: the board is hardware/tspico-v3.kicad_pcb and its schematic hardware-v2.5/benchprog.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Its repository publishes it under the CERN-OHL-S-2.0 licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).