Introduction: 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 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
- Arduino Mega - The brain
- Arduino Mega Sensor Shield - The I/O
- DS3231 or RTC clock breakout - To keep track of time
- 30 Servos - To move each segment
- 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.
- back x 4
- back-2 x 1
- face-1 x 1
- face-2 x 2
- gear x 30
- segment-1 x 16
- segment-2 x 12
- 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.
- The first digit will be at pin 2.
- The second digit starts at pin 9.
- The third digit starts at pin 22.
- 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); }

Second Prize in the
Arduino Contest
49 Comments
Question 5 weeks ago on Introduction
Is there an issue powering 30 servos from the Arduino Mega? Should they have their own power supply?
1 year ago on Step 8
first of all...Awesome Project! I am building it now and have some questions, as I am new-ish to coding. Does it matter which order I upload the codes ( I loaded RTC first, then tried calibration then the movement) as the loading of segment parts would seem to have a tendency to break if I then have to calibrate the servos AFTER putting the segments in to their 0 position? Is the calibration code supposed to move the servos or just show where the potentiometer scales the 0-180 values are and if it is to move them, do I have to change SEGMENT for all 30 servos? Again, new-ish to IDE coding but somewhat familiar with the logic process.
1 year ago
Hi,
have someone the STL File for the servo bracket?
Reply 1 year ago
Hallo .. hast du eine STL für die Servohalterung?
1 year 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.
Reply 1 year 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?
Reply 1 year 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.
Reply 1 year 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
Reply 1 year ago
sorted it, I moved the second digit to run from pin 38 onwards.
Question 1 year 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?
1 year 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?
2 years 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.
2 years ago
This is awesome thanks for sharing!
Question 2 years ago
Where do you folks get the sensor shield from? I can't find it anywhere there in Switzerland.
2 years ago
Hey,
did i miss out on something or is the bracket missing in the 3D Files?
Cheers,
T
2 years 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!
Reply 2 years 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
Reply 2 years 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!
Reply 2 years ago
My RTC works with the 5V and there are tons of 5V pins
2 years 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.