// This version uses RTC to set time instead of the CPU chrystal.
/*
28BYJ-48 stepper motor with ULN2003
IN1 7
IN2 6
IN3 5
IN4 4
32 full step with 64:1 gear ratio = 2048
*/
#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 <Button2.h>
#include <Encoder.h>
#include <EEPROM.h>
// 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
// Define stepper motor connections
// 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 IN4
// change the order as {4, 5, 6, 7};
int port[4] = { 7, 6, 5, 4 };
// wait for a single step of stepper
int delaytime = 2;
// 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);
}
}
const long stepsPerRevolution = 4096; // Adjust based on your motor (Stepper.h - not sure how to half-step yet)
#define BUTTON_PIN 12
Button2 button;
#define ENCODER_CLK 2
#define ENCODER_DT 3
Encoder myEnc(ENCODER_CLK, ENCODER_DT);
char buf[128]; //buffer for sprintf
time_t t;
time_t tz;
unsigned long prevminuteSteps = 0;
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
void click(Button2 &btn) {
for (int i = 0; i <= 15; i++) {
rotate(-256);
delay(50);
}
}
void doubleClick(Button2 &btn) {
for (int i = 0; i <= 15; i++) {
rotate(256 / 2);
delay(50);
}
}
// 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
tmElements_t tm;
bool getTime(const char *str);
bool getDate(const char *str);
void setup() {
Serial.begin(115200);
pinMode(port[0], OUTPUT);
pinMode(port[1], OUTPUT);
pinMode(port[2], OUTPUT);
pinMode(port[3], OUTPUT);
#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
button.begin(BUTTON_PIN);
button.setClickHandler(click);
button.setDoubleClickHandler(doubleClick);
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");
// intialize steps
t = now();
tz = myTZ.toLocal(t, &tcr); //set to local time zone
int seconds = second(tz);
int minutes = minute(tz);
int hours = hour(tz);
unsigned long minuteSteps = hours * stepsPerRevolution + (minutes * stepsPerRevolution + seconds * stepsPerRevolution / 60) / 60;
prevminuteSteps = minuteSteps; //remove intial time offest
}
unsigned long clockupdateinterval = 15000;
unsigned long correctioninterval = 1800000;
unsigned long dstinterval = 3600000;
unsigned long prevTime = 0;
unsigned long prevDSTTime = 0;
unsigned long prevCorrectionTime = 0;
long cumuSteps = 0;
long oldPosition = 0; // set encoder initial position
int prevDST;
int hour12 = 0;
void loop() {
button.loop();
long currentTime = millis();
if (currentTime - prevTime > clockupdateinterval) {
prevTime = currentTime;
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
int seconds = second(tz);
int minutes = minute(tz);
int hours = hour(tz);
bool isDST = myTZ.locIsDST(tz); // if true, then in daylight savings is active.
sprintf(buf, "%02d %s %02d:%02d %d", day(tz), monthName[month(tz) - 1], hour(tz), minute(tz), isDST);
Serial.println(buf);
// increment every minute resolution
//unsigned long minuteSteps = hours * stepsPerRevolution + minutes * stepsPerRevolution / 60;
// increment every second resolution
unsigned long minuteSteps = hours * stepsPerRevolution + (minutes * stepsPerRevolution + seconds * stepsPerRevolution / 60) / 60;
long steps = minuteSteps - prevminuteSteps;
prevminuteSteps = minuteSteps;
// Move stepper motors increment rotation matching interval time
rotate(steps);
Serial.println(steps);
}
if (currentTime - prevCorrectionTime > correctioninterval) {
//68 steps / min * 60 = 4080. 1 rev = 4096. need 16 steps per hour correction
prevCorrectionTime = currentTime;
rotate(16);
Serial.println("correction");
}
if (currentTime - prevDSTTime > dstinterval) {
prevDSTTime = currentTime;
Serial.println("check DST");
bool isDST = myTZ.locIsDST(tz); // if true, then in daylight savings is active.
// 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);
//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
prevDST = EEPROM.read(0);
}
//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;
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
for (int i = 0; i <= 15; i++) {
rotate(-256);
}
}
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
for (int i = 0; i <= 15; i++) {
rotate(256);
}
}
}
//read Encoder
long newPosition = myEnc.read() / 4;
if (newPosition != oldPosition) {
if (newPosition > oldPosition) {
//clockwise
rotate(-68);
} else {
//anti-clockwise
rotate(68);
}
oldPosition = newPosition;
}
}
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;
}