Kinetic & Digital Clock (Arduino + 3D Print)

14,900

355

47

Introduction: Kinetic & Digital Clock (Arduino + 3D Print)

About: Hello, I'm a Software Engineer!

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 microcontroller to first process the read the current time and then align the segments out accordingly through a motor controller.

This piece is a continuation of work I did previously out of paper. I bought a 3D printer to make this clock into a reality, as the precision and mechanical nature of this piece needed strong and reliable parts to create a clean finish and movement.

Supplies

  1. Arduino Mega - The brain
  2. Arduino Mega Sensor Shield - The I/O
  3. DS3231 or RTC clock breakout - To keep track of time
  4. 30 Servos - To move each segment
  5. 3D Printer & Filament

Step 1: Print Your Parts

I made this design with sliding and snap fit parts. As this is a large mechanical piece, designing smaller parts made it so that I could iterate quickly on individual components. Each digit is constructed out of 7 segments that are slotted into a panel. The body of the clock holds 4 of these digit panels. The back panel slides into the face plate, where the segments move on along the gear to create linear motion.

The STL's are provided for you to slice and print out with your printer of choice. The orientation of print for each piece was critical to this project. Ensure you orient the STLs in slicer in a way that suits your 3D printer when slicing. In my case I took care to ensure the faces of the clock are pieces that were printed directly on the Steel sheet, as this creates the cleanish finish on the Prusa. Finish is important to the aesthetic of this piece.

  1. back x 4
  2. back-2 x 1
  3. face-1 x 1
  4. face-2 x 2
  5. gear x 30
  6. segment-1 x 16
  7. segment-2 x 12
  8. segment-3 x 2

Step 2: Attach Servo to Bracket

Take a screwdriver and attach the servo to bracket. The screws should come bundled with your servos. The placement of the servo is from the bottom of the mount as pictured. You will need to repeat this step for all 30 of servos.

When you're done attaching it to the bracket, snip the servo head, and attach it to the gear. You may optionally want to glue this to keep things in place.

Step 3: Attach Servo Brackets to the Back Panel

Each panel is a a seven segment display, attach 7 servo & brackets to each panel. They are fitted with snap fit brackets. If you're doing some debugging and need to remove the bracket, take a pair of pliers to clamp the snap fit mechanism to release the bracket.

Step 4: Add Panel to Main Body

Slide the segment panels into the back of the body of the clock. There are four digit panels and one panel for the colon. The body includes alignment flags, that will hold the pieces in alignment for ease of assembly.

Step 5: Connect Servos to Microcontroller

Attach servos to microcontroller. Each segment of a digit is ordered from A to G. We will map these as 1 to 7 for connecting to our microcontroller. Simply attach the servos in order, starting from the following pins.

  1. The first digit will be at pin 2.
  2. The second digit starts at pin 9.
  3. The third digit starts at pin 22.
  4. The fourth digit starts at pin 29.

You can refer to the source-code section for the final pin-out.

Step 6: Insert Segments Into the Display

These hobby servos actually only have 180 degrees of motion. In order to calibrate and align the mechanisms, prep all the servos by moving them to the end of their motion (180 degree).

Slide the segment from the front of the display into each individual socket, you will feel the servos accept the segment there should be some light resistance and smooth actuation of the part as inserting.

Step 7: Programming

Our code is run in a loop, it checks the current time and moves the destination of each segment to the desired location. Using the loop as a ticker it makes small increments on the angle of the servo creating a smooth push and pull motion on the segment.

Since the servos used in this project are cheap and not the most accurate we use the segment intervals map, to store the internal offset of each servo. This is how we ensure that when we push or pull the servos they are all aligned. This is critical to the aesthetic of the display.

String input;
#include <Servo.h>
#include <DS3231.h>
DS3231  rtc(SDA, SCL);

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
};

