// update min hand every 15 sec, correct for step calc rounding errors every hour.
#include <Button2.h>
#include <Encoder.h>
#include <DS1307RTC.h> // https://github.com/PaulStoffregen/DS1307RTC
// SDA to A4, CLK to A5, VCC to 5V
#include <Timezone.h> // https://github.com/JChristensen/Timezone
#include <EEPROM.h>
// To set the time of the RTC to the local time when compiled, set setRTC to true. Once the RTC time is set, set it to false and reflash.
#define setRTC false
// Motor and clock parameters
const long STEPS_PER_REV = 4096; //64 STEPS PER MOTOR REV * 64:1 GEAR RATIO
/*
4096 *1/60 hr to min * 1/60 min to sec = 1.3777 steps/sec
15 sec * 1.377777 = 17.06666 steps. Use 17 steps every 15 sec.
.066666*4*60 = 16 step undershoot per hour.
*/
//#define STEPS_PER_REV 4096 // steps of a single rotation of motor
//#define RATIO 15 // minutes per a rotation
#define BUTTON_PIN 12
Button2 button;
// wait for a single step of stepper
int delaytime = 2;
// ports used to control the stepper motor
// if your motor rotate to the opposite direction,
// 7 to IN1, 6 to IN2, 5 to IN3, 4 to IN5
// change the order as {4, 5, 6, 7};
int port[4] = { 7, 6, 5, 4 };
// sequence of stepper motor control
int seq[8][4] = {
{ LOW, HIGH, HIGH, LOW },
{ LOW, LOW, HIGH, LOW },
{ LOW, LOW, HIGH, HIGH },
{ LOW, LOW, LOW, HIGH },
{ HIGH, LOW, LOW, HIGH },
{ HIGH, LOW, LOW, LOW },
{ HIGH, HIGH, LOW, LOW },
{ LOW, HIGH, LOW, LOW }
};
void rotate(int step) {
static int phase = 0;
int i, j;
int delta = (step > 0) ? 1 : 7;
step = (step > 0) ? step : -step;
for (j = 0; j < step; j++) {
phase = (phase + delta) % 8;
for (i = 0; i < 4; i++) {
digitalWrite(port[i], seq[phase][i]);
}
delay(delaytime);
}
// power cut
for (i = 0; i < 4; i++) {
digitalWrite(port[i], LOW);
}
}
#define ENCODER_CLK 2
#define ENCODER_DT 3
Encoder myEnc(ENCODER_CLK, ENCODER_DT);
// void pressed(Button2& btn) {
// while (digitalRead(12) == LOW) {
// delaytime = 2;
// rotate(-68);
// }
// delaytime = 10;
// }
void click(Button2& btn) {
delaytime = 2;
for (int i = 0; i <= 16; i++) {
rotate(-256);
}
delaytime = 10;
}
void doubleClick(Button2& btn) {
delaytime = 2;
for (int i = 0; i <= 16; i++) {
rotate(+256);
}
delaytime = 10;
}
// US Pacific Time - assuming RTC is set to PST
TimeChangeRule myDST = {"PDT", Second, Sun, Mar, 2, +60}; //Daylight time = RTC + 60 minutes
TimeChangeRule mySTD = {"PST", First, Sun, Nov, 2, 0}; //Standard time = RTC + 0 minutes
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr; //pointer to the time change rule, use to get TZ abbrev
int prevMin = 0;
int thisMin = 0;
int thisSec = 0;
int prevSec = 0;
int thisHr = 0;
int prevHr = 0;
int hour12;
bool prevDST;
int interval = 1000;
unsigned long lastmilli = 0;
//long steps = 0;
//int incr = 0;
long oldPosition = -999; // set encoder initial position
int elapseSec = 0;
int elapseMin = 0;
char buf[128]; //buffer for sprintf
time_t t;
time_t tz;
tmElements_t tm;
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
bool getTime(const char *str)
{
int Hour, Min, Sec;
if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
tm.Hour = Hour;
tm.Minute = Min;
tm.Second = Sec;
return true;
}
bool getDate(const char *str)
{
char Month[12];
int Day, Year;
uint8_t monthIndex;
if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
}
if (monthIndex >= 12) return false;
tm.Day = Day;
tm.Month = monthIndex + 1;
tm.Year = CalendarYrToTm(Year);
return true;
}
void setup() {
Serial.begin(9600);
//Only set this once on a fresh EEPROM.
//EEPROM.update(0, 1); // (addr, DST) setting to DST true
#if setRTC
bool parse=false;
bool config=false;
// get the date and time the compiler was run
if (getDate(__DATE__) && getTime(__TIME__)) {
parse = true;
// and configure the RTC with this info
if (RTC.write(tm)) {
config = true;
}
}
while (!Serial) ; // wait for Arduino Serial Monitor
delay(200);
if (parse && config) {
Serial.print("DS1307 configured Time=");
Serial.print(__TIME__);
Serial.print(", Date=");
Serial.println(__DATE__);
} else if (parse) {
Serial.println("DS1307 Communication Error :-{");
Serial.println("Please check your circuitry");
} else {
Serial.print("Could not parse info from the compiler, Time=\"");
Serial.print(__TIME__);
Serial.print("\", Date=\"");
Serial.print(__DATE__);
Serial.println("\"");
}
#endif
setSyncProvider(RTC.get); // the function to get the time from the RTC
if (timeStatus() != timeSet)
Serial.println("Unable to sync with the RTC");
else
Serial.println("RTC has set the system time");
pinMode(port[0], OUTPUT);
pinMode(port[1], OUTPUT);
pinMode(port[2], OUTPUT);
pinMode(port[3], OUTPUT);
// pinMode(14, OUTPUT);
// digitalWrite(14, LOW);
// pinMode(16, INPUT_PULLUP);
button.begin(BUTTON_PIN);
//button.setPressedHandler(pressed);
button.setClickHandler(click);
button.setDoubleClickHandler(doubleClick);
t = now();
tz = myTZ.toLocal(t, &tcr);
hour12 = (hour(tz) + 11) % 12 + 1; // take care of noon and midnight-- converts 24h time to 12h time
// Serial.print(hour(tz));
// Serial.println(" hr");
// Serial.print(minute(tz));
// Serial.println(" min");
prevSec = second(tz);
thisSec = prevSec;
prevMin = minute(tz);
thisMin = prevMin;
prevHr = hour(tz);
thisHr = prevHr;
int delaytime = 10;
bool isDST = myTZ.locIsDST(tz); // if true, then in daylight savings is active.
//EEPROM.update(0, isDST);
// verify RTC is set to standard time
sprintf(buf, "%02d %s %02d:%02d %d", day(tz), monthName[month(tz)-1], hour(tz), minute(tz), isDST);
Serial.println(buf);
}
void loop() {
unsigned long thismilli = millis();
button.loop();
if (thismilli - lastmilli > interval) {
lastmilli = thismilli;
t = now();
tz = myTZ.toLocal(t, &tcr);
hour12 = (hour(tz) + 11) % 12 + 1; // take care of noon and midnight-- converts 24h time to 12h time
thisSec = second(tz);
thisMin = minute(tz);
thisHr = hour(tz);
//Serial.print(thisMin); Serial.println(" thisMin");
// update min hand every 15 sec
if (thisSec != prevSec) {
elapseSec++;
prevSec = thisSec;
if (elapseSec % 15 == 0) {
rotate(17); //assume direct 1:1 gearing between stepper output shaft and min hand
Serial.print("elapseSec: ");
Serial.println(elapseSec);
if (elapseSec >= 15) elapseSec = 0;
}
}
// catch up step count each hour
if (thisMin != prevMin) {
elapseMin++;
prevMin = thisMin;
if (elapseMin % 60 == 0) {
rotate(16); //correct cumulative step error
if (elapseMin >= 60) elapseMin = 0;
}
//check if dst has changed
//track isDST and if this changes then move the hour hand +1 or -1 hour depending on if moving into or out of DST.
prevDST = EEPROM.read(0); //if EEPROM has never been set, this will be 255
bool isDST = myTZ.locIsDST(tz); // if true, then in daylight savings is active.
if (prevDST >= 2 ) EEPROM.update(0, isDST); //if invalid data, update with current isDST value
//compare prevDST to currentDST
//if prevDST = 0 (not DST) and isDST = 1 (is DST) then time has moved from standard time to daylight savings time. Diff = -1
//if prevDST = 0 (not DST) and isDST = 0 (not DST) then no change. Diff = 0
//if prevDST = 1 (is DST) and isDST = 0 (not DST) then time has moved from daylight savings to standard time. Diff = 1
//if prevDST = 1 (is DST) and isDST = 1 (is DST) then no change. Diff = 0
int diff = prevDST - isDST;
Serial.println(diff);
if (diff == -1) { // moved from ST to DST, so "Spring Back"
// roll back an hour | stepper -4096 steps
EEPROM.update(0, isDST); //store new DST value
delaytime = 2;
for (int i = 0; i <= 15; i++) {
rotate(-256);
}
delaytime = 10;
}
if (diff == 1) { // moved from DST to ST, so "Fall Forward"
// add an hour | stepper motor +4096 steps
EEPROM.update(0, isDST); //store new DST value
delaytime = 2;
for (int i = 0; i <= 15; i++) {
rotate(256);
}
delaytime = 10;
}
}
}
//read Encoder
long newPosition = myEnc.read() / 4;
if (newPosition != oldPosition) {
if (newPosition > oldPosition) {
//clockwise
delaytime = 2;
rotate(-68);
delaytime = 10;
} else {
//anti-clockwise
delaytime = 2;
rotate(68);
delaytime = 10;
}
oldPosition = newPosition;
}
}