There is an older tower clock which was driven by a Master Clock. The Master Clock didn't exist anymore and I had to replace it. I decided to use an Arduino Uno with a cheap Real-Time clock board attached and a H-driver which I build by discreet transistors. The tower clock is driven by a separate 24V supply.
A HC-06 Bluetooth connector was added for remotely adjusting time and date and current position of clock handles.
During development a LCD-board with some buttons was added for easier controlling the clock. Later it was removed and is now replaced by the former mentioned Bluetooth interface.
The clock handles are moved by a stepper motor inside the clock. This stepper motor needs a voltage of 24V to reliable move the handles. The 24V are send once a minute for a part of a second to the stepper motor. The voltage alternates every other minute. This is done by an H-driver.
The H-driver consists of 4 transistors which drive the 24V to the stepper motor inside the clock. 2 extra transistors were added to drive each branch of the H-driver.
A DCF-77 atomic clock receiver is prepared but doesn't work reliable yet.
The clock manages daylight savings and has a simple astro-algorithm to light a LED stripe at night as background light.
Arduino Code
ArduinoDuring development there was a 2 line alphanumeric LCD display with keypad connected. Some code lines are still there from this time.
#include <DCF77.h>
#include <EEPROM.h>
#include <Wire.h>
#include <RTClib.h>
//#include <LiquidCrystal.h>
//#include <LCDKeypad.h>
#include <string.h>
#include <stdlib.h>
//LCDKeypad lcd;
RTC_DS1307 rtc;
DCF77 dcf(2, INT0);
int twrMinutes = 0;
int twrStep = 0;
int twrReset = 0;
int twrLightSwitch = 0;
int twrLightValue = 100;
void handleTowerClock(DateTime now);
int handleRTC(DateTime now);
void HandleBluetooth();
void doDebugOutput(char line, int row, int col);
boolean isSommerTime(int hour, int day, int month, int year, int dow);
byte c_up[8] = {
B00100,
B01110,
B10101,
B00100,
B00100,
B00100,
B00100,
B00100,
};
byte c_down[8] = {
B00100,
B00100,
B00100,
B00100,
B00100,
B10101,
B01110,
B00100,
};
byte c_select[8] = {
B00000,
B01110,
B11111,
B11111,
B11111,
B11111,
B01110,
B00000,
};
#define CLOCK_DISPLAY 0
#define CLOCK_MENU 1
#define CLOCK_TOWER 2
#define CLOCK_RESET 3
#define CLOCK_ADJUST 4
#define CLOCK_DEBUG 5
#define MAX_MENU_ITEM 5
#define pinClockA 12
#define pinClockB 13
#define PIN_LED 13
#define PIN_POWER 13
#define PIN_CLOCKLIGHT 11
#define PIN_BACKLIGHT 10
#define PIN_BATTERY A3
void setup()
{
Serial.begin(9600);
Serial.print("AT");
delay(1000);
while (Serial.available())
Serial.read();
Serial.print("AT+NAMEuferclock");
delay(1000);
while (Serial.available())
Serial.read();
Serial.print("AT+PIN1134");
delay(1000);
while (Serial.available())
Serial.read();
/*
lcd.createChar(1, c_up);
lcd.createChar(2, c_down);
lcd.createChar(3, c_select);
lcd.begin(16, 2);
lcd.clear();
*/
pinMode(PIN_BACKLIGHT, OUTPUT);
analogWrite(PIN_BACKLIGHT, 1);
pinMode(pinClockA, OUTPUT);
digitalWrite(pinClockA, LOW);
pinMode(pinClockB, OUTPUT);
digitalWrite(pinClockB, LOW);
//pinMode(PIN_LED, OUTPUT);
//digitalWrite(PIN_LED, LOW);
//pinMode(PIN_POWER, OUTPUT);
//digitalWrite(PIN_POWER, LOW);
pinMode(PIN_CLOCKLIGHT, OUTPUT);
digitalWrite(PIN_CLOCKLIGHT, LOW);
/*
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print("Hi from UferUhr");
delay(1000);
lcd.setCursor(0, 0);
lcd.print("Hi from UferUhr");
lcd.setCursor(0, 1);
lcd.print("---------------");
delay(1000);
lcd.setCursor(0, 0);
lcd.print("---------------");
lcd.setCursor(0, 1);
lcd.print(" ");
delay(1000);
*/
if (!rtc.begin())
{
doDebugOutput("Couldn't find RTC.", 0, 1);
}
if (!rtc.isrunning())
{
doDebugOutput("RTC is NOT running.", 0, 1);
}
/*
lcd.print(" ");
*/
// get twrminutes from a safe place
twrMinutes = rtc.readnvram(0) << 8;
twrMinutes |= rtc.readnvram(1);
twrStep = rtc.readnvram(2);
if ((twrMinutes < 0) || (twrMinutes >= (12 * 60)))
{
// if tower clock is not reasonable:
// assume the tower clock is in sync with current time
DateTime now = rtc.now();
twrMinutes = now.minute() + (now.hour() % 12) * 60;
if (isSommerTime(now.hour(), now.day(), now.month(), now.year(), now.dayOfTheWeek()))
{
twrMinutes += 60;
if (twrMinutes >= (12 * 60))
twrMinutes -= (12 * 60);
}
}
if (twrStep < 0)
{
twrStep = 0;
}
dcf.Start();
}
void loop()
{
static unsigned long lastTowerMillis = 0;
DateTime now = rtc.now();
time_t time = dcf.getTime();
if (time != 0)
{
Serial.print((int) time, 10);
Serial.println();
//rtc.adjust();
}
// cheap astro schaltung:
if (now.month() < 6)
{
if ((now.hour() < (4 + (6 - now.month()) * 4 / 6)) || (now.hour() > (21 - (6 - now.month()))))
twrLightSwitch = 1;
else
twrLightSwitch = 0;
}
else if (now.month() > 6)
{
if ((now.hour() < (4 + (now.month() - 6) * 4 / 6)) || (now.hour() > (21 - (now.month() - 6))))
twrLightSwitch = 1;
else
twrLightSwitch = 0;
}
else
{
if ((now.hour() < 4) || (now.hour() > 21))
twrLightSwitch = 1;
else
twrLightSwitch = 0;
}
if (isSommerTime(now.hour(), now.day(), now.month(), now.year(), now.dayOfTheWeek()))
{
now = DateTime(now.unixtime() + (60 * 60));
}
/*
lcd.setCursor(0, 0);
*/
static int oldTwrMinutes = 0;
if (twrMinutes != oldTwrMinutes)
{
rtc.writenvram(0, (twrMinutes >> 8) & 0xff);
rtc.writenvram(1, twrMinutes & 0xff);
rtc.writenvram(2, twrStep & 0xff);
oldTwrMinutes = twrMinutes;
}
//if (handleLCD(now))
{
// this also handles rollovers
unsigned long currentMillis = millis();
if ((unsigned long)(currentMillis - lastTowerMillis) >= 500)
{
lastTowerMillis = currentMillis;
handleTowerClock(now);
}
}
if (twrLightSwitch > 0)
analogWrite(PIN_CLOCKLIGHT, twrLightValue);
else
digitalWrite(PIN_CLOCKLIGHT, 0);
//digitalWrite(PIN_POWER, HIGH);
delayMicroseconds(100);
//digitalWrite(PIN_POWER, LOW);
HandleBluetooth();
}
void handleTowerClock(DateTime now)
{
// convert now into minutes
int rtcMinutes = now.minute() + (now.hour() % 12) * 60;
if (twrReset)
{
// use a fixed time if we reset the clock to 09:05
rtcMinutes = 9 * 60 + 5;
}
// increase twrMinutes as long as there is a difference between rtcMinutes and twrMinutes
int diffMinutes = (twrMinutes < rtcMinutes) ? (twrMinutes + (12 * 60) - rtcMinutes) : (twrMinutes - rtcMinutes);
//if (twrMinutes != rtcMinutes)
//if (1 == 1)
if (diffMinutes >(2 * 60))
{
// move clock forward
if (twrStep == 0)
{
digitalWrite(pinClockA, HIGH);
}
else if (twrStep == 1)
{
digitalWrite(pinClockA, LOW);
twrMinutes++;
if (twrMinutes >= (12 * 60))
twrMinutes = 0;
}
else if (twrStep == 2)
{
digitalWrite(pinClockB, HIGH);
}
else if (twrStep == 3)
{
digitalWrite(pinClockB, LOW);
twrMinutes++;
if (twrMinutes >= (12 * 60))
twrMinutes = 0;
}
twrStep++;
if (twrStep > 3)
twrStep = 0;
}
}
void HandleBluetooth()
{
if (Serial.available())
{
char buffer[32];
Serial.println("Hello from UferUhr. (Send 's', 't' or 'l')");
DateTime now = rtc.now();
char chr = Serial.read();
if (chr == 's')
{
sprintf(buffer, "%02d.%02d.%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
Serial.println("Current Date/Time (w/o DST):");
Serial.println(buffer);
memset(buffer, 0, sizeof(buffer));
Serial.readBytesUntil('\n', buffer, sizeof(buffer));
Serial.print(strlen(buffer), DEC);
Serial.print(": ");
Serial.println(buffer);
if (strlen(buffer) >= 20)
{
int DD = atoi(buffer + 1);
int MM = atoi(buffer + 4);
int YY = atoi(buffer + 7);
int hh = atoi(buffer + 12);
int mm = atoi(buffer + 15);
int ss = atoi(buffer + 18);
rtc.adjust(DateTime(YY, MM, DD, hh, mm, ss));
now = rtc.now();
sprintf(buffer, "%02d.%02d.%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
Serial.println("New Date/Time (w/o DST):");
Serial.println(buffer);
}
else
{
Serial.println("To set Date/Time: s DD.MM.YYYY HH:MM:SS");
}
}
else if (chr == 't')
{
sprintf(buffer, "%02d.%02d", twrMinutes / 60, twrMinutes % 60);
Serial.println("TowerTime:");
Serial.println(buffer);
memset(buffer, 0, sizeof(buffer));
Serial.readBytesUntil('\n', buffer, sizeof(buffer));
Serial.print(strlen(buffer), DEC);
Serial.print(": ");
Serial.println(buffer);
if (strlen(buffer) >= 5)
{
int hh = atoi(buffer + 1);
int mm = atoi(buffer + 4);
twrMinutes = hh * 60 + mm;
sprintf(buffer, "%02d.%02d", twrMinutes / 60, twrMinutes % 60);
Serial.println("TowerTime:");
Serial.println(buffer);
}
else
{
Serial.println("To set TowerTime: t HH:MM");
}
}
else if (chr == 'l')
{
sprintf(buffer, "%02d", twrLightValue);
Serial.println("TowerLight:");
Serial.println(buffer);
memset(buffer, 0, sizeof(buffer));
Serial.readBytesUntil('\n', buffer, sizeof(buffer));
Serial.print(strlen(buffer), DEC);
Serial.print(": ");
Serial.println(buffer);
if (strlen(buffer) >= 1)
{
int l = atoi(buffer + 1);
if (l > 0) {
twrLightValue = l;
twrLightSwitch = 1;
}
else
{
twrLightSwitch = 0;
}
sprintf(buffer, "%02d", twrLightValue);
Serial.println("TowerLight:");
Serial.println(buffer);
}
else
{
Serial.println("To set TowerLight: l 0..255");
}
}
}
}
void doDebugOutput(const char *line, int row, int col)
{
// lcd.setCursor(row, col);
// lcd.print(line);
Serial.println(line);
// delay(1000);
}
/*
int handleLCD(DateTime now)
{
static int leftIndex = 0;
static int oldButtonPressed = KEYPAD_NONE;
static byte mode = 0;
static int hh, mm, ss;
static int DD, MM, YY;
static int menu_item = 0;
char buffer[20]; // 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 4´+ 1
int buttonPressed;
if (mode == CLOCK_DISPLAY)
{
// rtc
sprintf(buffer, "%02d:%02d:%02d %s ", now.hour() % 12, now.minute(), now.second(), (now.hour() >= 12) ? "PM" : "AM");
lcd.setCursor(0, 0);
lcd.print(buffer + leftIndex);
// tower clock and bat
sprintf(buffer, "Twr %02d:%02d", twrMinutes / 60, twrMinutes % 60);
lcd.setCursor(0, 1);
lcd.print(buffer);
}
else if (mode == CLOCK_MENU)
{
// menu
if (menu_item == 0)
{
lcd.setCursor(0, 0 - menu_item);
lcd.print(" EXIT MENU ");
}
if ((menu_item == 0) || (menu_item == 1))
{
lcd.setCursor(0, 1 - menu_item);
lcd.print(" ENTER CLOCK ");
}
if ((menu_item == 1) || (menu_item == 2))
{
lcd.setCursor(0, 2 - menu_item);
lcd.print(" RESET CLOCK ");
}
if ((menu_item == 2) || (menu_item == 3))
{
lcd.setCursor(0, 3 - menu_item);
lcd.print(" ADJUST RTC ");
}
if ((menu_item == 3) || (menu_item == 4))
{
lcd.setCursor(0, 4 - menu_item);
lcd.print(" DEBUG CLOCK ");
}
if (menu_item == 4)
{
lcd.setCursor(0, 5 - menu_item);
lcd.print(" ");
}
lcd.setCursor(0, 0);
lcd.print(">");
}
else if (mode == CLOCK_RESET)
{
// set clock to 9:05 and exit
lcd.setCursor(0, 0);
lcd.print("Reset to 9:05 ");
lcd.setCursor(0, 1);
lcd.print(" ");
}
else if (mode == CLOCK_TOWER)
{
// enter the current tower clock time
lcd.setCursor(0, 0);
lcd.print("Twr Clock: ");
sprintf(buffer, "%02d:%02d", twrMinutes / 60, twrMinutes % 60);
lcd.setCursor(11, 0);
lcd.print(buffer);
lcd.setCursor(0, 1);
lcd.print(" ");
if (leftIndex == 0)
{
lcd.setCursor(11, 1);
lcd.write(1);
lcd.write(2);
}
else
{
lcd.setCursor(14, 1);
lcd.write(1);
lcd.write(2);
}
}
else if (mode == CLOCK_ADJUST)
{
lcd.setCursor(0, 0);
sprintf(buffer, "%02d:%02d:%02d %02d.%02d.%04d", hh, mm, ss, DD, MM, YY);
if (leftIndex < 3)
{
lcd.print(buffer);
}
else
{
lcd.print(buffer + 3);
}
lcd.setCursor(0, 1);
lcd.print(" ");
if (leftIndex == 0)
{
lcd.setCursor(0, 1);
lcd.write(1);
lcd.write(2);
}
else if (leftIndex == 1)
{
lcd.setCursor(3, 1);
lcd.write(1);
lcd.write(2);
}
else if (leftIndex == 2)
{
lcd.setCursor(6, 1);
lcd.write(1);
lcd.write(2);
}
else if (leftIndex == 3)
{
lcd.setCursor(6, 1);
lcd.write(1);
lcd.write(2);
}
else if (leftIndex == 4)
{
lcd.setCursor(9, 1);
lcd.write(1);
lcd.write(2);
}
else if (leftIndex == 5)
{
lcd.setCursor(12, 1);
lcd.write(1);
lcd.write(1);
lcd.write(2);
lcd.write(2);
}
}
else if (mode == CLOCK_DEBUG)
{
while (lcd.button() == KEYPAD_SELECT)
{
delay(100);
}
lcd.setCursor(0, 0);
lcd.print("12 V on Pin A");
lcd.setCursor(0, 1);
lcd.print(" ");
digitalWrite(pinClockA, HIGH);
digitalWrite(PIN_LED, HIGH);
while (lcd.button() != KEYPAD_SELECT)
{
delay(100);
}
digitalWrite(pinClockA, LOW);
digitalWrite(PIN_LED, LOW);
delay(1000);
while (lcd.button() == KEYPAD_SELECT)
{
delay(100);
}
lcd.setCursor(0, 0);
lcd.print("12 V on Pin B");
lcd.setCursor(0, 1);
lcd.print(" ");
digitalWrite(pinClockB, HIGH);
digitalWrite(PIN_LED, HIGH);
while (lcd.button() != KEYPAD_SELECT)
{
delay(100);
}
digitalWrite(pinClockB, LOW);
digitalWrite(PIN_LED, LOW);
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
}
buttonPressed = lcd.button();
if (buttonPressed != oldButtonPressed)
{
if (buttonPressed == KEYPAD_LEFT)
{
if (leftIndex > 0)
leftIndex--;
}
else if (buttonPressed == KEYPAD_RIGHT)
{
if (mode == CLOCK_DISPLAY)
{
if (leftIndex < 3)
leftIndex++;
}
else if (mode == CLOCK_ADJUST)
{
if (leftIndex < 5)
leftIndex++;
}
else if (mode == CLOCK_TOWER)
{
if (leftIndex < 1)
leftIndex++;
}
}
else if (buttonPressed == KEYPAD_UP)
{
if (mode == CLOCK_ADJUST)
{
if (leftIndex == 0)
{
if (hh < 23)
hh++;
}
else if (leftIndex == 1)
{
if (mm < 59)
mm++;
}
else if (leftIndex == 2)
{
if (ss < 59)
ss++;
}
else if (leftIndex == 3)
{
if (DD < 31)
DD++;
}
else if (leftIndex == 4)
{
if (MM < 12)
MM++;
}
else if (leftIndex == 5)
{
if (YY < 2100)
YY++;
}
}
else if (mode == CLOCK_MENU)
{
menu_item--;
if (menu_item < 0)
menu_item = 0;
}
else if (mode == CLOCK_TOWER)
{
if (leftIndex == 0)
twrMinutes += 60;
else if (leftIndex == 1)
twrMinutes += 1;
if (twrMinutes >= (12 * 60))
twrMinutes -= 12 * 60;
}
}
else if (buttonPressed == KEYPAD_DOWN)
{
if (mode == CLOCK_ADJUST)
{
if (leftIndex == 0)
{
if (hh > 0)
hh--;
}
else if (leftIndex == 1)
{
if (mm > 0)
mm--;
}
else if (leftIndex == 2)
{
if (ss > 0)
ss--;
}
else if (leftIndex == 3)
{
if (DD > 1)
DD--;
}
else if (leftIndex == 4)
{
if (MM > 1)
MM--;
}
else if (leftIndex == 5)
{
if (YY > 2015)
YY--;
}
}
else if (mode == CLOCK_MENU)
{
menu_item++;
if (menu_item > MAX_MENU_ITEM)
menu_item = MAX_MENU_ITEM;
}
else if (mode == CLOCK_TOWER)
{
if (leftIndex == 0)
twrMinutes -= 60;
else if (leftIndex == 1)
twrMinutes -= 1;
if (twrMinutes < 0)
twrMinutes += 24 * 60;
}
}
else if (buttonPressed == KEYPAD_SELECT)
{
leftIndex = 0;
if (mode == CLOCK_DISPLAY)
{
// switch to time set mode
menu_item = 0;
mode = CLOCK_MENU;
analogWrite(10, 100);
}
else if (mode == CLOCK_MENU)
{
if (menu_item == 0)
{
// just show the current RTC
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
}
else if (menu_item == 1)
{
// enter the hour/minutes the tower clock displays
leftIndex = 0;
mode = CLOCK_TOWER;
}
else if (menu_item == 2)
{
// set tower clock to 9:05
mode = CLOCK_RESET;
}
else if (menu_item == 3)
{
leftIndex = 0;
now = rtc.now();
hh = now.hour();
mm = now.minute();
ss = now.second();
DD = now.day();
MM = now.month();
YY = now.year();
mode = CLOCK_ADJUST;
}
else if (menu_item == 4)
{
// steady 12 V
mode = CLOCK_DEBUG;
}
}
else if (mode == CLOCK_TOWER)
{
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
}
else if (mode == CLOCK_RESET)
{
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
twrReset = 1;
}
else if (mode == CLOCK_ADJUST)
{
// set time accordingly and switch mode
rtc.adjust(DateTime(YY, MM, DD, hh, mm, ss));
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
}
else if (mode == CLOCK_DEBUG)
{
mode = CLOCK_DISPLAY;
analogWrite(10, 1);
}
}
oldButtonPressed = buttonPressed;
}
delay(10);
// don't move clock if we just enter the current tower clock time
return (mode != CLOCK_TOWER);
}
*/
/******************************************************************************************/
/******************************************************************************************/
/*** Filter a 24 hour cycle to 12h am/pm. Detect (MEZ/MESZ) Standard and Daylight Saving Time (DST) ***/
/*** Create a 12 hour cycle with am/pm ***/
boolean isSommerTime(int hour, int day, int month, int year, int dow)
{
boolean pm = false; // Set to am per default
boolean dst = false;
/*** Switch to a 12 hour display ***/
if (hour > 12)
{
// 12h am/pm
hour -= 12;
pm = true;
}
/******************************************************************************************/
/*** Determine the Daylight Saving Time DST. In Germany it is the last Sunday in March and in October ***/
/*** In March at 2:00am the time will be turned forward to 3:00am and in ***/
/*** October at 3:00am it will be turned back to 2:00am and repeated as 2A and 2B ***/
/******************************************************************************************/
/*** Generally checking the full month and determine the DST flag is an easy job ***/
if ((month <= 2) || (month >= 11))
dst = false; // Winter months
if ((month >= 4) && (month <= 9))
dst = true; // Summer months
/*** Detect the beginning of the DST in March and set DST = 1 ***/
if ((month == 3) && ((day - dow) >= 25)) { // Begin of summer time
if (((pm == false) && (hour >= 2)) || (pm == true)) // MESZ – 1 hour
dst = true;
}
/*** Still summer months time DST beginning of October, so easy to determine ***/
if ((month == 10) && ((day - dow) < 25))
dst = true; // Summer months anyway until 24th of October
/*** Test the begin of the winter time in October and set DST = 0 ***/
if ((month == 10) && ((day - dow) >= 25))
{ // Test the begin of the winter time
if (((pm == false) && (hour >= 2)) || (pm == true))
{
// -1 since the RTC is running in GMT+1 only
dst = false;
Serial.println("We have winter time");
}
else
{
dst = true;
Serial.println("A good day! We have summer time");
}
}
return dst;
}
Comments
Please log in or sign up to comment.