StackmatLink is an open-source project based on the ESP32-S3. It captures the analog Stackmat signals from a GAN Halo (and other standard timers) audio port and converts them in real-time into the GAN Smart Timer Bluetooth Protocol.
With this project, you can bridge any standard non-Bluetooth Stackmat timer to csTimer wirelessly, eliminating the need for expensive Bluetooth-native hardware.
- Protocol Conversion: Seamlessly converts Stackmat Gen4 (1200 Baud) signals into the official GAN Bluetooth protocol (including the
0xFEmagic packet and CRC16/CCIT-FALSE checksums). - Intelligent State Inference: Specifically handles the GAN Halo
'I'(Idle) state. It dynamically determinesRunningandStoppedstates based on the time axis to ensure compatibility with csTimer. - Dual-Core Concurrency:
- Core 1: Dedicated to high-precision signal sampling and parsing at 1200 Baud.
- Core 0: Handles the NimBLE Bluetooth stack and Notify updates for zero-latency data transmission.
- Automatic Phase Correction: Built-in logic to detect inverted signals (common in different LM393 wiring) and correct them on the fly.
- Enhanced Visibility: Optimized BLE advertising parameters for instant discovery on Mac/PC Chrome and iPad/iOS.
- Robust Connectivity: Implements a connection polling mechanism (
getConnectedCount) to ensure stable data sync even when Bluetooth callbacks are delayed.
- MCU: ESP32-S3 (Tested on N16R8, PCB uses ESP32-S3-SuperMini, but compatible with all S3 variants).
- Signal Conditioning: LM393 Voltage Comparator (SMD/SOP-8 version).
- Status LED: Onboard or external NeoPixel (WS2812) LED.
- Interface: 3.5mm Audio Jack (Tip: Signal, Sleeve: GND).
- Electronic Components (BOM):
- 10kΩ Resistor Network (or 3x 10kΩ SMD resistors: 1x pull-up, 2x voltage divider for 1.65V ref).
Breadboard wiring reference for LM393 and ESP32-S3.
| Component | ESP32-S3 Pin | Description |
|---|---|---|
| LM393 VCC | 3.3V | Power Supply |
| LM393 GND | GND | Common Ground |
| LM393 Output | GPIO 4 | Requires 10kΩ Pull-up to 3.3V |
| NeoPixel DI | GPIO 48 | Status LED (Onboard on SuperMini) |
| 3.5mm Tip | LM393 IN+ | Raw Timer Signal |
| Reference GND | LM393 IN- | 1.65V Ref (via Voltage Divider) |
Since the timer outputs a weak analog audio signal (sine-ish wave) with noise:
- Analog Stage: The signal (~0.7V–2.5V) enters the LM393
IN+. - Comparison: The LM393 compares it against the fixed
IN-(1.65V). - Digital Stage:
- Signal > 1.65V -> Output 3.3V.
- Signal < 1.65V -> Output 0V.
- Result: A clean digital square wave is generated for the ESP32 UART to parse.
Exploded view of the 3D printed case and internal assembly.
Top view of the custom PCB design.
You can find all design files in the hardware/ directory:
- PCB Design (
hardware/PCB/):Gerber_PCB1_...: Production-ready Gerber files.ProPrj_...: PCB design project file.
- 3D Printed Case (
hardware/Case/):stackmatlinkcaseprint final-case.001.stl,.002.stl: Ready-to-print STL files.stackmatlinkcaseprint final.blend: Original Blender source file.
- Wiring Guide (
hardware/Wiring/):Wiringconnection.png: Visual diagram for circuit connections.
If you plan to manufacture the custom PCB using the Gerber files in hardware/PCB/, you will need:
- ESP32-S3 SuperMini: The core controller.
- LM393 (SOP-8): SMD Voltage Comparator.
- 10kΩ Resistors: Suggested 0603 or 0805 package.
- 3.5mm Audio Jack: PJ-307 or equivalent 5-pin DIP socket.
- Install Arduino IDE.
- Install the
esp32(by Espressif) board package. - Install the
NimBLE-ArduinoandAdafruit_NeoPixellibraries via the Library Manager. - Open
stackmatlink.inoand select your ESP32-S3 board. - Note: If
USB CDC On Bootis enabled, use the Native USB port for serial debugging. - Click Upload.
- Power Up: Connect the ESP32-S3 to power and plug the audio cable into the timer.
- Verify Signal: Open Serial Monitor (115200 baud). Operate the timer.
- Note: If debug prints are disabled in code, you can verify activity by observing the NeoPixel LED (Blue = Running).
- Connect to csTimer:
- Go to csTimer.net (Chrome/Edge recommended).
- Settings -> Timer -> Entering Type -> Bluetooth Timer.
- Click the Bluetooth icon at the top of csTimer and pair with "GAN-Timer".
- If successful, the Serial Monitor will show
>> [BLE] onConnect callback triggered!.
- Go!: Your physical timer is now synced with the digital solve.
- No Data / Signal Issues:
- If the Serial Monitor shows GPIO level activity but no time is parsed, try toggling the
invertedparameter in thestackmatTaskcode or check your wiring. - LM393 Adjustment: Use a screwdriver to fine-tune the potentiometer on the LM393 until the signal triggers consistently.
- If the Serial Monitor shows GPIO level activity but no time is parsed, try toggling the
- Connectivity: Use Chrome (Windows/macOS/Android) or Bluefy (iOS) for the best Web Bluetooth experience.
- LED Status:
- Blue: Timer is running.
- White/Blinking: Timer is reset or ready.
Copyright (c) 2026 liusonwood. MIT License. Contributions and PRs are welcome!
- The
NimBLE-ArduinoAdafruit_NeoPixelcommunity. - The
csTimerproject and its excellent GAN Timer driver reference.
This project was conceptualized by me and implemented in 2 hours with the help of AI pair programming. 本项目由我构思,并在 AI 结对编程的辅助下耗时两小时实现。


