Skip to the content
PCBKitNew board
4 layers54 × 54 mmSmartknob driver, the printed circuit board in 3D

Smartknob driver

aryanthomare/SmartKnob

A programmable force-feedback knob for your computer. Custom motor-driver PCB, closed-loop haptic firmware, a native Windows companion app and a parametric 3D-printed enclosure, all designed in house.

Size
54 × 54 mm
Copper layers
4
Thickness
1.6 mm
Parts
69
Nets
75
Pads
310
Tracks, vias
891, 130
Pours
9
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
10 Oct 2026
Updated
10 Oct 2026

Overview

Smartknob driver is an open-source printed circuit board by aryanthomare, from the aryanthomare/SmartKnob repository on GitHub. It is a 4-layer board of 54 × 54 mm with 69 parts on 75 nets, routed with 891 tracks and 130 vias.

It is built around the ESP32-S3-MINI-1-N8, DRV8316CRRGFR and TPS54202DDCR, with the AP2112K-3.3TRG1, USBLC6-2SC6 and B5819W among its other parts. Its parts include 25 capacitors, 20 resistors, 5 ICs, 5 diodes and 5 connectors. It belongs with the ESP32 boards of this library.

Main components

10

README

README.md

SmartKnob

A programmable force-feedback knob for your computer. Custom motor-driver PCB, closed-loop haptic firmware,
a native Windows companion app and a parametric 3D-printed enclosure, all designed in house.

3D render of the SmartKnob driver PCB SmartKnob desktop panel in dark mode

A brushless gimbal motor sits under the knob. By driving it with the right torque at every angle, the firmware makes the same physical knob feel like a stepped volume dial, a smooth scroll wheel or a switch with hard end stops, and changes that feel on the fly as you switch tasks on the PC. The knob talks to a small Windows tray app over USB or Bluetooth LE, and works on its own as a media volume knob when the app isn't running.

Contents

Features

  • Software-defined feel. Detent strength, steps per degree and spring end stops are pushed from the PC per mode.
  • Exact volume, not key nudges. With the app running, the knob sets system or per-app volume to the percent, and follows changes made elsewhere (keyboard, Windows mixer) so the dial never drifts.
  • Five modes: system volume, volume of the focused app, scroll, zoom, and left/right arrow keys.
  • Wired or wireless. USB-C, or Bluetooth LE from the 12 V barrel jack alone. The app switches between them automatically and prefers the cable.
  • Works without the app as a USB or Bluetooth HID volume knob.
  • Liquid Glass style UI rendered in software by a 280 KB native executable that uses no CPU while idle.

System overview

mermaid
flowchart LR
  subgraph Knob["Knob (ESP32-S3 firmware)"]
    ENC["AS5600 encoder<br/>12-bit, I²C"] --> CTRL["Control loop ~4.7 kHz<br/>FOC torque + haptic engine"]
    CTRL --> DRV["DRV8316 3-phase driver<br/>→ gimbal BLDC motor"]
    CTRL <--> PROTO["Line protocol<br/>HELLO · CFG · VAL · PING"]
    CTRL --> HID["HID volume keys<br/>(standalone)"]
  end
  PROTO <-- "USB CDC serial" --> APP
  PROTO <-- "BLE Nordic UART" --> APP
  HID -- "USB HID / HID-over-GATT" --> WIN["Windows"]
  subgraph PC["Windows PC"]
    APP["SmartKnob.exe<br/>tray app"] --> CA["Core Audio<br/>system + per-app volume"]
    APP --> SI["SendInput<br/>scroll · zoom · arrows"]
  end

The haptics always run on the knob, so the feel never waits on the PC. The app only sends a haptic config (range, degrees per step, detent strength, wall stiffness) when the mode changes, and receives value changes back.

Hardware: the driver PCB

Top side render Bottom side render

A 54 × 54 mm, four-layer board designed in KiCad. The motor's axis sits at the board centre, the AS5600 encoder board stacks above it on M3 standoffs, and the motor mounts on top of that.

