ChronoGrapher - the Arduino Clock That Is Painting Time With a Brush

by nopvelthuizen in Circuits > Clocks

168 Views, 2 Favorites, 0 Comments

ChronoGrapher - the Arduino Clock That Is Painting Time With a Brush

REAL_TIME_-_PUBLIC_WORKS_–_MAARTEN_BAAS.jpg
Chinese_Calligraphy_Copybook_Yan_Zhenqing_Regular_Script_Water_Writing_Brush_Set.jpg
ChronoGraphe development
IMG_0587.JPG
IMG_0596.JPG

For years I really wanted to make an original clock depicting time in a new way. Soe years ago I had made a whiteboard marker clock driven by servo motors. Nice but too erratic to have running for long hours. Last year I saw the videoperformance clocks of Maarten Baas in museum Voorlinden. Maarten takes 12 hours to life record the minutes by erasing and redrawing the hands of a fantasy clock. I wanted something similar, but robotic. In the museum shop I happend upon a Chinese calligraphy practice block. A calligraphy student writes with water on special material. At that moment I knew THIS IS IT!

Online I found a roll of paperlike material complete with brush for only 10$. It works really well, you can draw with regular water, the wet paper turns completly black. Drying takes 5 -10 minutes, the writing disappears like magic.

Next came the developing of smoothly running rotbot arms. No more jittering servo motors. I started to experiment with the ultra cheap 28BYJ48 5 volt stepper motor. The 28YB doesn't have a lot of torque, I found the combination of light-weigth 3D printed robot arms, some extra reduction gears in the shoulder joint and raising the voltge to 7,5 volt to do the trick, especially when using accelstepper library instead of stepper.h.

Next came the arduino code. I like to do the coding from scratch. This Instructable will show you the required high school trigonometry. And it will explain how to implement the math into sequential arduino code.

I have the clock writing the hour and minute hands every few minutes and dipping its brush water regularly. I am still writing code to make the clock write numbers and simple drawings (happy faces and such) I think the list of options will increase some more in the following weeks. (the number writing is already sucesful)

This ChronoGrapher is probably my first arduino project to require every single IO pin on a UNO. I designed this version to the size of the Creality S1 PLUS printbed of 30x30 cm. This size robotarm has just the right "reach" to draw on a whole sheet of 40 x 40 cm Chinese Magic Paper. If I get enough interest from the Instructable community I shall consider a redesign for regular sized 20x20 cm printbeds. A smaller arm would also be able to move more swiftly.

Supplies

Amazon_com___28byj48_stepper_motor.jpg
HiLetgo_10pcs_Micro_Limit_Switch_KW12-3_AC_250V_5A_SPDT_1NO_1NC_Micro_Switch_Normally_Open_Close_Limit_Switch_with_Roller_Lever_Arm_Black__Amazon_com__Industrial___Scientific.jpg
Amazon_com__Alinan_5pcs_DS3231_AT24C32_Clock_Module_IIC_RTC_Module_High_Precision_Real_Time_Clock_Module_Memory_Board_Beats_Replace_DS1307_I2C_RTC_Board___Electronics.jpg
1_3M_Water_writing_cloth_Gift_Reusable_Chinese_Magic_Cloth_Water_Paper_Calligraphy_Fabric_Book_Notebook.jpg
Super_Soft_Copper_Wire_Box__5_Colors_Mix_Kit__Heat_Resistant_Flexible_Silicone_30_28_26_24_22_20AWG_Electric_Tinned_Copper_Cable.jpg

MATERIALS:

  • Arduino UNO
  • 3 mini steppermotor 28BYJ48 + ULN2003 driver board (Amazon / aliexpress)
  • 3 KW12-3 micro limit switch (Amazon / aliexpress)
  • DS3231 time module for arduino
  • 4 ballbearings 19 x 6 mm
  • Chinese magic clothe with calligraphy brush (Water Paper Calligraphy set available on aliexpress)
  • Fibreboard or multiplex 12mm thick - 2pcs 40x40 cm. 1pc 44 x 80 cm
  • Lots 3D print filament
  • Thin flexible wire in multiple colours (approx. AWG24)
  • 7.5volt or 9volt power supply (1 amp or 10 watt will do)
  • assortment of M2, M3, M4, M5 and M6 bolts, nuts and screws
  • 3 springs (similar like in a 3D printer bed)

3D Printing the Parts

3D_design_Chrono-Grapher-upperarm_-_Tinkercad.jpg

All parts are designed in Tinkercad. Here is the public TINKERCAD link: The STL files are to be found with the Instructable supporting files.