const int SEGMENT_INTERVALS[4][7][2] = {
  {
    {141, 54},
    {155, 69},
    {150, 73},
    {151, 70},
    {159, 75},
    {159, 75},
    {125, 40}
  },
  {
    {164, 76},
    {155, 76},
    {138, 61},
    {180, 87},
    {151, 63},
    {145, 57},
    {165, 78}
  },
  {
    {157, 73},
    {156, 70},
    {165, 85},
    {137, 52},
    {133, 50},
    {133, 50},
    {168, 73}
  },
  {
    {131, 52},
    {147, 61},
    {131, 51},
    {158, 69},
    {155, 73},
    {116, 28},
    {137, 60}
  }
};

const int COLON_INTERVAL[2][2] = {
  {141, 62},
  {137, 30},
};


const int DIGIT_STARTING_SEGMENT_INDEX[4] = {2, 9, 22, 29};
const int COLON_STARTING_INDEX = 16;

const int START_POS = 0;
const int COLON = 2;
const int DIGITS = 4;
const int SEGMENTS_PER_DIGIT = 7;
const int STEP_MS = 20;
const int COUNT_MS = 2000;
const int NUM_SERVOS = DIGITS * SEGMENTS_PER_DIGIT;

int servoTargetDestination[DIGITS][NUM_SERVOS];
int servoTargetDestinationColon[COLON];

int count = 1200;
int timeMS = 0;

Servo servos[DIGITS][SEGMENTS_PER_DIGIT];
Servo colonServos[COLON];


void setup() {
  rtc.begin();
  for (int i = 0; i < DIGITS; i++) {
    for (int j = 0; j < SEGMENTS_PER_DIGIT; j++) {
      int offset = DIGIT_STARTING_SEGMENT_INDEX[i];
      servoTargetDestination[i][j] = SEGMENT_INTERVALS[i][j][START_POS];
      servos[i][j].attach(j + offset);
      servos[i][j].write(servoTargetDestination[i][j]);
    }

  }
  delay(500);
  for (int i = 0; i < DIGITS; i++) {

    for (int j = 0; j < SEGMENTS_PER_DIGIT; j++) {
      servos[i][j].detach();
    }
  }
  for (int i = 0; i < COLON; i++) {
    colonServos[i].attach(i + COLON_STARTING_INDEX);
    colonServos[i].write(COLON_INTERVAL[i][START_POS]);
  }
  delay(500);
  for (int i = 0; i < COLON; i++) {
    colonServos[i].detach();
  }
  for (int i = 0; i < COLON; i++) {
    colonServos[i].attach(i + COLON_STARTING_INDEX);
    colonServos[i].write(COLON_INTERVAL[i][1]);
  }
  delay(500);
  for (int i = 0; i < COLON; i++) {
    colonServos[i].detach();
  }
}

void loop() {
    // Retrieve and cleanup RTC string. "12:45" -> "1245"
    String timeStr = rtc.getTimeStr();
    String timeString = timeStr.substring(0, 2) + timeStr.substring(3, 5);

    for (int activeDigit = 0; activeDigit < 4; activeDigit++) {
      
      // Step 1: Set servoTargetDestination
      String stringCount = String(count); // Swap to timeString to use the clock.
      for (int i = 0; i < SEGMENTS_PER_DIGIT; i++)
      {
        int displayNumber = stringCount.charAt(timeString.length() - 1 - activeDigit) - '0';
        int placement = (activeDigit == 3 && displayNumber == 0) ? 0 : DIGIT_TO_SEGMENT_MAPPING[displayNumber][i];
        servoTargetDestination[activeDigit][i] =  SEGMENT_INTERVALS[activeDigit][i][placement];
      }

      // Step 2: Increment Segments
      for (int i = 0; i < SEGMENTS_PER_DIGIT; i++)
      {
        Servo servo = servos[activeDigit][i];
        int pos = servo.read();
        int dest = servoTargetDestination[activeDigit][i];
        if (pos != dest) {
          if (pos < dest) {
            pos++;
          } else {
            pos--;
          }
          if (!servo.attached()) {
            int offset = DIGIT_STARTING_SEGMENT_INDEX[activeDigit];
            servo.attach(i + offset);
          }
          servo.write(pos);
        }
      }
    }
  
    // Step 3: Wait
    delay(STEP_MS);
    timeMS = timeMS + STEP_MS;
  
    // Step 4A: Countdown
    if (timeMS >= COUNT_MS) {
      timeMS = 0;
      count = count + 1;
    }
  
    // Step 5: Detach anything that is at its destination
    for (int i = 0; i < DIGITS; i++) {
      for (int j = 0; j < NUM_SERVOS; j++) {
        Servo servo = servos[i][j];
        int pos = servo.read();
        int destination = servoTargetDestination[i][j];
        if (pos == destination && servo.attached()) {
          servos[i][j].detach();
        }
      }
    }

}<br>

