Skip to the content
4 layersDial me spinner main, the printed circuit board in 3D

Dial me spinner main

tyevco/dial-me-spinner

A Bluetooth dial spinner about 70 mm across, built on the nRF52840. A heavy, balanced stainless flywheel ring spins around a stationary centre that holds a colour OLED and a ring of 48 addressable RGB LEDs.

Copper layers
4
Thickness
1.6 mm
Parts
209
Nets
142
Pads
699
Tracks, vias
2,444, 366
Pours
14
Routed
Yes
KiCad
KiCad 9.0 (file 20241229)
Created
18 Oct 2026
Updated
26 Oct 2026

Overview

Dial me spinner main is an open-source printed circuit board by tyevco, from the tyevco/dial-me-spinner repository on GitHub. It is a 4-layer board with 209 parts on 142 nets, routed with 2,444 tracks and 366 vias.

It is built around the MDBT50Q-U1MV2, DRV8837DSGR-MS and TPS61040DBVRG, with the W25Q128JVSIQ, LIS2DW12TR-MS and RT9080-33GJ5 among its other parts. Its parts include 89 capacitors, 50 diodes, 28 resistors, 14 ICs and 5 connectors. It belongs with the nRF52 and nRF5x boards of this library.

Main components

12

README

README.md

dial-me-spinner

Docs site: https://tyevco.github.io/dial-me-spinner/

A Bluetooth dial spinner about 70 mm across, built on the nRF52840. A heavy, balanced stainless flywheel ring spins around a stationary centre that holds a colour OLED and a ring of 48 addressable RGB LEDs. You can press the whole top to click. A tiny actuator switches the ring electronically between free spin and magnetic click detents, and it holds either position with zero power.

The same hardware serves three uses:

  • Game / decision spinner. You flick the ring and a prize wheel animates on the LEDs and the OLED, with a tick on every detent and the result shown when it stops.
  • PC / phone controller. It works like a Surface Dial: a BLE or USB HID Radial Controller on Windows, and a wheel or consumer-control device on macOS, Linux, Android and iOS.
  • Fidget toy. Long free spins or clicky spins, with a spin and RPM counter.