BlockPartNotes
MCUESP32-S3-MINI-1-N8Native USB (no USB-UART bridge), BLE 5, 8 MB flash
Motor driverTI DRV8316CR3-phase, integrated FETs, SPI config, 3× PWM mode, current-sense outputs
EncoderAS5600 breakout12-bit magnetic angle over I²C, on its own board under the motor
Motor34.5 mm gimbal BLDC7 pole pairs (measured during bring-up)
12 V inputDC barrel jackP-MOSFET reverse-polarity block, 15 V gate clamp, SMAJ16A TVS
5 VTI TPS54202 buck12 V → 5.08 V, diode-ORed with USB VBUS
3.3 VAP2112K LDOMCU, encoder and breakout header
USB-CHRO TYPE-C-31-M-12500 mA polyfuse, USBLC6 ESD protection
MiscJST-PH motor and encoder connectors, 2×5 breakout header, BOOT/RESET buttons, VM voltage sense
mermaid
flowchart LR
  J1["12 V barrel"] --> Q1["Reverse-polarity<br/>P-MOSFET + TVS"] --> VM["+12 V (VM)"]
  VM --> DRV["DRV8316<br/>motor supply"]
  VM --> BUCK["TPS54202 buck<br/>→ 5.08 V"] --> D4["Schottky"] --> V5["+5 V"]
  USB["USB-C VBUS"] --> F1["Polyfuse"] --> D3["Schottky"] --> V5
  V5 --> LDO["AP2112K"] --> V33["+3.3 V<br/>MCU · encoder"]
  VM --> SENSE["÷11 divider → GPIO7<br/>(VM sense)"]

The two 5 V sources are diode-ORed, so the logic runs from either USB or the barrel jack. Motor power only comes from the barrel jack, so on USB alone the knob still reads the encoder and talks to the PC, and the firmware turns haptics off until 12 V is present.

Layer stack. Signals on the outer layers, a solid ground plane on inner 1 under everything, and inner 2 split between the 12 V motor supply and 3.3 V. The DRV8316's thermal pad drops straight into the ground plane through a via array, and the ESP32 antenna overhangs a copper keep-out.

The four copper layers

Schematic sheets (hierarchical: power, driver, MCU, connectors)

The design notes on each sheet record the datasheet decisions: feedback divider values, which DRV8316 pins may not float, strapping pins to avoid, ADC1-only analog inputs because ADC2 is unusable with the radio on, and so on.

Power Power sheet

Motor driver DRV8316 sheet

MCU ESP32-S3 sheet

Connectors Connectors sheet

Full PDF: hardware/smartknob_driver/smartknob_driver_schematic.pdf

Fabrication. hardware/smartknob_driver/fab has Gerbers, drill files and a JLCPCB assembly BOM and placement file with LCSC part numbers, so the board can be ordered fully assembled.

Firmware

volume_knob/src/main.cpp · Arduino on ESP32-S3 via PlatformIO

Motor control. At power-up the firmware calibrates itself: it aligns the rotor with a fixed voltage vector to find the encoder direction and the electrical zero, then applies a test push to learn the torque sign. After that it runs voltage-mode field-oriented control: every loop it reads the encoder over 400 kHz I²C, computes the electrical angle, and puts the requested torque on the q-axis through 20 kHz PWM. The loop runs at about 4.7 kHz. Drive voltage is capped per supply (3 V on 12 V, 1.2 V when VM is low), and a DRV8316 fault or loss of motor power drops the knob into plain-encoder mode instead of driving blind.

Haptic engine. The knob tracks an integer value in [lo, hi] with one value every step degrees. Around each value a capped spring pulls the knob to the detent centre, and past either end a stiffer spring wall pushes it back, with damping on both. Hysteresis on the step boundary stops the value chattering.

Torque vs angle: sawtooth detents between linear end walls

Two ways to run.

  • Host mode: the app sends a haptic config per mode and the knob streams value changes back.
  • Standalone: with no app for 3 s, the knob becomes a media-key volume knob. HID can only nudge the volume, so on connect it drives Windows to 0 % and back up to a known level, unless the app reported the real level shortly before it went away.

Protocol. Plain text lines, identical over USB serial and Bluetooth:

DirectionMessageMeaning
PC → knobHELLOHandshake, answered with INFO name=SmartKnob fw=2 hap=<0/1>
PC → knobCFG lo= hi= val= step= det= wall=Range, current value, degrees per step, detent volts, wall stiffness
PC → knobVAL v=Move the value without moving the knob (volume changed elsewhere)
PC → knobPING vol=Keep-alive (3 s timeout) plus the PC's real volume, answered with PONG hap= vm=
knob → PCVAL v=The user turned the knob

