This repository contains the source code (Master, Slave, and Flasher) for a 24-dial kinetic clock.
For the complete project description, construction details, hardware, and PCB files, please refer to the project on Hackaday.io: [HACKADAY.IO PROJECT]
The system consists of three main software components:
- Master (ESP32): The code in
Master/runs on an ESP32. It manages network connectivity, time synchronization, and command logic for controlling the Slave boards. - Slave (x2) (Arduino Mega): The code in
Slave/runs on two Arduino Mega 2560 boards. This code receives commands and handles low-level motor control. - Flasher (ESP32): The code in
Flasher/is a standalone tool that runs on an ESP32 and is used to flash new firmware onto the Slave boards via serial communication using the STK500 protocol.
- WiFi and Time Synchronization: Connects to a WiFi network (credentials in
common/CommonConfig.h) and uses theezTimelibrary to get the current time via NTP. - TCP Server (Telnet): The
DualLoggerlibrary starts a TCP server on port 23. This acts as a command-line interface to receive commands from a Telnet client or the 24Client Android app. - Structured Protocol: All responses follow a structured format with prefixes:
OK <command>— Command executed successfullyERR <command> <message>— Command failed with error detailsLOG <text>— Informational log messageSTATUS <key>=<value>— Status information (from DEBUG command)POS <slaveId> <values>— Hand position data (24 comma-separated floats per slave)
- Available Commands:
SETTIME=HHMM— Set the display time manuallySETNTP— Sync time via NTP and update display immediatelySETHOME— Move all hands to home position (0°)SETZERO— Set all hands to "0000" displayRESETHOME— Reset the current position reference to match a given timeFINETUNE=R,C,L,D— Fine-tune a specific hand (Row, Column, hand/Lance, Degrees offset)GETPOS— Request current hand positions from both slavesSETLED=0/1— Turn slave LEDs off/onDEBUG— Print system status (uptime, time mode, offsets, current time)UPTIME— Print system uptimeECHO— Echo testQUIT— Disconnect the current Telnet client
- Command Logic: The main loop checks the time every cycle. When the minute changes, it formats a command string and sends it to both Slaves via the
serialLinklibrary. - OTA Support: Supports Over-The-Air (OTA) updates for wireless firmware flashing.
- Division of Tasks: The system is divided vertically. The
slaveOffsetvariable defines which side a Slave controls (0 for Slave 1/Left, 1 for Slave 2/Right). This value is set dynamically by the Master at runtime via aSETSLAVEOFFSETcommand, allowing the same firmware to be used on both boards.- Slave 1 (Left): Manages the H1 and M1 digits.
- Slave 2 (Right): Manages the H2 and M2 digits.
- Command Reception: Listens on the
Serialport for commands sent from the Master. - Motor Control: Uses the
SwitecX12library to drive the stepper motors. The current version moves the motors at a constant speed. For the very outdated version with acceleration support, please refer to the taglast-commit-with-accel-support. - Font Matrix (ClockPositions.h): The heart of the visual logic. It contains an array that maps each digit (0-9) to a set of angles (0, 90, 180, 270) for the 12 hands that make up that specific digit.
- Pin Mapping (ClockPins.h): Defines the GPIO pins of the Arduino Mega used to drive the motor drivers.
- EEPROM Position Persistence: After every hand movement, once all motors have stopped, the Slave saves the current positions to EEPROM using a wear-leveling ring buffer with CRC validation. On boot, positions are restored from EEPROM, so the system retains hand calibration across power cycles (as long as the Slave doesn't lose power — see the UPS section on the Hackaday project page).
- AVR Programming Tool: The Flasher is a standalone utility that programs the ATmega2560 Slave boards using the STK500 protocol over serial communication.
- Flashing Process: The process starts automatically upon boot.
- OTA Support: The Flasher supports Over-The-Air (OTA) updates, allowing you to update the Flasher firmware (and thus the embedded Slave firmware) wirelessly.
The project relies on a few external libraries and some internal ones located in the lib/ directory.
ezTime: Used by the Master for NTP time synchronization.
DualLogger: (lib/DualLogger/) A shared library that provides a unified interface for logging to both Serial and Telnet (port 23). Supports a single Telnet client connection withdisconnectClient()to forcefully drop a session. Used by both Master and Flasher.serialLink: (lib/serialLink/) A custom library for handling serial communication between the Master and Slaves.SwitecX12: (Slave/SwitecX12.*) A library for controlling the X12 stepper motors. Note: This is included directly in the Slave sketch folder. The library is based on the original work by [Guy Carpenter (Clearwater Software, 2017)] and has been modified for the specific needs of this project.avr_flash_arduino: (Flasher/avr_flash_arduino.*) A library implementing the STK500 protocol to flash ATmega2560 boards. Based on Laukik Hase's work.
- Software Configuration:
- Open the
Master/Master.inoandSlave/Slave.inosketches in the Arduino IDE. - Install the required external libraries (e.g.,
ezTime) using the Library Manager. - In
common/CommonConfig.h, configure your WiFi credentials.
- Open the
- Upload Code to Slaves:
- Method 1 (Direct Upload): Upload
Slave.inodirectly to both Arduino Mega boards using a USB cable. - Method 2 (Using the Serial Flasher): This method uses a dedicated ESP32 to program the Slave boards without needing to connect them to a computer. (See the Build Automation section below for details).
- Method 1 (Direct Upload): Upload
- Upload Master:
- Upload
Master.inoto the ESP32 board.
- Upload
- Homing Prerequisite:
- This code operates "open-loop", meaning it assumes the initial position of the hands and cannot verify it.
- Before starting the system, it is mandatory to manually position all 48 hands to the "home" position (vertical, 12 o'clock). The
SETHOMEcommand in the software will move the hands to 0°, which must correspond to this physical position.
- Operation:
- On startup, the Master will connect to WiFi, synchronize the time via NTP, and start sending commands to the Slaves.
- You can connect to the Master's IP address via a Telnet client (port 23) to send commands manually.
The repository includes a Python script, build.py, to automate the entire build and deployment process for both the Flasher and the Master firmware. It handles compilation, header generation, uploading (via Serial or OTA), and monitoring (via Serial or Telnet).
Prerequisites:
- Python 3
arduino-cliinstalled and configured.- Required Python packages (standard library only).
Configuration:
Edit the build.py file to set your environment variables:
SERIAL_PORT: The serial port (e.g.,/dev/tty.usbserial-1310) or IP address (e.g.,192.168.1.100) of the target device.OTA_PASSWORD: Password for OTA updates (if enabled).BAUD_RATE: Baud rate for serial communication (default: 115200).
Usage: Run the script from the terminal:
./build.py [target]Targets:
flasher:- Compiles
Slave.inofor ATmega2560. - Converts the hex file to
firmware_slave.h. - Compiles
Flasher.ino(embedding the slave firmware). - Uploads to the ESP32.
- Monitors the output (Serial or Telnet) until success/failure.
- Compiles
master:- Compiles
Master.ino. - Uploads to the ESP32.
- Monitors the output (Serial or Telnet).
- Compiles
full:- Runs the
flashertarget first. - Waits for completion.
- Runs the
mastertarget.
- Runs the
monitor:- Connects to the device (via Serial or Telnet) and streams the logs. Useful for debugging without re-uploading.
Monitoring:
The script automatically detects if SERIAL_PORT is an IP address or a serial device:
- Serial: Opens
arduino-cli monitor. - IP (OTA): Connects via Telnet (port 23) to stream logs directly from the device. It handles connection retries and timeouts automatically.