Status: rev A. Schematics and both PCBs are generated and routed, and the fab outputs are written; nothing has been built or tested. The schematics of both boards pass ERC. The only report is one intended pin pair that build.py waives and lists (U21's QOD tied to its VOUT). Both boards are placed and routed by layout.py (Freerouting first, then the grid router finish_route.py for the connections it leaves open) and pass KiCad DRC with schematic parity: 0 errors and 0 unconnected on both, no warnings on the main board, two silk-to-edge warnings on the base board. release.py wrote the fab outputs into hardware/fab/main/ and hardware/fab/base/ only after that gate passed. The mechanical model's clash check of the routed boards reports only the intended contacts. A SPICE suite (hardware/sim/, docs/simulation.md) checks the analog design against the netlists; the part changes it drove are in the schematics. No prototype has been built and no firmware has been written. Every runtime, force and spin-time figure in the docs is a calculation or simulation that still has to be verified on a prototype.

Features (rev A design)

SizeØ70 × 36 mm. Stationary centre, spinning 316L stainless ring (≈ 117 g, OD 70 / ID 54 / 10 mm) on a single 608 bearing
Detents24 clicks per turn, strength continuously variable from 0 to about 16.5 mN·m, or free spin (the detent magnet's residual cogging is 2.7 µN·m; the stepper's own cogging still has to be bench-tested, mechanical §7). Switched by a micro linear stepper with a self-locking lead screw (zero holding power)
Rotation sensingTI TMAG5273 3D Hall sensor reading the same 48-magnet track. Interpolated angle, about 0.05–0.1° after self-calibration, plus µA-level wake on rotation
Display1.5 in 128×128 RGB PMOLED (SSD1351) behind a Ø30 mm round window
Lights48 × WS2812C-2020-V6 RGB LEDs under an opal diffuser ring, 2 per detent
InputPress-down click (the whole top floats on springs over a centre switch), rotation, tap and pick-up (LIS2DW12 accelerometer)
SoundMagnet-free piezo for detent ticks and UI beeps
RadionRF52840 module (Raytac MDBT50Q-U1MV2, u.FL) with an off-board flex antenna in the base. BLE HID, a custom GATT service for a companion app, and USB HID
NFCNFC-A tag emulation (Type 2 / Type 4) on the nRF52840 NFCT, with a coil in the top cover. Phones read it; the spinner cannot read tags (the phone app reads one and sends the content over BLE)
Power1S LiPo of about 1000 mAh, TI BQ25180 power-path charger, USB-C. Ship mode, about 38 µA sleep (25 °C typical; power budget)
Estimated runtimeAbout 9 h of continuous game mode, about 4 weeks of typical desk-controller use per charge (see the power budget)

Documentation

DocumentContents
docs/architecture.mdSystem overview, how each requirement is met, block diagram, board partition and link, subsystem decisions with the alternatives that were rejected, power tree, power budget and battery life, RF strategy, risk register, open decisions, rough BOM cost
docs/assembly.mdFabrication and assembly: JLCPCB order settings, sourcing, the link harness and antenna coax, magnet track, bearing prep, rotor balancing, mechanical assembly order
docs/bringup.mdBring-up and acceptance tests (steps B-x), from bare PCBA to an accepted unit
docs/mechanical.mdMechanical design: stack-up and where SPEC §8 is not buildable as written, bearing, flywheel and balancing, magnet track, detent mechanism, press click, antenna placement, drop, feet, assembly order, mechanical BOM, calculations
docs/firmware.mdFirmware architecture plan (nRF Connect SDK / Zephyr, MCUboot), including the BQ25180 register init table (§13.1). A plan only: no firmware code exists yet
docs/simulation.mdThe SPICE suite in hardware/sim/: how to run it, how a test is built, what each block covers; the latest values are on its results page
docs/display-study.mdRev-B study (future work): a larger round touch AMOLED. Rev A keeps the SSD1351 OLED
hardware/design/SPEC.mdArchitecture of record for the schematics: boards, link pinout, power tree, nRF52840 pin map, per-sheet contents, mechanical stack-up
hardware/tools/README.mdThe Python → KiCad generator toolchain

Repository layout

text
docs/                    design documentation (start with architecture.md;
                         simulation.md for the SPICE suite, display-study.md
                         for rev B)
hardware/
  design/                the circuit as code
    SPEC.md              architecture of record (what the schematics implement)
    nets.py              pin map and the shared net names (the "capture contract")
    build.py             generates the schematics (.kicad_sch), project file and
                         netlist from the sheet modules, and runs ERC
    layout.py            generates the boards (.kicad_pcb) from the netlists:
                         SPEC §8 placement of the constrained parts, clustering
                         of the rest, outline, keep-outs, zones, fiducials,
                         optional Freerouting autoroute, then the grid router
                         (hardware/tools/finish_route.py) for what it leaves
                         open, clean-up (GND stitching vias, silkscreen
                         references), then DRC
    release.py           fab and review outputs into hardware/fab/<board>/:
                         gerbers, JLCPCB BOM + CPL, ORDER-NOTES.md, schematic and
                         board PDFs, 3D renders; refuses to write them unless DRC
                         and schematic parity are clean
    jlc_offsets.json     cached per-LCSC-part rotation/origin corrections for the
                         JLC CPL (written by tools/jlc_cpl.py)
    sheets/              one Python module per schematic sheet
                         main board: main_power, mcu, display, leds, sensors, detent

The schematics in hardware/kicad/ are generated. Do not hand-edit the .kicad_sch files. Change the sheet modules in hardware/design/sheets/ (and SPEC.md or nets.py if the architecture changes), then rebuild.

Regenerating the KiCad projects

Requirements:

  • KiCad 9, providing:
    • kicad-cli;
    • the pcbnew Python module, used to write custom footprints and by layout.py and release.py;
    • the stock libraries and project template in /usr/share/kicad. KICAD9_SYMBOL_DIR overrides the symbol path (used by build.py). KICAD9_FOOTPRINT_DIR overrides the footprint path, and only layout.py reads it. The project template path (/usr/share/kicad/template/kicad.kicad_pro) is fixed in schgen.py.
  • Python 3.10+, with numpy for layout.py, and matplotlib for the grid router.
  • poppler-utils (pdftoppm), only for --render.
  • Optionally xvfb-run, which release.py uses for headless 3D renders.
  • For autorouting only: Java 21 and the Freerouting 2.1.x jar, given by FREEROUTING_JAR.

From the repository root:

sh
# Both boards: writes hardware/kicad/main/ and hardware/kicad/base/,
# runs ERC and exports a netlist for each
python3 hardware/design/build.py

# One board, plus a PDF and one PNG per sheet for review
python3 hardware/design/build.py --board main --render

# Scratch build of selected sheets into another directory, without ERC
python3 hardware/design/build.py --board main --only leds sensors --out /tmp/scratch --no-erc

Each board directory receives:

  • dial-me-spinner-<board>.kicad_pro, the .kicad_sch sheets and a .net netlist;
  • the ERC report erc.rpt;
  • a project-local library in lib/ (dial_spinner.kicad_sym and dial_spinner.pretty).

Open the .kicad_pro in KiCad 9 to inspect the result. build.py exits non-zero if its own connectivity checks fail.

Boards and fab outputs (after build.py, which writes the netlists they read):

sh
# Place both boards and run DRC; add --route to autoroute with Freerouting
# (--minutes N is the Freerouting time budget of the first attempt, default 30,
# and attempt k gets k x N; --attempts N, default 3, re-runs it while
# connections remain; whatever is still open then goes to the grid router;
# --resume continues routing the board as it is; --complete runs only the grid
# router on it; --finish only cleans up an already routed board)
python3 hardware/design/layout.py [--board main|base] [--route] [--minutes N] [--attempts N] [--resume] [--complete] [--finish]

# Gerbers, JLCPCB BOM + CPL, ORDER-NOTES.md, PDFs and 3D renders into
# hardware/fab/<board>/; stops if DRC or schematic parity reports an error
# (--force writes a review-only package)
python3 hardware/design/release.py [--board main|base] [--force]

The CPL corrections come from hardware/design/jlc_offsets.json, so release.py needs no network access. It stops if a placed part has no entry there; jlc_cpl.update_offsets() adds one by fetching that part's EasyEDA footprint, and caches its pads. Before writing the CPL, release.py puts those EasyEDA pads down at each row's point and angle, the way JLC places the part, and stops if any pad lands off copper or on a differently numbered pad of the same name (a symmetric package turned 180° still lands on copper, so the numbers are the only sign). python3 -m pytest hardware/tools/tests -q checks that this check passes on both boards and catches a wrong rotation or origin. The EasyEDA footprint titles and pad centres in jlc_offsets.json come from the JLCEDA/EasyEDA Official Library (lceda.cn, easyeda.com), whose copyright belongs to JLCEDA/EasyEDA.

Running the SPICE suite

The suite reads the netlists that build.py writes, so run it after a schematic change. It needs ngspice 42 or newer and pytest, numpy and scipy (docs/simulation.md):

sh
python3 -m pytest hardware/sim -q                  # everything
python3 -m pytest hardware/sim -q -m "not slow"    # skip the long Monte Carlo sweeps

It writes hardware/sim/results/report.md and results.json. A check that shows a real design problem is a strict xfail with the reason, so it cannot quietly start passing.

Status and next steps

  • Done: subsystem research with independent verification. Architecture (SPEC.md). Generator toolchain. Schematics of both boards, ERC-clean apart from the one documented build.py waiver. Both boards placed and routed (layout.py: SPEC §8 placement of the constrained parts, clustering of the rest, Freerouting, then the grid router finish_route.py), DRC clean with schematic parity. The fab-output script release.py, which writes Gerbers, the JLCPCB BOM and CPL (rotations corrected per LCSC part), ORDER-NOTES.md, PDFs and renders behind a DRC and schematic-parity gate, and its outputs for both boards in hardware/fab/. The placement fixes from the mechanical model's clash check (module u.FL facing outward, link connector J1 at r 19.5, the 2.95 mm SM04B battery connector, R42 clear of the cover skirt), after which the check reports only the intended contacts. The SPICE suite in hardware/sim/ and the part changes it drove (VBUS series resistor R12, a second USB ESD array U12 at the module, the OLED load switch's QOD tied to its output, R4 68 Ω for the LED rail discharge, 25 V VSYS caps, base C1 and R4). Assembly, bring-up, mechanical, simulation and firmware-plan documents.
  • In progress: the SPEC items in mechanical §3A. The remaining strict xfails of the SPICE suite are firmware-dependent rules (written into firmware.md and SPEC §5) or accepted rev-A risks, such as the USB ESD margin with a short harness, the actuator thrust and the OLED boost's FB-bias spread (docs/simulation.md).
  • Not done: no prototype has been built or tested, and no firmware code has been written. Every item marked "to verify on prototype" in the docs is still open. The detent actuator's thrust margin is open (architecture risk R4).
  • Next:
    • close the pre-order checks in assembly §2.4 and §13 (LED-cavity silk, boost-inductor spacing), then order both boards with the outputs in hardware/fab/main/ and hardware/fab/base/: two-sided assembly for the main board, top side only for the base board (4-layer, ENIG), the MDBT50Q through JLC Global Sourcing or consignment, the DNP list from ORDER-NOTES.md;
    • a printed mechanical mock-up to check the antenna shelf, the harness and coax path and the ring's ESD bleed path, and to measure the RF antenna (VNA/RSSI), spin-down and detent feel, plus a bench test of the stepper's free-mode cogging before the PCB order;
    • bring-up of the first prototype (docs/bringup.md) and the items to verify on it;
    • firmware, following docs/firmware.md.
  • Known issues and open decisions are tracked in architecture §10–§13 and assembly §13.