Connectivity. USB enumerates as a composite device (HID consumer control + CDC serial) with its own product ID. Over Bluetooth LE the knob exposes two services on one connection: HID-over-GATT for the standalone volume keys and the Nordic UART Service for the protocol. Every message the firmware prints goes to both transports.

Desktop app

desktop/smartknob.cpp · C++17, Win32, GDI+, C++/WinRT

Volume mode, darkScroll mode, light
Volume popup
Volume popup shown while turning

A tray app with a floating panel for picking the mode and click strength, and a volume popup that appears while you turn the knob.

  • Volume: sets the Windows master volume exactly through Core Audio (IAudioEndpointVolume), and listens for changes made elsewhere so the knob's haptic position stays in sync. It tags its own writes so it can ignore them.
  • App volume: follows the foreground window (SetWinEventHook) and adjusts every audio session of that process.
  • Scroll / Zoom / Arrow keys: relative modes that emit wheel events, Ctrl+wheel or ←/→ through SendInput, each with its own detent feel.
  • Connection: finds the knob by USB ID and opens it with overlapped I/O on a reader thread. If there's no cable it connects to the paired knob's GATT service with C++/WinRT on a worker thread, and moves to USB as soon as a cable appears. The UI thread never blocks on either.

Liquid Glass material, in software. The panel and popup follow Apple's Liquid Glass guidelines without any GPU framework:

  • The screen behind the window is captured, downsampled, saturated and box-blurred. The windows exclude themselves from capture, so they never see their own pixels.
  • Each pixel then gets a lens offset from the shape's signed distance field, so content bends at the rim, with slight chromatic dispersion, a specular rim lit from the top left, an adaptive tint and a soft shadow.
  • The small popup flips light or dark with the luminance behind it, and corners are concentric (inner radius = outer radius − padding).
  • It honours Windows dark mode, transparency effects, animation settings and high contrast.
  • It only re-captures while a window is visible, and only re-renders when what's behind it changed: about 1 % of one core with the panel open, nothing when hidden.

Footprint: one 280 KB statically linked executable, no runtime to install, about 7 MB of memory, 0 % CPU idle. Settings live in %APPDATA%\SmartKnob\settings.ini.

Enclosure

Enclosure assembled Enclosure exploded

enclosure/smartknob_enclosure.fs · Onshape FeatureScript

