Kinetic Clock

Kinetic Clock

There’s something mesmerizing about motion-based clocks — where time is displayed not just as numbers, but as movement.
This is my take on a Kinetic Digital Clock — an Arduino-driven, 3D-printed build that uses mechanical motion to form digital digits.

Concept

Instead of showing time on a screen, this clock physically moves flaps to form the digits.
Each segment of the 7-segment display is a 3D-printed arm, driven by a small servo motor.
The result is a clock that feels alive — every minute it animates, morphing smoothly from one number to the next.

The idea came from wanting something that blends electronics, motion, and 3D printing — something more dynamic than an LED display.

Components

Each number (0–9) is made up of seven segments, just like a regular digital clock.
Every segment is a mechanical “bar” that pivots up or down using a micro servo.
The Arduino controls these servos using simple timing logic, updating the pattern every minute.

Component Example Amazon AliExpress
Controller Arduino Mega https://amzn.to/4nEtkK8 https://s.click.aliexpress.com/e/_c3mVtKft
Motor shield Arduino Mega Sensor Shield https://amzn.to/4oZwnOq https://s.click.aliexpress.com/e/_c3gWBKb1
30 servo motors SG90 https://amzn.to/43QqA5p https://s.click.aliexpress.com/e/_c3D5lkTv
RTC DS3231 https://amzn.to/43gtbp1 https://s.click.aliexpress.com/e/_c3mv2RUJ
Power supply 5v/20A https://amzn.to/3LnwlkM https://s.click.aliexpress.com/e/_c3xSP0rh
Battery CR2032 https://amzn.to/47q8zx5 https://s.click.aliexpress.com/e/_c39QeH3l
3D filament any type

3D Printing

The design is forked from

Kinetic & Digital Clock (Arduino + 3D Print)
Kinetic & Digital Clock (Arduino + 3D Print): This is a seven segment clock where instead of LEDs we have segments moving on the Z-axis of the viewing pane, the difference in depth by these segments will allow the viewer to see the time against the white on white segments. We use an Arduino mic…

and the box around the clock was made from scrap wood 18mm pieces i had laying around.

Print settings:

  • 0.2 mm layer height
  • 15–20% infill
  • No supports needed

Electronics

Servos connect to the Arduino Mega Sensor Shield through PWM pins
A DS3231 module keeps accurate time, even when power is disconnected.
Each servo receives:

  • Signal → Arduino PWM pin
  • Power → 5 V regulator
  • Ground → common GND

Eventually all motors should be connected to the 30 pins in this fashion

Assembly

Once printed, assembly is quite straightforward

💡
Pay attantion
If you plan to power Arduino+Shield+Motors from a single external power supply, you should keep the VCC-PWR jumper on the Shield (usually green jumper), but in that case, DO NOT connect the USB to the Arduino, in that case you might back-feed power back to your USB port.

If you plan to power only the Shield+Motor from external supply, and the Arduino from a USB - so make sure to REMOVE the VCC-PWR jumper.
  1. Mount the servos each of the back panels
  2. Connect the power supply to the Arduino board
14 AWG wires or thicker
make sure to remove the green jumper if you plan to power the lower Arduino via USB

Calibration

  1. Connect wires to the Arduino and RTC based on the diagram above
    The motors pins are connecting to the following pins on the shield
    {2, 3, 4, 5, 6, 7, 8}, // 1st digit
    {9, 10, 11, 12, 13, 14, 15}, // 2nd digit
    {22, 23, 24, 25, 26, 27, 28}, // 3rd digit
    {29, 30, 31, 32, 33, 34, 35} // 4th digit
  2. Calibrate the motors (if needed), then upload the logic sketch.
  3. Power it up — and watch time come alive!

Calibrate motors

If needed - you can calibrate the motors by uploading this sketch, then on serial monitor, each the needed motors to 0 degrees, insert the segment, then move the motor to 180 degrees (if your motors are 0-360, adjust accordingly)

#include <Servo.h>

#define NUM_SERVOS 30

// Store servo instances and pin mapping
Servo servos[NUM_SERVOS];
int servoPins[NUM_SERVOS] = {
  2, 3, 4, 5, 6, 7, 8, 
  9, 10, 11, 12, 13, 14, 15, 
  22, 23, 24, 25, 26, 27, 28, 
  29, 30, 31, 32, 33, 34, 35, 
  38, 39
};

void setup() {
  Serial.begin(9600);
  Serial.println("=== Servo Control Mode ===");
  Serial.println("Type: PIN DEGREE (example: 23 180)");
  Serial.println("Moves the servo on that pin to the given degree.");
  Serial.println();
}

void loop() {
  if (Serial.available()) {
    int pin = Serial.parseInt();     // Read the first integer (servo pin)
    int degree = Serial.parseInt();  // Read the second integer (angle)

    if (pin > 0 && degree >= 0 && degree <= 180) {
      int servoIndex = findServoIndex(pin);

      if (servoIndex >= 0) {
        Serial.print("Moving servo on pin ");
        Serial.print(pin);
        Serial.print(" to ");
        Serial.print(degree);
        Serial.println(" degrees...");

        // Create a temporary servo just for this action
        Servo tempServo;
        tempServo.attach(pin);
        delay(100);
        tempServo.write(degree);
        delay(800); // give servo time to move
        tempServo.detach();

        Serial.println("Done. Servo detached.");
      } else {
        Serial.println("Pin not found in servo list.");
      }
    } else {
      Serial.println("Invalid input. Use format: PIN DEGREE (e.g. 23 90)");
    }

    // Clear serial buffer
    while (Serial.available()) Serial.read();
  }
}