Long-lead parts to order early: the SSD1351 OLED panel (0 stock at major distributors in Oct 2026), the MDBT50Q-U1MV2 module (0 stock at JLC: use JLC Global Sourcing or consign it) and the Molex 146153-0100 antenna (DigiKey/Mouser; LCSC stocks only the -0050, which is too short).

Parts

209
QtyValueFootprintReferences
62100nCapacitor_SMD:C_0402_1005MetricC11, C101, C102, C103, C104, C105, C106, C107, C108, C109, C110, C111, C112, C113, C114, C115 +46
48WS2812C-2020-V6LED_SMD:LED_WS2812B-2020_PLCC4_2.0x2.0mmD101, D102, D103, D104, D105, D106, D107, D108, D109, D110, D111, D112, D113, D114, D115, D116 +32
6100kResistor_SMD:R_0402_1005MetricR3, R25, R41, R42, R56, R70
6Fiducial_1mm_Mask2mmFiducial:Fiducial_1mm_Mask2mmFID1, FID2, FID3, FID4, FID5, FID6
44.7kResistor_SMD:R_0402_1005MetricR50, R51, R53, R54
310kResistor_SMD:R_0402_1005MetricR10, R11, R55
310uCapacitor_SMD:C_0603_1608MetricC3, C10, C53
310u/25VCapacitor_SMD:C_0603_1608MetricC2, C6, C41
31MResistor_SMD:R_0402_1005MetricR2, R20, R59
31uCapacitor_SMD:C_0402_1005MetricC33, C34, C51
322uCapacitor_SMD:C_0805_2012MetricC1, C8, C9
34.7u/25VCapacitor_SMD:C_0805_2012MetricC35, C38, C39
3SPINDLE_M2MountingHole:MountingHole_2.2mm_M2_Pad_ViaH1, H2, H3
210u/25VCapacitor_SMD:C_0805_2012MetricC30, C74
212p C0GCapacitor_SMD:C_0402_1005MetricC13, C14
2180p C0GCapacitor_SMD:C_0402_1005MetricC16, C17
21n/50VCapacitor_SMD:C_0402_1005MetricC7, C37
2470Resistor_SMD:R_0402_1005MetricR57, R58
2DRV8837DSGRPackage_SON:Texas_DSG0008A_WSON-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mmU40, U41
2MountingHole_2.2mm_M2MountingHole:MountingHole_2.2mm_M2H4, H5
2TPS22917DBVRPackage_TO_SOT_SMD:SOT-23-6U2, U21
1+3V3TestPoint:TestPoint_Pad_D1.0mmTP2
10Resistor_SMD:R_0402_1005MetricR43
1100n/50VCapacitor_SMD:C_0402_1005MetricC31
110uInductor_SMD:L_Sunlord_SWPA3015SL20
12.2MResistor_SMD:R_0402_1005MetricR1
1220Resistor_SMD:R_0402_1005MetricR40
132.768k 9pFdial_spinner:Crystal_SII_SC-20S_2.0x1.2mmY1
133p C0GCapacitor_SMD:C_0402_1005MetricC40
14.7Resistor_SMD:R_0402_1005MetricR12
14.7u/16VCapacitor_SMD:C_0603_1608MetricC12
14.7u/25V TaCapacitor_Tantalum_SMD:CP_EIA-3216-18_Kemet-AC32
147kResistor_SMD:R_0402_1005MetricR52
1499k 0.5%Resistor_SMD:R_0402_1005MetricR26
151R 1/4WResistor_SMD:R_1206_3216MetricR22
152.3k 0.1%Resistor_SMD:R_0402_1005MetricR27
1560kResistor_SMD:R_0402_1005MetricR21
168Resistor_SMD:R_0402_1005MetricR4
1B5819WDiode_SMD:D_SOD-123D21
1BAV99Package_TO_SOT_SMD:SOT-23D20
1DRV5032FADBZRPackage_TO_SOT_SMD:SOT-23U42
1GNDTestPoint:TestPoint_Pad_D1.0mmTP4
1LED_D48TestPoint:TestPoint_Pad_D1.0mmTP40
1LINKConnector_JST:JST_SH_SM12B-SRSS-TB_1x12-1MP_P1.00mm_HorizontalJ1
1LIS2DW12TRPackage_LGA:LGA-12_2x2mm_P0.5mmU31
1MDBT50Q-U1MV2dial_spinner:Raytac_MDBT50Q_UFLU10
1MOTORConnector_JST:JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_HorizontalJ40
1NFC_COILdial_spinner:NFC_Coil_Pads_1x02_P2.0mmJ11
1OLED_ZIF30dial_spinner:JUSHUO_AFC01-S30FCA-00_1x30-1MP_P0.50mm_HorizontalJ20
1P0.03TestPoint:TestPoint_Pad_D1.0mmTP10
1P0.04TestPoint:TestPoint_Pad_D1.0mmTP11
1P1.11TestPoint:TestPoint_Pad_D1.0mmTP12
1P1.13TestPoint:TestPoint_Pad_D1.0mmTP13
1PKMCS0909E4000-R1dial_spinner:Buzzer_Murata_PKMCS0909E4000-R1BZ1
1RT9080-33GJ5Package_TO_SOT_SMD:SOT-23-5U1
1SN74LV1T34DBVRPackage_TO_SOT_SMD:SOT-23-5U43
1SWDConnector:Tag-Connect_TC2030-IDC-NL_2x03_P1.27mm_VerticalJ10
1TMAG5273A2QDBVRPackage_TO_SOT_SMD:SOT-23-6U30
1TPD4E05U06DQARPackage_SON:USON-10_2.5x1.0mm_P0.5mmU32
1TPS61040DBVRPackage_TO_SOT_SMD:SOT-23-5U20
1USBLC6-2SC6Package_TO_SOT_SMD:SOT-23-6U12
1VLEDTestPoint:TestPoint_Pad_D1.0mmTP3
1VSYSTestPoint:TestPoint_Pad_D1.0mmTP1
1W25Q128JVSIQPackage_SO:SOIC-8_5.3x5.3mm_P1.27mmU11

