Project tutorial

# Backward Running Analog Clock with Three Steppers © GPL3+

Use three stepper motors to make the classic analog clock movement in the opposite direction.

• 3,730 views
• 11 respects

## Components and supplies

 SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
 Elegoo stepper motor and driver Can purchase as five pack with driver affordably online
×3
 External power Supply External variable or 5Vdc power supply. Couple amps max probably enough. I have a 5 amp, 0-30Vdc
×1
×1
 misc cardboard or equivalent to mount motors and attach watch movements and labels, plus hook-up wires and cable to be able to connect Pro Mini to computer via USB
×1

## Apps and online services

 Arduino IDE

I wanted to make an analog clock that ran counter-clockwise. A nice lesson to not assume the normal is very normal at all. There really is no reason other than convention for a clock to turn the direction around the circle it does.

Since the classic DIY positioning servos I've implemented have a 180-degree working range, I decided to use a stepper motor.

A circle is broken into 360 degrees also by convention only. However, the number 360 is quite convenient due to the quantity of numbers that divide evenly into 360. The number of hours, minutes and seconds divide evenly into 360. As a result each hour represents 30 degrees of rotational travel, each minute is 6 degrees of travel and same for seconds.

This code calculates absolute "MoveTo" positions with the information in mind.

However, there is a bug that I cannot resolve. The code uses the AccelStepper library and there is one move that is incorrect that I cannot find the bug in the code/logic. It is more obvious on the seconds hand since the problem arises every 60 seconds. Problem is same on hours and minutes but is more easily tolerable since happens twice per day on hour hand and only once per hour on the minute hand.

Exact steps to reproduce the problem:

The second hand makes the correct move of 6 degrees in the counter-clockwise direction from second value 58 to 59. Same for move from 59 to 0. The minute hand makes it's 6 degree move at the transition of second hand from 59 to 0. However, on the next second hand move (from 0-1), the second hand servo makes the long move in the clockwise direction.

The motor eventually catches up since it runs at faster than 6 degrees per second. Same problem exists with code for other motors. I experimented with negative speed commands and other things without success.

I prefer to not use incremental moves due to accumulated error and the need to write code that puts the motor ahead of the actual seconds value of the Arduino clock time to sync as clock time "passes" the current motor position or similar.

## Code

##### Arduino codeArduino
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <Time.h>
#include <TimeLib.h>
#include <EEPROM.h>

//Backward Analog World Clock (BABwatch_2)
//Scott Mangiacotti
//Tucson, Arizona USA
//September 2018

//Global constants
const int GIVE_BACK = 5;  //number milliseconds delay at end of each scan to ensure not using 100% CPU

//Constants for the steppers
const int STEPS = 4096;
const int HALF_STEP = 8;

const int HOUR_STEPPER_IN1 = 2;
const int HOUR_STEPPER_IN2 = 3;
const int HOUR_STEPPER_IN3 = 4;
const int HOUR_STEPPER_IN4 = 5;

const int MIN_STEPPER_IN1 = 6;
const int MIN_STEPPER_IN2 = 7;
const int MIN_STEPPER_IN3 = 8;
const int MIN_STEPPER_IN4 = 9;

const int SEC_STEPPER_IN1 = 10;
const int SEC_STEPPER_IN2 = 11;
const int SEC_STEPPER_IN3 = 12;
const int SEC_STEPPER_IN4 = 13;

//Global variables
AccelStepper gHourStepper(HALF_STEP, HOUR_STEPPER_IN1, HOUR_STEPPER_IN3, HOUR_STEPPER_IN2, HOUR_STEPPER_IN4);
AccelStepper gMinStepper(HALF_STEP, MIN_STEPPER_IN1, MIN_STEPPER_IN3, MIN_STEPPER_IN2, MIN_STEPPER_IN4);
AccelStepper gSecStepper(HALF_STEP, SEC_STEPPER_IN1, SEC_STEPPER_IN3, SEC_STEPPER_IN2, SEC_STEPPER_IN4);

int gHour;
int gMinute;
int gSecond;
int gDay;
int gMonth;
int gYear;
bool gTimeSynced = false;

bool gTempCW = false;

int gHourOffset = 0;     //units +/- degrees
int gMinuteOffset = 0;   //units +/- degrees
int gSecondOffset = 0;   //units +/- degrees

bool gSystemEnabled = false; //determines if servos are moved to match wallclock time every loop scan