// Find servo index from pin number
int findServoIndex(int pin) {
  for (int i = 0; i < NUM_SERVOS; i++) {
    if (servoPins[i] == pin) return i;
  }
  return -1;
}

Coding

This code differs from the original one by using the newer `RTClib` library (by Adafruit), as well as adding a smooth motion between the transitions.

#include <Wire.h>
#include "RTClib.h"
#include <Servo.h>

RTC_DS3231 rtc;

// --- CONFIGURATION ---

const int DIGIT_PINS[4][7] = {
  {2, 3, 4, 5, 6, 7, 8},        // 1st digit
  {9, 10, 11, 12, 13, 14, 15},  // 2nd digit
  {22, 23, 24, 25, 26, 27, 28}, // 3rd digit
  {29, 30, 31, 32, 33, 34, 35}  // 4th digit
};

const int COLON_PINS[2] = {38, 39};

// Segment ON/OFF mapping (1 = ON, 0 = OFF)
const int DIGIT_TO_SEGMENT_MAPPING[10][7] = {
  {1, 1, 1, 1, 1, 1, 0}, // 0
  {0, 1, 1, 0, 0, 0, 0}, // 1
  {1, 1, 0, 1, 1, 0, 1}, // 2
  {1, 1, 1, 1, 0, 0, 1}, // 3
  {0, 1, 1, 0, 0, 1, 1}, // 4
  {1, 0, 1, 1, 0, 1, 1}, // 5
  {1, 0, 1, 1, 1, 1, 1}, // 6
  {1, 1, 1, 0, 0, 0, 0}, // 7
  {1, 1, 1, 1, 1, 1, 1}, // 8
  {1, 1, 1, 1, 0, 1, 1}  // 9
};

// Motion settings
const int SEGMENT_ON_POS = 90;   // Segment lit
const int SEGMENT_OFF_POS = 180; // Segment off
const int STEP_DELAY = 10;       // milliseconds per smooth step
const int STEP_SIZE = 1;         // degrees per step

Servo digitServos[4][7];
Servo colonServos[2];
int currentAngles[4][7]; // to track smooth motion

// --- FUNCTIONS ---

void smoothMove(Servo &servo, int &currentPos, int targetPos) {
  if (currentPos == targetPos) return;

  int step = (currentPos < targetPos) ? STEP_SIZE : -STEP_SIZE;
  currentPos += step;
  servo.write(currentPos);
}

// --- SETUP ---

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC!");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, setting time from compile time...");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  // Attach digit servos
  for (int d = 0; d < 4; d++) {
    for (int s = 0; s < 7; s++) {
      digitServos[d][s].attach(DIGIT_PINS[d][s]);
      digitServos[d][s].write(SEGMENT_OFF_POS);
      currentAngles[d][s] = SEGMENT_OFF_POS;
    }
  }

  // Attach colon servos (always ON)
  for (int i = 0; i < 2; i++) {
    colonServos[i].attach(COLON_PINS[i]);
    colonServos[i].write(90); // Always ON
  }

  Serial.println("Clock initialized. All segments OFF, colons ON at 90°.");
}

// --- LOOP ---

void loop() {
  static unsigned long lastUpdate = 0;
  static int lastDigits[4] = {-1, -1, -1, -1};

  DateTime now = rtc.now();
  int hour = now.hour();
  int minute = now.minute();

  int digits[4] = {
    hour / 10,
    hour % 10,
    minute / 10,
    minute % 10
  };

  // Only update when time changes
  if (millis() - lastUpdate >= 1000) {
    lastUpdate = millis();
    Serial.print("Time: ");
    Serial.print(hour);
    Serial.print(":");
    Serial.println(minute);
  }

  // Smoothly move servos toward their target angles
  for (int d = 0; d < 4; d++) {
    int num = digits[d];

    for (int s = 0; s < 7; s++) {
      int targetAngle = (DIGIT_TO_SEGMENT_MAPPING[num][s] == 1)
                            ? SEGMENT_ON_POS
                            : SEGMENT_OFF_POS;
      smoothMove(digitServos[d][s], currentAngles[d][s], targetAngle);
    }
  }

  delay(STEP_DELAY);
}

Video

Conclusion

  1. I'd like to add an ESP32 module maybe (to make it network time sync)
  2. Power supply stability is key, ensure you have at least of 20A in your power supply, otherwise your motor would jitter.
  3. Cable management make all the servo wires easier to manage
  4. Servo motors can be noisy, it is better to check the dB (if specified) to make sure they are quite (nobody likes a noisy clock)

If you connect an external power supply to power both Arduino/Shield + Motors -> make sure to remove the VCC-PWR jumper from the Shield (green pin), otherwise you could backtrack power back to your PC USB port (which could lead to bad news)