Digital & Analog Timepiece

An array of mechanical segments moving in unison to create a clock that sits between analog and digital time keeping.
39h 48m
7× print file
0.20 mm
0.40 mm
457.00 g
In the contest Timekeepers
1,626
1069
2
35 k
updated June 27, 2021

Description

PDF

Video: https://www.reddit.com/r/3Dprinting/comments/o929er/this_3d_printed_digital_analog_timepiece_is_art/

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 before I had a 3D printer here. I brought the Prusa 3D printer just 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. All pieces pictured were printed on PETG Prusament on a Prusa i3 MK3S+

Construction

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 orientation of print for each piece was critical to this project. This is because as the segments would actually create too much friction if the orientation of the layers rubbed against each other. We also took care to ensure the face 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. 

Electronics

  • 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

Arduino Code

The 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();
        }
      }
    }

}

Awarded in the contest


Timekeepers
188 entries | May 19 – June 27, 2021

Model origin

The author hasn't provided the model origin yet.

License