//Run once
void setup()
{

//Setup static stepper properties
gHourStepper.setMaxSpeed(1000.0);
gHourStepper.setAcceleration(100.0);
gHourStepper.setSpeed(999);

gMinStepper.setMaxSpeed(1000.0);
gMinStepper.setAcceleration(100.0);
gMinStepper.setSpeed(999);

gSecStepper.setMaxSpeed(1000.0);
gSecStepper.setAcceleration(100.0);
gSecStepper.setSpeed(999);

//Initialize serial port
Serial.begin(9600);

//Post app data
postAppData();

}

//Run continuous
void loop()
{

//Parse into separate variables
if (gTimeSynced == true)
{
gHour = hour();
gMinute = minute();
gSecond = second();
gDay = day();
gMonth = month();
gYear = year();
}

if (gSystemEnabled == true)
{
if (gTimeSynced == true)
{
//Calculate HOUR hand position
if (gHourStepper.currentPosition() ==  gHourStepper.targetPosition())
{
calculateHourMoveTo(gHour);
}

//Calculate MINUTE hand position
if (gMinStepper.currentPosition() ==  gMinStepper.targetPosition())
{
calculateMinuteMoveTo(gMinute);
}

//Calculate SECOND hand position
if (gSecStepper.currentPosition() ==  gSecStepper.targetPosition())
{
calculateSecondMoveTo(gSecond);
}

}
else
{
gSystemEnabled = false;
Serial.println("Time not synchronized");

}
}

//Process serial messages
if (Serial.available() > 0)
{
processMessage();

}

//Run the motors until desired position achieved
gHourStepper.setSpeed(999);
gHourStepper.runSpeedToPosition();

gMinStepper.setSpeed(999);
gMinStepper.runSpeedToPosition();

gSecStepper.setSpeed(-999);
gSecStepper.runSpeedToPosition();

//Give some time back
delay(GIVE_BACK);

}

//Based on hour value from wallclock time, calculate and move to hour position
//Hour to be passed in 24-hour time
void calculateHourMoveTo(int iHour)
{
int iCWposition;
int iCCWposition;

//Validate
if (iHour < 0 && iHour > 23)
{
Serial.println("Invalid hour parameter in function calculateHourMoveTo");
return;
}

//Calculate hour servo position for clockwise clock
//12 hours per day
//360 degrees in a full circule
//360/12 = 30 degrees per hour

//Convert 24-hour format hour into 12-hour format
if (iHour > 12)
{
iHour = iHour - 12;
}

//Calculate position based on above formula
iCWposition = iHour * 30;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gHourStepper.moveTo(dStepperMove);

}

//Based on minute value from wallclock time, calculate and move to minute position
void calculateMinuteMoveTo(int iMinute)
{

int iCWposition;
int iCCWposition;

//Validate
if (iMinute < 0 && iMinute > 12)
{
Serial.println("Invalid minute parameter in function calculateMinuteMoveTo");
return;
}

//Calculate minute servo position for clockwise clock
//60 minutes per hour
//360 degrees in a full circule
//360/60 = 6 degrees per minute

//Calculate position based on above formula
iCWposition = iMinute * 6;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gMinStepper.moveTo(dStepperMove);

}

//Based on second value from wallclock time, calculate and move to second position
void calculateSecondMoveTo(int iSecond)
{

int iCWposition;
int iCCWposition;

//Validate
if (iSecond < 0 && iSecond > 12)
{
Serial.println("Invalid second parameter in function calculateSecondMoveTo");
return;
}

//Calculate second servo position for clockwise clock
//60 seconds per minute
//360 degrees in a full circule
//360/60 = 6 degrees per second

//Calculate position based on above formula
iCWposition = iSecond * 6;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gSecStepper.moveTo(dStepperMove);

//  if (iSecond == 0 || iSecond == 1)
//  {
//    Serial.println(dStepperMove);
//
//  }

}

//Set SBC time, declare synced and ready to run and display results
void setSystemTimeNow()
{

setTime(gHour, gMinute, gSecond, gDay, gMonth, gYear);
gTimeSynced = true;
Serial.println("System time set successfully");

}

