I would like to preface this by acknowledging the delay of this entry. I’ve been developing this project for the past 4 weeks, but have not had the chance to enter any updates. With that out of the way, let’s catch up to speed.
Concept
I cannot take credit for the initial Idea behind the clock, it was proposed to me by my brother and colleague, Travis Adsitt. He prompted me initially with a task, to write a function that would represent the length of two strings, attached to either side of a weight, that would move the weight in a circle. In this model, the weight would be the indicator of time, as it would move in a circular motion on a clock face, and wherever it lay is the current time. To help visualize this, I have attached a rough render of this idea.
Now with this concept, it clearly needs a lot to bring it to life. I began by creating functions for the length of the string using the following steps:
- Locate the point of the rotor
- Locate the point it meets the circumference of the circle
- Find the distance between the points
This was relatively simple to do, and I was able to create a working script in Python. This script was relatively simple, it used parsArgs to take user input of the position of the rotors, as well as the diameter and position of the clock face. I then used a “For Loop” in this script, and had it output a render of each value that was put in, or create an image for each hour and minute on the clock. I was able to compile these images into an animation, which is below.
Supplies
Now with this render completed, and knowing I had gotten the math stapled, the next step was to begin working with some hardware. Most of the materials are readily available, and I will list below for reference.
- 28BYJ-48 Stepper Motor
- ULN2003APG Motor Driver Board
- Arduino ESP32-WROOM Microcontroller
- 400 Point BreadBoard
- Other – Thread, Simple thread spool, Cardboard for body, and a ring, working as the weight
Single Motor Testing
With these positioned using a cardboard frame, the first prototype was created. Of course, it still needed to be developed and functioning. To start with this, I downloaded the Arduino IDE and plugged into my ESP32, and got straight to working. I began with using the myStepper library, just at an attempt to get a single stepper motor moving, as it is the most basic, yet important piece of the project. Below is the initial code snippet, using the myStepper library.
void setup() {
//Set Stepper Speed
myStepper.setSpeed(5);
//Begin Serial Monitoring
Serial.begin(115200);
}
void loop() {
// Run stepper motor, with steps set
myStepper.step(stepsPerRevolution); //move clockwise 1 rotation
delay(1000);
myStepper.step(-stepsPerRevolution); //move counter-clockwise 1 rotation
delay(1000);
while(true){};
}
Above the visible segment attached, I defined stepsPerRevolution as 2048, the number of steps for a full revolution, which is specific to the 28BYJ-48 Motor. Upon uploading this segment, the single motor connected should do 1 full rotation clockwise, followed by 1 full rotation counter-clockwise. It then ends with a while(true) loop, so that the program can continue to run without looping endlessly.
Two Motor Application
Now, the next step in this process is to get two stepper motors, with individual control. Since this is not supported in the myStepper library, I switched to the accelStepper library, as it is designed for multiple stepper motors.
void setup()
{
// Set Maximum Speed, Acceleration
stepper1.setMaxSpeed(300.0);
stepper1.setAcceleration(200.0);
// Repeat for stepper2
stepper2.setMaxSpeed(300.0);
stepper2.setAcceleration(200.0);
}
void loop()
{
stepper1.moveTo(2048); // set target forward 2048 steps
stepper2.moveTo(2048); // set target forward 2048 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(0); // set target back to 0 steps
stepper2.moveTo(0); // set target back to 0 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
}
With this segment, I was able to get both motors to do a full rotation clockwise, or move forward 2048 steps, before moving counterclockwise a full rotation, back to the original position.
Controlling String Lengths
With the ability to move both motors, now all I needed to do was control the rotations and move the weight to specific positions. To begin with this, I positioned the weight at the bottom of the circle I wanted to use as my clock face. From now on, this is position zero. From position zero, I moved the left motor Counter-Clockwise, and the right motor clockwise, moving the weight upward. I repeated this in 500 step increments, until I reached the top of the clock face. I took careful note of how many steps it took to go from top to bottom of the face, and how much the string length changed. My documentation of these changes is below.
Testing a Sample Set
With this information, I knew how to adjust the string length in centimeters, by changing the number of steps on either motor. I had observed and then verified that rotating one motor a full rotation, or 2048 steps, resulted in the correlating string length changing by 10 centimeters, dependent on the direction of rotation. To move forward from here, I then did the calculations given the motor positions as well as the position and specifications of the clock face, to find a sample set of points around the clock. I took each hour and found the string lengths needed, and then converted those lengths and converted them into the steps for each motor. Once I got these values verified I made a table of the values and where each motor needed to be for each hour.
I then put these values into my Arduino IDE, and was able to format it in a manner that the motors would move to the positions specified, at the same time, with the same rate of change. The code for this is very similar to the test values, just extended for each of the hours. The code is attached below.
void setup()
{
// Set Maximum Speed, Accelleration
stepper1.setMaxSpeed(300.0);
stepper1.setAcceleration(200.0);
// Repeat for stepper2
stepper2.setMaxSpeed(300.0);
stepper2.setAcceleration(200.0);
}
void loop()
{
stepper1.moveTo(-3200); // set target forward 500 steps
stepper2.moveTo(-3200); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-4190); // set target back to 500 steps
stepper2.moveTo(-1966); // set target back to 0 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-4506); // set target forward 500 steps
stepper2.moveTo(-817); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-3842); // set target forward 500 steps
stepper2.moveTo(80); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-2670); // set target forward 500 steps
stepper2.moveTo(391); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-1415); // set target forward 500 steps
stepper2.moveTo(252); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(0); // set target forward 500 steps
stepper2.moveTo(0); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(252); // set target forward 500 steps
stepper2.moveTo(-1415); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(391); // set target forward 500 steps
stepper2.moveTo(-2671); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(80); // set target forward 500 steps
stepper2.moveTo(-3842); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-817); // set target forward 500 steps
stepper2.moveTo(-4506); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
stepper1.moveTo(-1966); // set target forward 500 steps
stepper2.moveTo(-4190); // set target forward 500 steps
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0)
{
stepper1.run();
stepper2.run();
}
}
You will notice this segment is extremely inefficient and lengthy, but that’s alright, as it is just a test set, and will not be used in the final iteration of the project. After writing these values in, I was able to upload the script, and watch the first working version of the clock. The video is attached below.
Conclusion and Final Thoughts
Now, as you may notice, these number sets only apply to the specific clock diameter, motor position, and pulley diameter that relate to this specific model. My goal now, is to allow this script to be fully adjustable, so the user can simply input the measurements of the specific clock being used, and it will perform the necessary functions to find the string lengths needed. This process is still under-way but can be expected soon. I will cover the creation of these functions as well as go into detail on the physical clock prototype in my next entry. Stay posted.
Hey Jack! So cool! I can’t wait for the final iteration that is adaptable to any user’s diameter/dimension inputs. Thank you for posting!
Very well written and interesting design! Looking forward to more updates!