The whole enclosure is a single code-defined Onshape feature, so changing a standoff height or the knob fit is one constant and a rebuild.

  • Cover: one printed piece. A side profile (flat deck over the motor, rounded slope down to a low nose on the cable side) is intersected with the plan shape, filleted along the top and shelled from the open bottom. It has cut-outs for the motor, barrel jack, USB-C plug and paper-clip access to BOOT/RESET.
  • Base plate: a locating lip, and counterbored M3 holes on the board's 24 mm mounting square.
  • Encoder spacers: printed rings that lift the AS5600 board clear of the barrel jack.
  • Knob cap: 40 mm, 40 grip flutes and a pointer dimple. Four pegs press-fit into the rotor's M2.5 holes, with peg diameter as a printer-tuning constant.
  • Reference stack: the populated PCB (built from the KiCad 3D models' bounding boxes, in the same coordinate frame as the KiCad STEP export), the encoder board, standoffs and motor.
  • Self-checks: the feature measures the distance between the enclosure and everything inside it, warns on interference and reports the minimum clearance.

How it was built

The whole stack was designed in house and brought up one layer at a time.

  1. Board design. A hierarchical schematic in KiCad with datasheet decisions written onto each sheet, then a four-layer layout around a centred motor axis, with the JST connectors on the cable side and the power entry on the opposite edge. Exported Gerbers, drill files and a JLCPCB assembly BOM and placement file.
  2. Encoder bring-up. encoder_test scans the I²C bus and streams raw angle, magnet status and AGC to confirm the magnet height.
  3. Motor bring-up. motor_test brings up the DRV8316 over SPI and checks supply and faults. It steps through the phases while watching the rotor, sweeps electrical revolutions to measure the pole pairs (7), and spins the motor open-loop in both directions before any closed-loop code runs.
  4. Enclosure. Iterated in Onshape around a STEP export of the real board. Parameters live as constants rather than feature-dialog inputs, so pushing new code never invalidates the feature.
  5. Firmware. Closed-loop FOC and the generic haptic engine, first as a standalone USB volume knob, then with the host protocol.
  6. Desktop app. First prototyped in Python, then rewritten as a native Win32 executable to keep the memory and CPU footprint negligible.
  7. Bluetooth. Added as a second transport carrying the same protocol, so the knob runs from the barrel jack alone.

Problems found on real hardware

  • Port mix-up. The knob first enumerated with Espressif's default USB product ID, the same one as the chip's built-in flashing port, which the app deliberately never opens. The firmware now sets its own PID. Because the Arduino core starts USB before setup() when CDC-on-boot is enabled, that meant turning CDC-on-boot off and owning the serial object.
  • Buzzing at the end stops. After the knob was released past 0 % or 100 %, the wall spring returned it to rest about 1° past the end, where friction holds it. There the wall's damping term was amplifying one-LSB encoder jitter, which differentiates to tens of degrees per second, into ±0.25 V of noise. That was three times the spring force, so the motor sat buzzing between −0.32 V and +0.17 V. A heavier velocity filter plus a 30 °/s deadband on the damping fixed it: the motor now holds a steady −0.07 V. A test command that shifts the knob's reference point made the bug reproducible on the bench without touching the knob.

Building and flashing

Firmware (PlatformIO):

sh
cd volume_knob
pio run -t upload        # first flash goes through the chip's USB-Serial/JTAG port (COM3 in platformio.ini)

Once the firmware is running, the board shows up as 303A:4B4E (HID + serial) and the flashing port disappears. To reflash, choose Disconnect for firmware update in the app's tray menu, then open the knob's COM port at 1200 baud to reboot into the bootloader. Holding BOOT while plugging in also works.

Desktop app (Visual Studio with the C++ workload):

bat
desktop\build.bat
desktop\SmartKnob.exe

SmartKnob.exe --snapshot <dir> renders the panel and popup over the current screen to PNGs and exits. The UI images in this README were made that way.

Bluetooth: pair "SmartKnob" once in Windows Settings → Bluetooth. Windows caches the knob's services, so after changing them in firmware, remove the device and pair again.

Repository layout

text
hardware/smartknob_driver/   KiCad project: schematic, 4-layer PCB, custom symbols/footprints, fab outputs
volume_knob/                 Main firmware: FOC, haptic engine, USB + BLE protocol, HID
encoder_test/                Bring-up: AS5600 over I²C
motor_test/                  Bring-up: DRV8316 + open-loop motor tests
desktop/                     Windows companion app (single C++ file + build script)
enclosure/                   Onshape FeatureScript enclosure + board STEP export
docs/images/                 README images (renders, schematic sheets, app snapshots)

Parts

69
QtyValueFootprintReferences
510kResistor_SMD:R_0402_1005MetricR1, R12, R14, R20, R21
4100nFCapacitor_SMD:C_0402_1005MetricC4, C10, C28, C42
4100nF 50VCapacitor_SMD:C_0603_1608MetricC3, C20, C21, C22
4M3MountingHole:MountingHole_3.2mm_M3H1, H2, H3, H4
322pF C0GCapacitor_SMD:C_0402_1005MetricC43, C44, C45
322uF 25VCapacitor_SMD:C_0805_2012MetricC5, C6, C26
3330RResistor_SMD:R_0402_1005MetricR22, R23, R24
35.1kResistor_SMD:R_0402_1005MetricR11, R30, R31
2100k 1%Resistor_SMD:R_0402_1005MetricR2, R4
210uFCapacitor_SMD:C_0603_1608MetricC9, C41
210uF 50VCapacitor_SMD:C_1206_3216MetricC2, C23
21uFCapacitor_SMD:C_0402_1005MetricC8, C40
21uF 50VCapacitor_SMD:C_0603_1608MetricC24, C27
24.7k DNPResistor_SMD:R_0402_1005MetricR32, R33
2B5819WDiode_SMD:D_SOD-123D3, D4
1100kResistor_SMD:R_0402_1005MetricR13
1100uF 35VCapacitor_SMD:CP_Elec_6.3x7.7C1
110k 1%Resistor_SMD:R_0402_1005MetricR5
113.3k 1%Resistor_SMD:R_0402_1005MetricR3
115uHInductor_SMD:L_Sunlord_SWPA5040SL1
11kResistor_SMD:R_0402_1005MetricR6
122RResistor_SMD:R_0603_1608MetricR10
147nF 50VCapacitor_SMD:C_0603_1608MetricC25
1500mA PTCFuse:Fuse_0805_2012MetricF1
175pF C0GCapacitor_SMD:C_0402_1005MetricC7
1AP2112K-3.3TRG1Package_TO_SOT_SMD:SOT-23-5U2
1BOOTButton_Switch_SMD:SW_Push_1P1T_XKB_TS-1187ASW2
1BREAKOUT 2x5Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_VerticalJ5
1BZT52C15Diode_SMD:D_SOD-123D1
1DC-005C-20A (5.5x2.1)smartknob:BarrelJack_Hroparts_DC-005C-20A_HorizontalJ1
1DMP3099L-7Package_TO_SOT_SMD:SOT-23Q1
1DRV8316CRRGFRsmartknob:TI_RGF0040E_VQFN-40-1EP_5x7mm_P0.5mm_EP3.7x5.7mm_ThermalViasU3
1ENCODER (S4B-PH-K-S)Connector_JST:JST_PH_S4B-PH-K_1x04_P2.00mm_HorizontalJ3
1ESP32-S3-MINI-1-N8RF_Module:ESP32-S2-MINI-1U4
1MOTOR (S3B-PH-K-S)Connector_JST:JST_PH_S3B-PH-K_1x03_P2.00mm_HorizontalJ4
1RESETButton_Switch_SMD:SW_Push_1P1T_XKB_TS-1187ASW1
1Red (PWR)LED_SMD:LED_0603_1608MetricD5
1SMAJ16ADiode_SMD:D_SMAD2
1TPS54202DDCRPackage_TO_SOT_SMD:SOT-23-6U1
1TYPE-C-31-M-12Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12J2
1USBLC6-2SC6Package_TO_SOT_SMD:SOT-23-6U5

Questions

How big is the Smartknob driver PCB?

It measures 54 × 54 mm and has 4 copper layers, 1.6 mm thick.

What parts does the Smartknob driver use?

69 parts, built around the ESP32-S3-MINI-1-N8, DRV8316CRRGFR, TPS54202DDCR and AP2112K-3.3TRG1. 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 Smartknob driver KiCad files?

From the aryanthomare/SmartKnob repository on GitHub, at the commit this page was made from: the board is hardware/smartknob_driver/smartknob_driver.kicad_pcb and its schematic hardware/smartknob_driver/connectors.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Smartknob driver design in my own project?

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

Similar boards

  • 4 layers38.5 × 29 mmPl60c dmx mini, the printed circuit board in 3D

    Pl60c dmx mini

    tsmiltonAgents/pl60c-dmx-wakeup

    A small ESP32-S3 board that drives the wired DMX512 input of a Neewer PL60C panel light so the light can be ramped up slowly in the morning (sunrise alarm). Rev A, 2026-09-02.

    37 parts
  • 2 layers80 × 34 mmDji fcc, the printed circuit board in 3D

    Dji fcc

    nikolaus018/dji-fcc-esp32

    ESP32-S3 firmware that switches a DJI N1 remote to FCC mode over USB (replaces the Android app)

    69 parts3MIT
  • 2 layers234.5 × 120.8 mmSmk kbd devkit, the printed circuit board in 3D

    Smk kbd devkit

    mburger89/SMK_Keyboard_DevKit

    59-key hot-swap mechanical keyboard PCB carrier for a socketed ESP32-C6-DevKitC-1 dev board — KiCad project generated/routed by Python scripts

    148 parts
  • 2 layers87.9 × 87.5 mmCreative controller, the printed circuit board in 3D

    Creative controller

    ZMGID/grokbot-home

    Grok Bot account migration home (secrets encrypted)

    59 parts
  • 4 layers40 × 40 mmSnowboard fc, the printed circuit board in 3D

    Snowboard fc

    RotemCarr/snowboard-fc

    Snowboard flight controller hardware and software stacks - using the Zephyr framework

    103 parts
  • 2 layers149 × 65 mmFlight controller, the printed circuit board in 3D

    Flight controller

    DinosaurPotato534/Project-Albatross

    birb

    155 parts
  • 4 layers125 × 50.1 mmD lev io, the printed circuit board in 3D

    D lev io

    wormyrocks/d-lev-mini

    155 parts
  • 4 layers93 × 62 mmPIM V01, the printed circuit board in 3D

    PIM V01

    kpayinda/PIM_V01

    PIM PCB.

    260 parts