More from tyevco/dial-me-spinner

1
  • 4 layers57.6 × 46 mmDial me spinner base, the printed circuit board in 3D

    Dial me spinner base

    tyevco/dial-me-spinner

    A Bluetooth dial spinner about 70 mm across, built on the nRF52840. A heavy, balanced stainless flywheel ring spins around a stationary centre that holds a colour OLED and a ring of 48 addressable RGB LEDs.

    25 parts

Similar boards

  • 4 layers220 × 120 mmCandidate filled profile, the printed circuit board in 3D

    Candidate filled profile

    misko/prompt2device

    The separate vNext PCB skill and orchestrator package is available alongside the reference workflow below; existing projects keep their accepted toolchain until an explicit migration.

    569 parts3MIT
  • 2 layersG Love, the printed circuit board in 3D

    G Love

    AlexEsqu/G-Love

    [G-Love] est un projet de conception d’un système de vêtement électronique (wearable) porté sur les bras., intégrant des bandes LEDSs RGB réactives à l’environnement et aux mouvements du porteur.

    57 parts2
  • 2 layers59 × 29 mmTable display, the printed circuit board in 3D

    Table display

    yeyintlwin/kicad-board-design

    Design a two-layer KiCad PCB entirely from Python scripts - no GUI step, no commercial autorouter

    53 partsMIT
  • 2 layers36 × 38 mmDigicron, the printed circuit board in 3D

    Digicron

    devicefuture/digicron

    Software and hardware for the DigiCron smartwatch.

    56 parts2MIT
  • 2 layers22 × 39 mmPhilly Node, the printed circuit board in 3D

    Philly Node

    tasmaniandroid/Philly_Node

    The start of a Philly-Centric mesh node.

    77 parts2CERN-OHL-S-2.0
  • 2 layers148 × 30 mmUi board 1u, the printed circuit board in 3D

    Ui board 1u

    betz-engineering/ui_board_1u

    User interface PCB for 1U chassis front-panels

    85 parts1CERN-OHL-P-2.0
  • 4 layers40 × 40 mmSatellite, the printed circuit board in 3D

    Satellite

    ciborg971/KISSE-1

    A homebuilt aicraft made with the same philosophy as the linux kernel ... and electric

    116 parts1MIT
  • 2 layers40 × 27.6 mm1.02inch right, the printed circuit board in 3D

    1.02inch right

    shiqi-614/ErgoCai.modules

    25 parts

Questions

What parts does the Dial me spinner main use?

209 parts, built around the MDBT50Q-U1MV2, DRV8837DSGR-MS, TPS61040DBVRG and W25Q128JVSIQ. 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.

Where can I download the Dial me spinner main KiCad files?

From the tyevco/dial-me-spinner repository on GitHub, at the commit this page was made from: the board is hardware/kicad/main/dial-me-spinner-main.kicad_pcb and its schematic hardware/kicad/base/base.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Dial me spinner main design in my own project?

Its repository states no licence, so its author keeps all rights: ask tyevco before reusing the design.