//Read data from serial port and process message from power user
//Format is: XXnnnn
//XX is a value between 1 - 32 and represents the command type or area (for example manual commands to the HOUR servo motor)
//nnnn is a value between 0-1000 and represents the value for the target command type
//For example, 01180 is type 02 and value 180. It represents HOUR servo motor move to position 180 degrees
//See documentation for command definitions and value ranges
void processMessage()
{
int iMessage;
int iControlCode;
int iControlValue;

//Read the data in the serial port buffer
iMessage = Serial.parseInt();
Serial.println(iMessage);

//Process the serial port message
if (iMessage > 0)
{
iControlCode = iMessage/1000;
Serial.print("Control Code: ");
Serial.println(iControlCode);

iControlValue = iMessage % 1000;
Serial.print("Control Value: ");
Serial.println(iControlValue);
}

//Misc control and command codes
if (iControlCode == 10)
{
//Control codes and commands
if (iControlValue == 1)
{
gSystemEnabled = true;
Serial.println("System Enabled");

}
else if (iControlValue == 2)
{
gSystemEnabled = false;
Serial.println("System Disabled");

}
else if (iControlValue == 3)
{
gTempCW = !gTempCW;
if (gTempCW == true)
{
Serial.println("manual moves command clockwise");
}
else
{
Serial.println("manual moves command counter-clockwise");
}

}
else if (iControlValue == 4)
{
//spare

}
else if (iControlValue == 5)
{
setSystemTimeNow();

}
else if (iControlValue == 6)
{
postAppData();

}
else if (iControlValue == 7)
{
Serial.print(gHour);
Serial.print(":");
Serial.print(gMinute);
Serial.print(":");
Serial.print(gSecond);
Serial.print(" ");
Serial.print(gDay);
Serial.print("-");
Serial.print(gMonth);
Serial.print("-");
Serial.println(gYear);

}
else if (iControlValue == 8)
{
Serial.print("Hour stepper: ");
Serial.print(gHourStepper.currentPosition());
Serial.print(", Minute stepper: ");
Serial.print(gMinStepper.currentPosition());
Serial.print(", Second stepper: ");
Serial.println(gSecStepper.currentPosition());

}
else if (iControlValue == 20)
{
debugTimeSet();
setSystemTimeNow();

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 11)
{
//HOUR servo manual move commands
if (iControlValue >= 0 && iControlValue <= 359)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

gHourStepper.moveTo(dStepperMove);
Serial.print("HOUR stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 12)
{
//MINUTE servo manual move commands
if (iControlValue >= 0 && iControlValue <= 360)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

gMinStepper.moveTo(dStepperMove);
Serial.print("Minute stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 13)
{
//SECOND servo manual move commands
if (iControlValue >= 0 && iControlValue <= 360)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

if (gTempCW == false)
{
dStepperMove *= -1;
}

gSecStepper.moveTo(dStepperMove);
Serial.print("Second stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

//todo: save to EEPROM for all three motor offset values
if (iControlCode == 14)
{
//HOUR offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gHourOffset = iControlValue;
Serial.print("Hour servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gHourOffset = iTempMath;
Serial.print("Hour servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 15)
{
//MINUTE offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gMinuteOffset = iControlValue;
Serial.print("Minute servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gMinuteOffset = iTempMath;
Serial.print("Minute servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 16)
{
//SECOND offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gSecondOffset = iControlValue;
Serial.print("Second servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gSecondOffset = iTempMath;
Serial.print("Second servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 17)
{
//Manual HOUR setting
if (iControlValue >= 0 && iControlValue <= 23)
{
gHour = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Hour set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 18)
{
//Manual MINUTE setting
if (iControlValue >= 0 && iControlValue <= 59)
{
gMinute = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Minute set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 19)
{
//Manual SECOND setting
if (iControlValue >= 0 && iControlValue <= 59)
{
gSecond = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Second set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 20)
{
//Manual DAY setting
if (iControlValue >= 0 && iControlValue <= 31)
{
gDay = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Day set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 21)
{
//Manual MONTH setting
if (iControlValue >= 0 && iControlValue <= 12)
{
gMonth = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Month set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 22)
{
//Manual YEAR setting
if (iControlValue >= 0 && iControlValue <= 100)
{
gYear = 2000 + iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Year set to: ");
Serial.println(gYear);
}
}

}

void debugTimeSet()
{

gTimeSynced = false;

gYear = 2018;
gMonth = 10;
gDay = 13;

gHour = 13;
gMinute = 49;
gSecond = 0;

Serial.println("Debug time set initiated");

}

void postAppData()
{
//Backward Analog World Clock (BABwatch)
//Scott Mangiacotti
//Tucson, Arizona USA
//November 2016

Serial.println("BABwatch");
Serial.println("Backward Analog World Clock");
Serial.println("V2");
Serial.println("By Scott Mangiacotti");
Serial.println("Tucson, Arizona USA");
Serial.println("September 2018");
Serial.println("-----");

}
##### Arduino codeArduino
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <Time.h>
#include <TimeLib.h>
#include <EEPROM.h>

//Backward Analog World Clock (BABwatch_2)
//Scott Mangiacotti
//Tucson, Arizona USA
//September 2018

//Global constants
const int GIVE_BACK = 5;  //number milliseconds delay at end of each scan to ensure not using 100% CPU

//Constants for the steppers
const int STEPS = 4096;
const int HALF_STEP = 8;

const int HOUR_STEPPER_IN1 = 2;
const int HOUR_STEPPER_IN2 = 3;
const int HOUR_STEPPER_IN3 = 4;
const int HOUR_STEPPER_IN4 = 5;

const int MIN_STEPPER_IN1 = 6;
const int MIN_STEPPER_IN2 = 7;
const int MIN_STEPPER_IN3 = 8;
const int MIN_STEPPER_IN4 = 9;

const int SEC_STEPPER_IN1 = 10;
const int SEC_STEPPER_IN2 = 11;
const int SEC_STEPPER_IN3 = 12;
const int SEC_STEPPER_IN4 = 13;

//Global variables
AccelStepper gHourStepper(HALF_STEP, HOUR_STEPPER_IN1, HOUR_STEPPER_IN3, HOUR_STEPPER_IN2, HOUR_STEPPER_IN4);
AccelStepper gMinStepper(HALF_STEP, MIN_STEPPER_IN1, MIN_STEPPER_IN3, MIN_STEPPER_IN2, MIN_STEPPER_IN4);
AccelStepper gSecStepper(HALF_STEP, SEC_STEPPER_IN1, SEC_STEPPER_IN3, SEC_STEPPER_IN2, SEC_STEPPER_IN4);

int gHour;
int gMinute;
int gSecond;
int gDay;
int gMonth;
int gYear;
bool gTimeSynced = false;

bool gTempCW = false;

int gHourOffset = 0;     //units +/- degrees
int gMinuteOffset = 0;   //units +/- degrees
int gSecondOffset = 0;   //units +/- degrees

bool gSystemEnabled = false; //determines if servos are moved to match wallclock time every loop scan

//Run once
void setup()
{

//Setup static stepper properties
gHourStepper.setMaxSpeed(1000.0);
gHourStepper.setAcceleration(100.0);
gHourStepper.setSpeed(999);

gMinStepper.setMaxSpeed(1000.0);
gMinStepper.setAcceleration(100.0);
gMinStepper.setSpeed(999);

gSecStepper.setMaxSpeed(1000.0);
gSecStepper.setAcceleration(100.0);
gSecStepper.setSpeed(999);

//Initialize serial port
Serial.begin(9600);

//Post app data
postAppData();

}

//Run continuous
void loop()
{

//Parse into separate variables
if (gTimeSynced == true)
{
gHour = hour();
gMinute = minute();
gSecond = second();
gDay = day();
gMonth = month();
gYear = year();
}

if (gSystemEnabled == true)
{
if (gTimeSynced == true)
{
//Calculate HOUR hand position
if (gHourStepper.currentPosition() ==  gHourStepper.targetPosition())
{
calculateHourMoveTo(gHour);
}

//Calculate MINUTE hand position
if (gMinStepper.currentPosition() ==  gMinStepper.targetPosition())
{
calculateMinuteMoveTo(gMinute);
}

//Calculate SECOND hand position
if (gSecStepper.currentPosition() ==  gSecStepper.targetPosition())
{
calculateSecondMoveTo(gSecond);
}

}
else
{
gSystemEnabled = false;
Serial.println("Time not synchronized");

}
}

//Process serial messages
if (Serial.available() > 0)
{
processMessage();

}

//Run the motors until desired position achieved
gHourStepper.setSpeed(999);
gHourStepper.runSpeedToPosition();

gMinStepper.setSpeed(999);
gMinStepper.runSpeedToPosition();

gSecStepper.setSpeed(-999);
gSecStepper.runSpeedToPosition();

//Give some time back
delay(GIVE_BACK);

}

//Based on hour value from wallclock time, calculate and move to hour position
//Hour to be passed in 24-hour time
void calculateHourMoveTo(int iHour)
{
int iCWposition;
int iCCWposition;

//Validate
if (iHour < 0 && iHour > 23)
{
Serial.println("Invalid hour parameter in function calculateHourMoveTo");
return;
}

//Calculate hour servo position for clockwise clock
//12 hours per day
//360 degrees in a full circule
//360/12 = 30 degrees per hour

//Convert 24-hour format hour into 12-hour format
if (iHour > 12)
{
iHour = iHour - 12;
}

//Calculate position based on above formula
iCWposition = iHour * 30;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gHourStepper.moveTo(dStepperMove);

}

//Based on minute value from wallclock time, calculate and move to minute position
void calculateMinuteMoveTo(int iMinute)
{

int iCWposition;
int iCCWposition;

//Validate
if (iMinute < 0 && iMinute > 12)
{
Serial.println("Invalid minute parameter in function calculateMinuteMoveTo");
return;
}

//Calculate minute servo position for clockwise clock
//60 minutes per hour
//360 degrees in a full circule
//360/60 = 6 degrees per minute

//Calculate position based on above formula
iCWposition = iMinute * 6;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gMinStepper.moveTo(dStepperMove);

}

//Based on second value from wallclock time, calculate and move to second position
void calculateSecondMoveTo(int iSecond)
{

int iCWposition;
int iCCWposition;

//Validate
if (iSecond < 0 && iSecond > 12)
{
Serial.println("Invalid second parameter in function calculateSecondMoveTo");
return;
}

//Calculate second servo position for clockwise clock
//60 seconds per minute
//360 degrees in a full circule
//360/60 = 6 degrees per second

//Calculate position based on above formula
iCWposition = iSecond * 6;

//Deal with roll-over
if (iCWposition == 360)
{
iCWposition = 0;
}

//Calculate counter-clockwise
//360 - CW position calculated above
iCCWposition = 360 - iCWposition;

//Deal with roll-over
if (iCCWposition == 360)
{
iCCWposition = 0;
}

//Move stepper. Above math calculates in degrees. Following converts into stepper "steps"
double dStepperMove;
dStepperMove = (iCCWposition/360.00) * STEPS;//ilem: convert offset into steps and add
gSecStepper.moveTo(dStepperMove);

//  if (iSecond == 0 || iSecond == 1)
//  {
//    Serial.println(dStepperMove);
//
//  }

}

//Set SBC time, declare synced and ready to run and display results
void setSystemTimeNow()
{

setTime(gHour, gMinute, gSecond, gDay, gMonth, gYear);
gTimeSynced = true;
Serial.println("System time set successfully");

}

//Read data from serial port and process message from power user
//Format is: XXnnnn
//XX is a value between 1 - 32 and represents the command type or area (for example manual commands to the HOUR servo motor)
//nnnn is a value between 0-1000 and represents the value for the target command type
//For example, 01180 is type 02 and value 180. It represents HOUR servo motor move to position 180 degrees
//See documentation for command definitions and value ranges
void processMessage()
{
int iMessage;
int iControlCode;
int iControlValue;

//Read the data in the serial port buffer
iMessage = Serial.parseInt();
Serial.println(iMessage);

//Process the serial port message
if (iMessage > 0)
{
iControlCode = iMessage/1000;
Serial.print("Control Code: ");
Serial.println(iControlCode);

iControlValue = iMessage % 1000;
Serial.print("Control Value: ");
Serial.println(iControlValue);
}

//Misc control and command codes
if (iControlCode == 10)
{
//Control codes and commands
if (iControlValue == 1)
{
gSystemEnabled = true;
Serial.println("System Enabled");

}
else if (iControlValue == 2)
{
gSystemEnabled = false;
Serial.println("System Disabled");

}
else if (iControlValue == 3)
{
gTempCW = !gTempCW;
if (gTempCW == true)
{
Serial.println("manual moves command clockwise");
}
else
{
Serial.println("manual moves command counter-clockwise");
}

}
else if (iControlValue == 4)
{
//spare

}
else if (iControlValue == 5)
{
setSystemTimeNow();

}
else if (iControlValue == 6)
{
postAppData();

}
else if (iControlValue == 7)
{
Serial.print(gHour);
Serial.print(":");
Serial.print(gMinute);
Serial.print(":");
Serial.print(gSecond);
Serial.print(" ");
Serial.print(gDay);
Serial.print("-");
Serial.print(gMonth);
Serial.print("-");
Serial.println(gYear);

}
else if (iControlValue == 8)
{
Serial.print("Hour stepper: ");
Serial.print(gHourStepper.currentPosition());
Serial.print(", Minute stepper: ");
Serial.print(gMinStepper.currentPosition());
Serial.print(", Second stepper: ");
Serial.println(gSecStepper.currentPosition());

}
else if (iControlValue == 20)
{
debugTimeSet();
setSystemTimeNow();

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 11)
{
//HOUR servo manual move commands
if (iControlValue >= 0 && iControlValue <= 359)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

gHourStepper.moveTo(dStepperMove);
Serial.print("HOUR stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 12)
{
//MINUTE servo manual move commands
if (iControlValue >= 0 && iControlValue <= 360)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

gMinStepper.moveTo(dStepperMove);
Serial.print("Minute stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 13)
{
//SECOND servo manual move commands
if (iControlValue >= 0 && iControlValue <= 360)
{
double dStepperMove;
dStepperMove = (iControlValue/360.00) * STEPS;  //the compiler will not do floating point math without the .00 on the constant

if (gTempCW == false)
{
dStepperMove *= -1;
}

gSecStepper.moveTo(dStepperMove);
Serial.print("Second stepper commanded to position: ");
Serial.println(dStepperMove);

}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

//todo: save to EEPROM for all three motor offset values
if (iControlCode == 14)
{
//HOUR offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gHourOffset = iControlValue;
Serial.print("Hour servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gHourOffset = iTempMath;
Serial.print("Hour servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 15)
{
//MINUTE offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gMinuteOffset = iControlValue;
Serial.print("Minute servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gMinuteOffset = iTempMath;
Serial.print("Minute servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 16)
{
//SECOND offset commands
if (iControlValue >= 0 && iControlValue <= 180)
{
gSecondOffset = iControlValue;
Serial.print("Second servo offset: ");
Serial.println(iControlValue);

}
else if (iControlValue >= 181 && iControlValue <= 360)
{
int iTempMath;
iTempMath = 181 - iControlValue;
gSecondOffset = iTempMath;
Serial.print("Second servo value: ");
Serial.print(iControlValue);
Serial.print(" processed as servo offset: ");
Serial.println(iTempMath);
}
else
{
Serial.print("Invalid Control Value: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 17)
{
//Manual HOUR setting
if (iControlValue >= 0 && iControlValue <= 23)
{
gHour = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Hour set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 18)
{
//Manual MINUTE setting
if (iControlValue >= 0 && iControlValue <= 59)
{
gMinute = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Minute set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 19)
{
//Manual SECOND setting
if (iControlValue >= 0 && iControlValue <= 59)
{
gSecond = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Second set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 20)
{
//Manual DAY setting
if (iControlValue >= 0 && iControlValue <= 31)
{
gDay = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Day set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 21)
{
//Manual MONTH setting
if (iControlValue >= 0 && iControlValue <= 12)
{
gMonth = iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Month set to: ");
Serial.println(iControlValue);
}
}

if (iControlCode == 22)
{
//Manual YEAR setting
if (iControlValue >= 0 && iControlValue <= 100)
{
gYear = 2000 + iControlValue;
gTimeSynced = false;  //user is trying to manually set time on  SBC
Serial.print("Year set to: ");
Serial.println(gYear);
}
}

}

void debugTimeSet()
{

gTimeSynced = false;

gYear = 2018;
gMonth = 10;
gDay = 13;

gHour = 13;
gMinute = 49;
gSecond = 0;

Serial.println("Debug time set initiated");

}

void postAppData()
{
//Backward Analog World Clock (BABwatch)
//Scott Mangiacotti
//Tucson, Arizona USA
//November 2016

Serial.println("BABwatch");
Serial.println("Backward Analog World Clock");
Serial.println("V2");
Serial.println("By Scott Mangiacotti");
Serial.println("Tucson, Arizona USA");
Serial.println("September 2018");
Serial.println("-----");

}

## Custom parts and enclosures

babwatch_2_miIiFYfm00.xlsx

## Schematics

• 8 projects
• 19 followers

October 13, 2018

#### Members who respect this project

and 4 others

See similar projects
you might like

#### Analog Clock with LED Matrix and Arduino

Project showcase by LAGSILVA

• 22,452 views
• 79 respects

#### How to Make Analog Clock & Digital clock with Led Strip

Project tutorial by DKARDU

• 4,973 views
• 9 respects

#### Arduino Analog Panel Meter Clock

Project tutorial by Mirko Pavleski

• 13,153 views
• 68 respects

#### Simple Arduino Digital Clock Without RTC

Project in progress by Annlee Fores

• 165,093 views
• 155 respects

#### Arduino Without External Clock Crystal on ATmega328

Project tutorial by Techmirtz

• 45,861 views