3D-Printing notes according to the colours of the parts in the Tnkercad overview.

  1. YELLOW PLA the robot arms: You want to print these arms as lightweight as possible. I have used 0.2 mm layer height, 0.4mm nozzle. Wall and bottom are 2 printing layers. The arms fit diagonally on the printbed with flat sides down. IMPORTANT: use the "support-blocker combined with "per model settings" in CURA in some places. All the areas where screws and bearings attach to the arm setup locally to 5 print layers. (This tutorial by "PushingPlastic" shows you how to do that)
  2. ORANGE PLA shoulder bearing + motor + gear frame. This parts needs to be strong,; print with 5 layer wall and bottom. The part is NOT flat, so don't forget to add support.
  3. BLACK PLA gears, bearing holder, limit switch parts. 3 or 4 layers walls will do for these parts. (I have had a few issues with the small steppermotor getting to hot for the PLA. More heat resistant filament is safer, so ABS for the smallest gear.
  4. RED TPU brush holder, flexible shoulder joint holde and brush pushrod joint. These parts require flexibility.
  5. WHITE PLA bedlevel adjustment knobs. Pretty standard printing.

Arduino Wiring

Untitled_Sketch_fzz_-_Fritzing_-__Breadboard_Weergave_.jpg
IMG_0566.JPG

The backside of my clock looks sloppy. After uploading this Instructable, I will tidy things up and print a little electronics box. Solid wires are used to connect the stepper controllers to the arduino. The stepper and limit switch wires need to be extended with thin supple wire. Solder all these wires as shown in the Fritzing diagram. There is some confusion with the green and white middle wires connected to each stepper driver. They are supposed to swapped. If you connect them straight as shown, then make sure the swapping is happens in the arduino sketch definitions.

It is best to test functionality after soldering each part. Write a small steppermotor sketch for the testing.

When using the RTC-timer the first time it needs to be set through the computer timer. (see note in the sketch)

I still need to add the following: (these are optional)

  • Power on/off switch
  • Summer/wintertime button on pin1
  • Reset button
  • toggle button on pin 2 to set the clock in different modes digital / analog / funny mode

Assembling

IMG_0569-1.JPG
IMG_0571-1.JPG
IMG_0568-1.JPG
IMG_0570-1.JPG
IMG_0567-1.JPG

Please study the foto's

  1. Make the wooden frame 44x70 cm. Thickness or type of wood is not critical I used 1/2 inch fiberboard.
  2. The SVG drawing is attached. My laser cutter is too small for that, but it is simple to cut and drill by hand.
  3. Cut two wooden circles of 40 cm diameter.
  4. Attach bottom cirkel to the frame (use a wood block or some angle joints)
  5. Mount the shoulder joint with the four flexible plugs. This will allow you to tweak the angle of the arm later on. Important for constand line widths.
  6. I tapped M4 thread for mounting the stepper motor, but hot-glue is also an option.
  7. The middle gear is mounted on a long M5 bolt. (You need to cleanup the holes in the 3D printed parts for smooth running gears.)
  8. Press two ballbearings in de upper arm, and fit on the shoulder joint.
  9. Now test your steppermotor. Make sure it is running smoothly.
  10. Continue with forearm. Add two ballbearings in the black bushing. Place the bushing it it in the elbow of the forearm. After testing the arm movement you can superglue the bushing in place.
  11. First fit the steppermotor in the upperarm, and then connect the two arms with a M6 bolt through the elbow.
  12. Test both motors, and add the limit switches. Test those too.
  13. Attach the paintbrushholder M4 bolt.
  14. Mount the brushlift steppermotor, the gears and the limit switch.
  15. Make a connecting rod (bicycle spoke) to the correct size between the gear and the brush. The brush should swivel a 1/4 rotation with 1/2 gear rotation. The TPU flex joint should then touch the limit switch.
  16. The clock disc needs three long M4 screws attached at 120 degrees spacing. Use the fram SVG as a template.
  17. You can then staple the Magic Cloth arround the edge of the disc. Add sprigs to the screws, then push the screws that are sticking out of the disc through the holes of the frame. Your clockface is ready.

I might have forgotten a detail or two, check the pictures.

Try to get the limit switches positions in such a way that after "homing" the brush sits 20 cm below and 10 cm to the left of the shoulder axis. (actual position can be measured and used for calibration later on)

Understanding the Math

Affinity_Designer_-__Untitled___Modified___50_0__.jpg

We are used to drawing in x,y coördinates, the arduino code can only control the angle of each stepper motor. We need a set of formulas to convert x,y coördinates into rotation angles alpha(shoulder) and beta(elbow).

Please study the construction drawing.

X and y are the desired position.

Pythgoras on x and y gives us the length l = sqrt ((x*x + y*y)/2)

Revers pythagoras gives us the length of d = sqrt(600*600 - l*l)

angle omega = atan(x/y)

angle f = atan(y/x)

angle epsilon = atan((d/l)

Now we can add up some angles to derive alpha and beta:

Shoulder angle rotation alpha = 1/2PI - epsilon - omega

Elbow angle rotation beta = PI - (2PI-2*epsilon)

In the next step we shall convert this math into arduino code.

Arduino Code

The code is going to use some libraries Accelstepper, Math.h, and timer. We need to define a bunch of variables.

most importantly the steps per revolution of each motor

#include <AccelStepper.h>
#include <math.h>
#include <RTClib.h>
RTC_DS3231 rtc;
float hour, minute, second;
#define HALFSTEP 8
long initial_homing=-1;
float SR1= 147.456; // Shoulder joint: Steps per revolution times 1000
float SR2= -24.288; // Elbow joint: Steps per revolution times 1000
float zeroalpha = -0.18; // needs to be calibrated on each machine
float zerobeta = 2.39; // needs to be calibrated on each machine
float zerox = 10.5; // needs to be calibrated on each machine
float zeroy = 19.5; // needs to be calibrated on each machine
float x = -15;
float y = 50;
double l = 0;
double d = 0;
double epsilon = 0;
double omega = 0;
double alpha = 0;
double beta = 0;
float xt = zerox;
float yt = zeroy;
//float targetx;
//loat targety;

// Motor pin definitions
// *** BEWARE THE MIDDLE WIRES OF EACH DRIVER IS SWITCHED OVER CHOOSE SOFTWARE OR WIRING***
#define motor1Pin1 A0   // IN1 on the ULN2003 driver 1
#define motor1Pin2 A1   // IN2 on the ULN2003 driver 1
#define motor1Pin3 A2  // IN3 on the ULN2003 driver 1
#define motor1Pin4 A3   // IN4 on the ULN2003 driver 1
#define motor2Pin1 4   // IN1 on the ULN2003 driver 2
#define motor2Pin2 6   // IN2 on the ULN2003 driver 2
#define motor2Pin3 5  // IN3 on the ULN2003 driver 2
#define motor2Pin4 7   // IN4 on the ULN2003 driver 2
#define motor3Pin1 8   // IN1 on the ULN2003 driver 2
#define motor3Pin2 10   // IN2 on the ULN2003 driver 2
#define motor3Pin3 9  // IN3 on the ULN2003 driver 2
#define motor3Pin4 11   // IN4 on the ULN2003 driver 2

// Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
AccelStepper stepper1(HALFSTEP, motor1Pin1, motor1Pin3, motor1Pin2, motor1Pin4);
AccelStepper stepper2(HALFSTEP, motor2Pin1, motor2Pin3, motor2Pin2, motor2Pin4);
AccelStepper stepper3(HALFSTEP, motor3Pin1, motor3Pin3, motor3Pin2, motor3Pin4);


The setup is used for switching on or off the time adjust option.. You can also keep an eye on the maximum speed of your stepper motors. Try the highest possible value.

void setup() {
 // is power has been lost of RTC timer:
 // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
 Serial.begin(9600);
 stepper1.setMaxSpeed(1500.0);   stepper2.setMaxSpeed(1000.0);   stepper3.setMaxSpeed(1500.0);
 stepper1.setAcceleration(90000.0); stepper2.setAcceleration(80000.0); stepper3.setAcceleration(2000.0);
 stepper1.setSpeed(1500);     stepper2.setSpeed(1000);     stepper3.setSpeed(1500);
pinMode(2,INPUT_PULLUP);
pinMode(13,INPUT_PULLUP);
pinMode(12,INPUT_PULLUP);
zero();
} //--(end setup )---


I have moved all the calculation in voids, that keeps the void loop very simple:

void loop() {
water();
HourMark();
urenwijzer();
minuutwijzer();
zero();
}


Void zero comes first. It is called fro the setup to home the stepper motors.

void zero(){
//1==================================1 
 initial_homing=-1;
 stepper1.setCurrentPosition(0);
 stepper1.moveTo(800);  
 stepper1.runToPosition();
 while (digitalRead(2)) {  // Make the Stepper move until the switch is activated  
  stepper1.moveTo(initial_homing); // Set the first position to move to
  initial_homing--;  // Decrease by 1 for next move if needed
  stepper1.run();  // Start moving the stepper
 }  //if (distanceToGo() ==0){
 stepper1.setCurrentPosition(0);
//2==================================2  
 initial_homing=-1;
 stepper2.setCurrentPosition(0);
 stepper2.moveTo(100);  
 stepper2.runToPosition();
 while (digitalRead(13)) {  // Make the Stepper move until the switch is activated  
  stepper2.moveTo(initial_homing); // Set the first position to move to
  initial_homing--;  // Decrease by 1 for next move if needed
  stepper2.run(); // Start moving the stepper
 }  //if (distanceToGo() ==0){
 stepper2.setCurrentPosition(0);
//3==================================3
 stepper3.setCurrentPosition(0);
 stepper3.moveTo(-100);  
 initial_homing=1;
 stepper3.runToPosition();
 while (digitalRead(12)) {  // Make the Stepper move until the switch is activated  
  stepper3.moveTo(initial_homing); // Set the first position to move to
  initial_homing++;  // Decrease by 1 for next move if needed
  stepper3.run();  // Start moving the stepper
 }  //if (distanceToGo() ==0){
 stepper3.setCurrentPosition(0);
}


First action is dipping brush in water: Here you can see the math in action. The brush moves in 500 steps from current position to the cup of water.

void water(){
zerox = 10.5;
zeroy = 19.5;
 Serial.println("Water");
 for(int i = 0; i<500; i++){
 xt = xt + (0-zerox)/500;
 yt = yt + (57-zeroy)/500;
 l=sqrt(xt*xt+yt*yt)/2;
 d=sqrt(30*30-l*l);
 epsilon=atan(d/l);
 omega=atan(xt/yt);
 alpha=PI/2-epsilon-omega;
 beta=2*epsilon; 
  stepper1.moveTo(1000*SR1*(alpha-zeroalpha)/(2*PI));  
  stepper1.runToPosition();
  stepper2.moveTo(1000*SR2*(beta-zerobeta)/(2*PI));  
  stepper2.runToPosition();
 }
xt = 0 ;
yt = 57 ; 
   Serial.println("WaterReady");
}


To small voids for moving the brush, to be called at any time.

void Lijntekenen (line draw) moves the stepper from current position to new x,y-coordinates.

void penseelUP(){
 stepper3.moveTo(-2000);  
 stepper3.runToPosition();
}
void penseelDOWN(){
 stepper3.moveTo(-4000);  
 stepper3.runToPosition();
}
void lijntekenen(){
 zerox = xt;
 zeroy = yt;
  for(int i = 0; i<1000; i++){
   xt = xt + (x-zerox)/1000;  
   yt = yt + (y-zeroy)/1000;
   l=sqrt(xt*xt+yt*yt)/2;
   d=sqrt(30*30-l*l);
   epsilon=atan(d/l);
   omega=atan(xt/yt);
   alpha=PI/2-epsilon-omega;
   beta=2*epsilon;
   stepper1.moveTo(1000*SR1*(alpha-zeroalpha)/(2*PI));  
   stepper1.runToPosition();
   stepper2.moveTo(1000*SR2*(beta-zerobeta)/(2*PI));  
   stepper2.runToPosition();
   }
 xt = x;
 yt = y;
}


Two voids to call the timer, and calculate hours and minutes to x,y-coordinates.

You can tweak the centre position to your own liking. Also the length of the "hands" is tweakable.

void minuutwijzer(){        //minute hand
 DateTime now = rtc.now();
 hour=now.hour();
 minute=now.minute();
 x = -15*sin((minute*2*PI)/60);   //calculate x position
 y = 39-15*cos((minute*2*PI)/60);  //calculate y position
 lijntekenen();          //linedraw
 penseelDOWN();          //brushdown
x = 0;               //centrepoint x-position
y = 38;               //centrepoint y-position
 lijntekenen();           //linedraw
 penseelUP();           //brushup
 x=10;               //upper left resting x-position
 y=20;               //upper left resting y-position
 lijntekenen();          //linedraw
}
void urenwijzer(){. // hour hand
 DateTime now = rtc.now();
 hour=now.hour();
 minute=now.minute();
x = -10*sin(((hour+(minute/60))*2*PI)/12);
y = 39-10*cos(((hour+(minute/60))*2*PI)/12);
  lijntekenen();
  penseelDOWN();
x = 0;
 y = 38;
lijntekenen();
penseelUP();
}

And finally drawing the 12 positions around the circle. The positions are calculated using trigonometry.

void HourMark(){
 for (float k=0 ; k<12 ; k++){
  x = -17*sin((k/6)*PI);   //calculate x position
  y = 39-17*cos((k/6)*PI);  //calculate y position
  lijntekenen();
  x = -14*sin((k/6)*PI);   //calculate x position
  y = 39-14*cos((k/6)*PI);  //calculate y position
  penseelDOWN();
  lijntekenen();
  penseelUP();  
 }
}


Final Notes

The project will continue to be improved. I look forward to receive feedbacks and ideas. I shall update this Instructable whenever I find the time.

The clock is now running for several days. All seems well. The regular water is replaced by demineralised water to prevent mineral marking.