Step 8: Calibration & Setup

The previous step will actually require additional calibration because the servos that I have will have completely different parameters as yours. The following code is what I used to calibrate and find the intervals needed for each servo

/*
  Controlling a servo position using a potentiometer (variable resistor)
  by Michal Rinott < http://www.arduino.cc/en/Tutorial/Knob
>

  modified on 8 Nov 2013
  by Scott Fitzgerald
   http://www.arduino.cc/en/Tutorial/Knob

*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo

int potpin = 0;  // analog pin used to connect the potentiometer
int val;    // variable to read the value from the analog pin


const int SEGMENT_INTERVALS[4][7][2] = {
  {
    {146, 57},
    {147, 62},
    {138, 51},
    {160, 88},
    {135, 64},
    {149, 70},
    {139, 58}
  },
  {
    {131, 45},
    {150, 53},
    {138, 52},
    {146, 61},
    {151, 70},
    {140, 57},
    {137, 48}
  },
  {
    {157, 73},
    {156, 70},
    {135, 50},
    {137, 52},
    {133, 50},
    {133, 50},
    {168, 73}
  },
  {
    {131, 52},
    {147, 61},
    {131, 51},
    {128, 43},
    {125, 41},
    {104, 24},
    {137, 60}
  }
};
const int COLON_INTERVAL[2][2] = {
  {127, 45},
  {156, 81},
};

const int DIGIT_STARTING_SEGMENT_INDEX[4] = {2, 9, 22, 29};
const int COLON_STARTING_INDEX = 16;

const int SEGMENTS_PER_DIGIT = 7;
const int DIGITS = 4;
const int START_POS = 1;
const int COLON = 2;




int segment = 12;

void setup() {

  Servo servos[DIGITS][SEGMENTS_PER_DIGIT];
  Servo colonServos[COLON];

  for (int i = 0; i < DIGITS; i++) {
    for (int j = 0; j < SEGMENTS_PER_DIGIT; j++) {
      int offset = DIGIT_STARTING_SEGMENT_INDEX[i];
      servos[i][j].attach(j + offset);
      servos[i][j].write(SEGMENT_INTERVALS[i][j][START_POS]);
    }
      delay(1000);

    for (int j = 0; j < SEGMENTS_PER_DIGIT; j++) {
      servos[i][j].detach();
    }
  }

  for (int i = 0; i < COLON; i++) {
    colonServos[i].attach(i + COLON_STARTING_INDEX);
    colonServos[i].write(COLON_INTERVAL[i][START_POS]);
  }
  delay(1000);
  for (int i = 0; i < COLON; i++) {
    colonServos[i].detach();
  }

  myservo.attach(segment);  // attaches the servo on pin 9 to the servo object
  Serial.begin(9600); // open the serial port at 9600 bps:


}

int inputParse(String input) {
  // Clean number
  int angle = input.toInt();
  if (angle > 180 || angle < 0) {
    angle = 0;
  }
  return angle;
}

void loop() {

  if (Serial.available()) {
    segment = inputParse(Serial.readString());
    Serial.print("Attaching: " );
    Serial.println(segment);
    myservo.detach();
    myservo.attach(segment);
  }


  if (myservo.attached()) {
    val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
    val = map(val, 0, 1023, 0, 180);     // scale it to use it with the servo (value between 0 and 180)
    myservo.write(val);                  // sets the servo position according to the scaled value

    Serial.print("Seg: " );
    Serial.print(segment);
    Serial.print(" Pot: " );
    Serial.println(val);
    delay(15);                           // waits for the servo to get there
  }
}

In addition, we also need to set the time of our RTC! Here is the following code used to do that.

// DS3231_Serial_Easy
// Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
// web: http://www.RinkyDinkElectronics.com/
//
// A quick demo of how to use my DS3231-library to 
// quickly send time and date information over a serial link
//
// To use the hardware I2C (TWI) interface of the Arduino you must connect
// the pins as follows:
//
// Arduino Uno/2009:
// ----------------------
// DS3231:  SDA pin   -> Arduino Analog 4 or the dedicated SDA pin
//          SCL pin   -> Arduino Analog 5 or the dedicated SCL pin
//
// Arduino Leonardo:
// ----------------------
// DS3231:  SDA pin   -> Arduino Digital 2 or the dedicated SDA pin
//          SCL pin   -> Arduino Digital 3 or the dedicated SCL pin
//
// Arduino Mega:
// ----------------------
// DS3231:  SDA pin   -> Arduino Digital 20 (SDA) or the dedicated SDA pin
//          SCL pin   -> Arduino Digital 21 (SCL) or the dedicated SCL pin
//
// Arduino Due:
// ----------------------
// DS3231:  SDA pin   -> Arduino Digital 20 (SDA) or the dedicated SDA1 (Digital 70) pin
//          SCL pin   -> Arduino Digital 21 (SCL) or the dedicated SCL1 (Digital 71) pin
//
// The internal pull-up resistors will be activated when using the 
// hardware I2C interfaces.
//
// You can connect the DS3231 to any available pin but if you use any
// other than what is described above the library will fall back to
// a software-based, TWI-like protocol which will require exclusive access 
// to the pins used, and you will also have to use appropriate, external
// pull-up resistors on the data and clock signals.
//

#include <DS3231.h>

// Init the DS3231 using the hardware interface
DS3231  rtc(SDA, SCL);

void setup()
{
  // Setup Serial connection
  Serial.begin(115200);

  // Uncomment the next line if you are using an Arduino Leonardo
  //while (!Serial) {}
  
  // Initialize the rtc object
  rtc.begin();
  
  // The following lines can be uncommented to set the date and time
  //  rtc.setDOW(FRIDAY);     // Set Day-of-Week to SUNDAY
  rtc.setTime(23, 05, 0);     // Set the time to 12:00:00 (24hr format)
  rtc.setDate(7, 19, 2021);   // Set the date to January 1st, 2014
}

void loop()
{

  // Send time
  Serial.println(rtc.getTimeStr());
  
  // Wait one second before repeating :)
  delay (1000);
}
Arduino Contest

Second Prize in the
Arduino Contest

Be the First to Share

    Recommendations

    • Make It Modular: Student Design Challenge

      Make It Modular: Student Design Challenge
    • Build a Tool Contest

      Build a Tool Contest
    • Fruits and Veggies Speed Challenge

      Fruits and Veggies Speed Challenge

    47 Comments

    0
    DavidH1075
    DavidH1075

    5 months ago

    Hi, Great project. I've built mine now and going through the testing. I have a problem with PIN 13 which controls the onboard led. When I run the code it seems to ignore any servo movements and the servo gets very hot. Did you have a similar problem? Otherwise its working. I'm running a testing .ino to run through the movements over and over again to wear them in.

    0
    Earik
    Earik

    Reply 10 days ago

    Hello David, I'm Earik from Holland. I'd like to make this awesome clock. I saw that you have built the clock. I'm new in 3D-printing. Can you please tell me what filament you used and what printer or printservice would you recommend?

    0
    DavidH1075
    DavidH1075

    Reply 10 days ago

    I used PLA and printed the pieces myself on a pusa mini albeit I did need to modify the files to make them fit. I also modified the servo mounting brackets quite significantly along with the individual gears and sliding components. To date i've struggled to get it to work for a long periods without some of the pieces sticking.

    0
    Earik
    Earik

    Reply 9 days ago

    Thanx for your very quick respons!!! You made me curious when you said that you had to modify the files to make them fit and that you also had to modify the servo mounting brackets quite significantly along with the individual gears and sliding components. Is it possible that you explain me something more...
    Greetz from a warm Holland, Earik

    0
    DavidH1075
    DavidH1075

    Reply 5 months ago

    sorted it, I moved the second digit to run from pin 38 onwards.

    0
    Earik
    Earik

    Question 27 days ago

    This is a top project. Very nice and very smart made. Awesome!! Can you please tell me what kind of filament you used for the 3D-printing? Did you do the 3D-printing yourself?

    0
    JamesWalton2
    JamesWalton2

    6 months ago

    Cool project alstroemeria! I'm wondering if you powered it all through the arduino, as using many servos could use a lot of current! Like the shield passes through and uses the arduino underneath I suppose? The arduino then powered via the barrel connector at the bottom?

    0
    XMAZDAX
    XMAZDAX

    6 months ago

    Hi,

    have someone the STL File for the servo bracket?

    0
    asegal0000
    asegal0000

    7 months ago

    How do you get the servo interval map? I am building this great looking project right now, and found the easiest way to insert the servo brackets to the back panel is to line up the pins and use a trigger clamp (I used a dewalt) and squeeze the trigger to get the pins into the hole.

    0
    berlingozzo
    berlingozzo

    8 months ago

    This is awesome thanks for sharing!

    0
    stifael
    stifael

    Question 8 months ago

    Where do you folks get the sensor shield from? I can't find it anywhere there in Switzerland.

    0
    TobeeR
    TobeeR

    9 months ago

    Hey,

    did i miss out on something or is the bracket missing in the 3D Files?

    Cheers,

    T

    0
    rcoronil
    rcoronil

    10 months ago

    Hi. I print all part and ensambled it. But I have a question,,, I buy a arduino mega 2560, DS3231 RTC and Arduino Mega Sensor Shield v2.0.
    Where do you connect the DS3231 RTC?
    Thanks. Great job!

    0
    alstroemeria
    alstroemeria

    Reply 10 months ago

    You can refer to the comments of the code, cheers

    Arduino Mega:
    SDA pin -> Arduino Digital 20 (SDA) or the dedicated SDA pin
    SCL pin -> Arduino Digital 21 (SCL) or the dedicated SCL pin

    0
    rcoronil
    rcoronil

    Reply 10 months ago

    Thanks for your soon answer.

    I known where the SDA and SCL lines connect. But how I get the 3.3V and ground from the Arduino Mega Sensor Shield v2.0.

    Can you send a image of the Mega Sensor Shield?

    Thanks again!

    0
    alstroemeria
    alstroemeria

    Reply 10 months ago

    My RTC works with the 5V and there are tons of 5V pins

    Screenshot 2021-08-31 at 10.44.12 PM.png
    0
    smooth_jamie
    smooth_jamie

    10 months ago

    How is your desk so clean? Lol. As a maker I have things everywhere. I can't tell if that's just because I am creative or disorganised :-S Nice project btw, it would be nice if there was a way of defining the digits so they contrast a little.

    0
    alstroemeria
    alstroemeria

    Reply 10 months ago

    Oh, when my project is out, my entire desk is filled too. I just have a habit of clearing everything daily into boxes so I that can do my day job.

    0
    asegal0000
    asegal0000

    11 months ago

    Is there more than 1 back stl file? I can't find one with the colons. Are there end pieces also? The picts look like there are 3 back panels for a 2 digit side of the clock.

    0
    alstroemeria
    alstroemeria

    Reply 11 months ago

    The end piece in the pictures was just a test piece to hold the clock during debugging and not part of the clock. I've updated the print files to include the missing back file.