scott mangiacotti
Published © GPL3+

Backward Running Analog Clock with Three Steppers

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

EasyFull instructions provided4 hours1,029
Backward Running Analog Clock with Three Steppers

Things used in this project

Hardware components

Arduino Pro Mini 328 - 5V/16MHz
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
Breadboard (generic)
Breadboard (generic)
×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

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Custom parts and enclosures

Instructions

Schematics

Schematic

Code

Arduino code

Arduino
#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.print("Message received: ");
  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 code

Arduino
#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.print("Message received: ");
  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("-----");
  
}

Credits

scott mangiacotti

scott mangiacotti

5 projects • 12 followers

Comments