Project tutorial
Arduino "Atomic" Grandfather Clock

Arduino "Atomic" Grandfather Clock © CC BY-NC-SA

I wanted an electro-mechanical Grandfather clock that did not require winding and correcting every week but maintained its antiquity.

  • 6,088 views
  • 1 comment
  • 7 respects

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
or Atmega 328
×1
LCD 4x20 display
×1

About this project

I wanted an electro-mechanical Grandfather clock that did not require winding and correcting every week but that looked and sounded as original as possible.

(see my site here Longcase Clock Website)

This tutorial shows how to add Arduino-controlled analogue movements to a Grandfather (longcase clock/tallcase clock) or any other clock case where analogue displays are required.

The Arduino gets the time code from the DCF77 transmitter in Germany and will work for any location in range of the transmitter including England.

The code can be used for any other transmitter in the world by changing the decoder library and modifying the code to suit.

This clock has 3 analogue dials, hours/mins, seconds and also a Moon phase dial. There is a secondary 4x20 LCD display under the hood to give full info on the clocks status. The clock controls are mounted on the same vero board as the LCD display.

The pendulum is electric and uses a heavy duty pendulum drive powered by a single 1.5v battery. This battery will last over a year.

As the clock is synchronized to the DCF77 transmitter as are all my clocks I use ticking sounds and full Westminster chimes from other clocks on my system.

Step 1: Video

Short 2 minute video showing some of the clock functions.

The clock ticking and chiming is controlled by other clocks on the system and can be turned off or on independently.

The 1 second ticking comes from my Dial Clock Dial Clock and chimes from my calendar clock .

or you can view Dial Clock Instructables here Dial Clock .

Step 2: Parts required

Clock case: CaseThe empty longcase clock case was purchased from Ebay and is probably from the early 1900s.

Dial: The dial is made from scratch using a sheet of aluminium for the dial plate and then chapter rings and spandrels were purchased from clock suppliers/Ebay. I had to design custom lettering for the Moon dial and this was applied to a spare seconds chapter ring using Lazertran paper.

Clock movements: 2 x standard UTS movements for the seconds and Moon phase and a radio movement for hour/minutes. All movements have their stepper motors isolated so the Arduino can control them.

Arduino: I used a self-built Arduino Uno on a veroboard but a prebuilt Uno will be fine as well. You will also need the electronic components and switches as per the schematic.

Pendulum drive: The trunk to my clock glazed and I wanted the pendulum to move so I used a heavy duty pendulum drive. If your case is closed then this will not be required.

Weights & Chains: Again as the trunk is glazed I added false weights and chains.

Step 3: The Case

The clock case is 2.1m x 0.415m x 0.21m or 6' 10" x 1' 4" x approx. 8" (height x width x depth) and is made of Oak. The removable hood will take an 8" breakarch dial and would have been accessed via the hinged door. The case has a fully glazed door, so it would probably have had decorative brass weights and chains along with a brass pendulum bob. I have fitted false weights and chain from an old clock and an electric motor to drive a false pendulum.

The Hood pic 3 slides off the clock after disconnecting the 5 volt power plug and DCF77 input wire.

Battery backup keeps the clock running while disconnected.

There is plenty of space in the hood for circuit boards, controls and displays.

Step 4: Dial Plate

The clock had no dial so a complete dial was made up from scratch.

Picture 1: Using the wooden dial surround as a template and leaving a 20mm overlap, the dial plate is cut from a sheet of aluminium. Holes are drilled to take miniature bolts: 4 for the hours/mins chapter ring; 2 for the moon and seconds chapter rings; and 2 each for the dial, break arch, and top centre spandrels. Holes are also drilled to take the hand shafts from the 3 modified movements. In the original clock the dial would have been mounted in the hood separate from the dial surround door to enable the time to be set from the front of the dial by moving the minute hand. On this clock the hands are set by remote switches so the dial can be screwed to the wooden dial surround. Holes are drilled around the edge to take these screws. The aluminium dial plate in the animation is shown in it's unpolished state.

Picture 2: Brass spandrels can be purchased from clock maker suppliers/Ebay. To give an aged effect, I sprayed them with black paint then rubbed the black paint off the highlights.

Picture 3: Once all dials and spandrels have been test fitted, the aluminium dial plate is given a final rub down in one direction only to give a grain effect, and a clear lacquer is added.

Picture 4: Rear view of dial shows the bolts fixing chapter rings and spandrels and clock motor modules fixed to mounting bars. The mounting bar for the seconds and Moon dial is set back from the dial plate to keep both seconds and Moon hands near the chapter rings. Note the dial plate is shown semi-transparent to show locations of chapter rings and spandrels on the front of the dial plate.

Step 5: Chapter Rings

The Moon chapter ring is made from a re-labelled seconds chapter ring. The seconds lettering is removed with wire wool then the moon lettering is printed out on Lazertran Paper applied and then lacquered over.

The hour & minute chapter ring is from an old clock dial and was already drilled out for mounting bolts.

Moon

Picture 1: The moon dial shows the current age of the moon with a new moon at 0 a full moon at around 15.The actual moon age in days and hours is shown on the LCD display while the moon dial moves a half segment in half a moon day.

Picture 2: This clock displays the Synodic month, and this varies as the Earth's orbit around the Sun and the Moon's orbit around the Earth are elliptical. The actual rate of Lunation, the time from new Moon to full Moon, is between 29.18 to 29.93 days. The long term average is 29.530587981 days and this number is used by this clock to determine the Moons age. The Moon dial is mechanically split into 59 segments (actually 60 but the code jumps the 60th segment) with each segment being 1/2 a Moon day. The Arduino code steps through exactly once per second and adds 1 to the Moon Count every second. A Lunation takes 29.530587981 days or 2551442.877 seconds. I calculated this by the following:

29 days = 29x24hours = 696 hours

696 hours = 696x60minutes = 41760 minutes

41760 minutes = 41760x60seconds = 2505600 seconds

So, 29 days = 2505600 seconds.

0.530587981 days = 0.530587981x24hours = 12.73413247 hours

12.73413247 hours = 12.73413247x60minutes = 764.0479483 minutes

764.0479483 minutes =764.0479483x60seconds = 45842.8769 seconds

So, 0.530587981 days = 45842.8769 seconds.

Add the two together:

2505600 seconds + 45842.8769 seconds = 2551442.877 seconds

So, 29.530587981 days = 2551442.877 seconds.

As the Moon display steps 59 times in a Lunation, each step is 2551442.877 seconds / 59 steps = 43244.79452 seconds. I have rounded this up to 43245 seconds. Every time the Arduino Moon count hits 43245, it advances the Moon display by 1 segment or 1/2 a Moon day. At the end of the Lunation, the Arduino steps 1 extra segment to return the Moon display to 0 days.

Seconds

Picture 3: The seconds chapter ring was purchased from Ebay and only needed holes drilled for the mounting bolts.

Hours/Minutes

Picture 4: The hour and minute chapter ring came with four pre-drilled holes where it had been mounted in it's original dial.

Step 6: Movements

Analogue Movements

Picture 1: There are three analogue movements - one each for the Hours/Minutes, Moon & Seconds displays. All the analogue displays are driven by Lavet-type stepping motors. The motors are sourced from 2 x U.T.S. quartz clock movements for seconds and moon display and a U.T.S. DCF77 Radio movement for the hour and minutes display. The motor requires very low current to drive it and can be driven directly from the Arduino output via a trimmer resistor. The resistor is used to adjust the current to the motor so it works without being over driven. The motor is driven by reversing the polarity to the drive coil which causes the permanent magnet toothed rotor (in red below) to turn 180°. The toothed rotor will continue to turn in the same direction each time the drive motor polarity is reversed. Two output pins from the Arduino are used to pulse the drive motor with 1 pin always the opposite to the other.

The Lavet motor in the U.T.S. quartz clock movements drives the attached hand 1/60th of a turn (1 second) on each activation while the Lavet motor in the U.T.S. DCF77 Radio movement drives the min hand (the hour hand is geared down from this) 1/240th of a turn or 15 seconds.

Hacking the movements

In order for the Arduino to control the drive motors, the 3 movements have to have their motors isolated from their on-board drive circuits.

U.T.S. DCF77 Radio Clock Movement Hack

Picture 2, 3: Carefully prise the movement apart and remove the top and bottom case sections.

Picture 4: This will leave the movement PCB, DCF77 aerial and motors.

Picture 5: Turn the PCB over so the solder side is visible.

Desolder the DCF77 aerial from its solder pads as it is not required. There are 2 drive coils on this movement: one for seconds and one for hours and minutes. Locate the 2 drive coil solder terminals (marked 1 and 2 above). 1 is hour and minutes; 2 is seconds. Cut one of the tracks to the minute and hour solder terminal to isolate the drive coil. Only the hour and minute coil is required, so wires are soldered to coil contacts 1 and 1. Take these 2 wires through the clock movement and out into the battery bay.

Picture 6: Solder the 2 wires from the hour/minute motor to the "Clk Motor Coil" terminals on the Lavet-type stepping motor driver board. Locate the PCB/motor board back in the 2 case sections making sure the wires do not foul the case and clip the case back together. The Lavet-type stepping motor driver board is "hot melt" glued in place.

U.T.S. Quartz Clock Movement Hack

Picture 7, 8, 9: Carefully prise the movement apart and remove the top and bottom case sections. The Quartz PCB and motor section can then be lifted out as one part.

Picture 10: Turn the Quartz PCB and motor section over to reveal the solder side of the PCB. Cut one of the tracks to the motor coil to isolate it. Solder wires to each of the coil solder pads and take them out of the clock into the battery bay.

Picture 11: The Lavet-type stepping motor driver board is "hot melt" glued in place.

Step 7: LCD secondary display

The LCD (picture 1) is a yellow 4x20 with an I2C module (picture 2) soldered to the board allowing 2 wire control by the Arduino.

The LCD Display shows the following information:

Row 0: Current time and date should be identical to the hours, minutes and seconds dials.

Row 1: Clock Name My Name & software version no. 1 second clock total missed (fast) pulses and extra (slow) pulses. When the clock drifts and then resets a second ahead of clock time. An extra 1 second pulse (slow) is required to keep correct time on 1 second clocks. When the clock drifts and then resets a second behind clock time. A 1 second pulse is missed (fast) to keep correct time on 1 second clocks. The total is reset at 06:10 in the morning. This row also displays the Summer/Winter advance retard information when the clock is correcting the hour and minute dial.

Row 2: This row shows the Moon age in Days, Hours, Minutes & Seconds. This row also displays decoding status Syn, Lck, Dty and Fai.

Row 3: This row shows the received signal accuracy in %, the Auto-Tuned Quartz crystal frequency accuracy down to 1 Hz and the actual Auto-Tuned Quartz crystal tuned frequency in Hz. Summertime/wintertime indicator GMT/GMT+1.

Step 8: Controls

There are 14 control switches in the clock controlling various functions.

SW5 Arduino reset non-locking switch that performs a reset on the Arduino.

SW1 Hour/Minutes dial On/Off Locking switch mounted in the hour/minutes motor housing and provides On/Off control of the hour/minutes display.

SW6 Moon Phase Dial On/Off Locking switch mounted in the Moon dial motor housing and provides On/Off control of the Moon phase display.

SW14 Seconds Dial On/Off Locking switch mounted in the Seconds dial motor housing and provides On/Off control of the Seconds display.

SW12 Seconds set Double pole three position non-locking switch with centre off. When SW13 is set to Manual SW12 steps the second hand when moved from the 2 step positions.

SW13 Seconds Control Double pole three-way locking switch. Down Auto-Arduino controlled, Middle seconds off and up Manual- allow seconds stepping by SW12.

SW15 LCD display On/Off Locking switch mounted on the main board. Signals the Arduino to turn on/off the LED backlight on the LCD display and LCD display itself.

SW11 Summer Advance (picture 1) non-locking switch that when pressed starts the summer advance of the hour/mins dial. The hour/mins dial is advance by 1/4min every 1 seconds. The LCD display changes to show the number of Summer Advance pulses sent. On 0,15,30 and 45 seconds the advance count is not incremented to allow for normal hour/mins drive pulses (see looped animation below). This ensures that after 240 pulse counts the hour/mins clock is advanced exactly 1 hour. The LCD display reverts back to normal stepping every 15 seconds.

SW9 Winter Retard (picture 2) non-locking switch on main board. When pressed starts the winter retard of the hour/mins dial. On the next missed hour/min drive pulse @ 0,15,30 or 45 seconds the LCD changes to show "Winter Retard". Every time a drive pulse is missed the "Winter Retard" count increments by 1.

Once the "Winter Retard" count reaches 240 the LCD display reverts back to normal and the clock is advanced every 15 seconds as normal.

SW16 Hour/Minute SetLocking switch mounted on the main board. Advances the hour/minute dial by 15 seconds every second pressed.

SW7 Reset Moon Count Non locking switch on main board. When pressed resets the Moon day count and time on the LCD display to 0.

SW8 Moon Time Advance Non locking switch on main board. When pressed advances the Moon Time on the LCD by 24 mins and 25 seconds.

SW3 Moon Phase Advance Non locking switch on main board. When pressed advances the Moon day/phase once per second.

SW10 Moon Display Advance Non locking switch on main board. When pressed advances the Moon display dial once per second.

Step 9: Pendulum Drive

Picture 1: As this clock case has a glass trunk door the Pendulum is visible and to make the clock look like it is still mechanically operated an electric pendulum drive is added.This heavy duty drive will dive a pendulum of 40" and weighing 8oz or more from a single D cell battery for over a year.

Instructions for the pendulum drive

INSTRUCTIONS FOR THE ISI HEAVY DUTY PENDULUM DRIVE: This heavy duty pendulum drive is the first of it’s kind. It was designed to operate a 40” pendulum that weighs 8 ounces for more than a year on one D-cell battery. It is perfect for making low cost grandfather size clocks, but if you use your imagination, it will do so much more.

  • When opening the device, please note that there is a separate battery well with wires and a connector (figure B) that needs to be connected to the pendulum drive (figure C). In most cases, it is best to mount the battery well above the pendulum drive.
  • This heavy duty pendulum drive has a very unique feature. The part of the device that powers the pendulum is actually “hinged” This means that if properly installed, your clock can actually be a few degrees out of level, from front to back, and still work properly. If you cut a 2” wide by 5 5/16” long hole in the board you are mounting this to (this is the measurement of the rectangle when looking at the back of the device), this will allow the hinged pendulum drive (figure A) to move back and forth if needed.
  • Both the battery well and the heavy duty pendulum drive were designed to be installed with screws. We do not supply the screws, because the type of screw you use should be determined by the material you are mounting this device to. You will need six screws. The mounting positions for these screws are indicated in the drawing to the right.
  • Mounting tips: please consider how far behind the dial of your clock you want the pendulum to swing. You will need to attach the heavy duty pendulum drive to a board of some type that is hidden behind the dial of your clock. Please make sure that this board is installed so that this device can easily be made level.
  • Swing Adjustment: This device is operated by an electromagnet that is positioned between two magnets (D) that are on the pendulum arm. If the swing of your pendulum is too wide, then you can put a piece of tape over one or both of the magnets to reduce the width of the swing of the pendulum.

Pendulum picture 2: The wooden pendulum and brass bob are constructed from old clock parts.

Weights & Chains picture 3: To complete the clock, false weights and chains are added and fixed to a wooden batten behind the hood.

Step 10: Main Board Layouts

Components are mounted on prototyping boards.

Picture 1 (front) and 2 (back) show Lavet-type stepping motor driver board. One required for each motor, 3 in total.

Picture 3 (front) and 4 (back) show the main board with Arduino 328 microprocessor configured as an Arduino Uno, the control switches and LCD secondary display.

Step 11: Schematic

Picture 1 shows the schematic. If an Arduino Uno is used, the diagram on the top left shows how the I/C pin connections translate to Arduino Uno connections.

Step 12: Code

Code v16.0 download here

This code Requires the following libraries:

dcf77.h Note this clock uses Udo Kleins Release 2 library download here DCF77 Release 2

LiquidCrystal_I2C.h

Wire.h

Code

"Atomic" Longcase Clock Arduino Code v16Arduino
Arduino Code v16
Requires the following libraries


dcf77.h Note this clock uses Udo Kleins Release 3 library download here DCF77 Release 3
LiquidCrystal_I2C.h
Wire.h
[code]
/*
v16 coded to use Udo Klein's v3 library https://github.com/udoklein/dcf77/releases/tag/v3.0.0
v15 remove moonphase advance fast add mooncountSec advance by 1465 (1/60th phase)

  Longcase Clock
  http://www.brettoliver.org.uk

  Copyright 2014 Brett Oliver

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see http://www.gnu.org/licenses/

 
 Based on the DCF77 library by Udo Klein
 http://blog.blinkenlight.net/experiments/dcf77/dcf77-library/
 http://blog.blinkenlight.net/experiments/dcf77/simple-clock/

*/


#include <dcf77.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
using namespace Internal; //v3


/* we always wait a bit between updates of the display */
unsigned long delaytime=250;
//**********************
// set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


//**********************

const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = A1;  // A5 == d19 (DFC77 signal)changed from A5
const uint8_t dcf77_inverted_samples = 0;
const uint8_t dcf77_analog_samples = 0;
const uint8_t dcf77_monitor_led       = A0; // v3
//const uint8_t dcf77_monitor_pin = A0;  // A4 == d18 changed from A4 removed v3
uint8_t ledpin(const uint8_t led){ //v3
  return led; //v3
} //v3

const int8_t timezone_offset = -1;  // GB is one hour behind CET/CEST

//*********************


int retardOn = 0;

//int infraredSwval = 0; // infrared value
//int infraredSw = A3; // set A3 to input ( pin 13)
int retardcount = 0;
int pulsecount = 0; // pulse counts upto required number to step forward 1 hour
int retardSw = 13; // winter retard switch pin 9
int retardSwval = 0; // value of retardSw
int advanceSw = 9; // summer advance switch pin 13
int advanceSwval = 0; // value of advanceSw
int pulseOn = 0; // 0 pulse off 1 pulse on (summer winter pulsing
int summertest = 0; // equals 1 for summertime and 0 for wintertime
int summerwinter = 0; // summer winter toggle
int extracount = 0; // where quartz seconds needs to miss a pulse
int hourextra = 00; // hour last miss pulse variable
int dayextra = 00; // day last miss pulse variable
int monthextra = 00; // month last miss pulse variable
int minuteextra = 00; // minute last miss pulse variable
int secondextra = 00; // second last miss pulse variable
int yearextra = 00;



int secondsnow = 0; // previous second
int yearmiss = 00;
int daymiss = 00; // day last extra pulse variable
int monthmiss = 00; // month last extra pulse variable
int secsmiss = 0; // works out is seconds need to an extra pulse or miss a pulse
int misscount = 0; // 1 ok 0 needs to miss a pulse 1 needs an extra pulse
int hourmiss = 00; // hour last extra pulse variable
int minutemiss = 00; // minute last extra pulse variable
int secondmiss = 00; // second last extra pulse variable
int signalQual = 0; // computed once per minute and indicates how well 
// the received signal matches the locally synthesized reference signal max 50
int monthval = 0;
int dayval = 0;
//int zero = 0;
int years = 0;
int months = 0;
int days = 0;

int hours = 0;

int minutes = 0;

int seconds = 0;
int secsval = 0;
int minsval = 0;
int hourval = 0;
int led15 = 2; //15 second pulse

int quartzmotor1 = HIGH;  // ledState used to set the quartz motor pin 7 initial state
int quartzmotor2 = LOW;   //  ledState used to set the quartz motor pin 8 initial state
int quartz01 =7;  //  Quartz clock motor pulse 01
int quartz02 =8;  //  Quartz clock motor pulse 02
int stepSecs =11; // when high seconds are advanced

int hrminmotor1 = HIGH;              // ledState used to set the quartz motor pin 1 initial state
int hrminmotor2 = LOW;              //  ledState used to set the quartz motor pin 2 initial state
int hrmin01 =12;  //  Quartz clock motor pulse 01
int hrmin02 =2;  //  Quartz clock motor pulse 02


//Moon Display
unsigned long mooncountSec = 1; // counts every sec until 43245 then moonphase is advanced by 1
unsigned long moontime = 1; 
int moonphase = 0; // advances 1 every 43245 secs and 86490 secs until 60 when it is reset to 2 (moon dial advanced to 1)
int moonphasedisp = 1; // advances 1 every 86490 secs until moonphase 60 when it is reset to 1 (moon dial advanced to 1)
int moondisAdv = 3; // advances moon phase display once per second
int moonphaseAdv = 4; //advances moon phase count once per second
//int moonphaseAdvfast = 11; //advances moon phase count 5 once per second
int mooncountSecAdv = 11; //advances mooncountSec  1465 once per second
int moonReset = A3; // resets moon phase and count to 1 new moon
int moonmotor1 = HIGH;              // ledState used to set the quartz motor pin 5 initial state
int moonmotor2 = LOW;              //  ledState used to set the quartz motor pin 6 initial state
int moon01 =5;  //  Moon clock motor pulse 01
int moon02 =6;  //  Moon clock motor pulse 02

float h, m;
int s = 0;
unsigned long over = 0;

// End Moon Display
int  LCDdispCtrl = A2;
int step15 = 10; //step hour mins forward by 15 seconds

//********************

namespace Timezone {
  uint8_t days_per_month(const Clock::time_t &now) { //v3 mod
    switch (now.month.val) {
    case 0x02:
      // valid till 31.12.2399
      // notice year mod 4 == year & 0x03
       return 28 + ((now.year.val != 0) && ((bcd_to_int(now.year) & 0x03) == 0)? 1: 0);
            case 0x01: case 0x03: case 0x05: case 0x07: case 0x08: case 0x10: case 0x12: return 31;
            case 0x04: case 0x06: case 0x09: case 0x11:                                  return 30;
            default: return 0;
     
    }
  }   

  void adjust(Clock::time_t &time, const int8_t offset) { //v3 mod
    // attention: maximum supported offset is +/- 23h

    int8_t hour = BCD::bcd_to_int(time.hour) + offset;

    if (hour > 23) {
      hour -= 24;
      uint8_t day = BCD::bcd_to_int(time.day) + 1;
      uint8_t weekday = BCD::bcd_to_int(time.weekday) + 1; //v3
      if (day > days_per_month(time)) {
        day = 1;
        uint8_t month = BCD::bcd_to_int(time.month);
        ++month;
        if (month > 12) {
          month = 1;
          uint8_t year = BCD::bcd_to_int(time.year);                  
          ++year;
          if (year > 99) {
            year = 0;
          }
          time.year = BCD::int_to_bcd(year);
        }                
        time.month = BCD::int_to_bcd(month);
      }
      time.day = BCD::int_to_bcd(day);
      time.weekday = BCD::int_to_bcd(weekday); //v3
    }

    if (hour < 0) {
      hour += 24;
      uint8_t day = BCD::bcd_to_int(time.day) - 1;
      uint8_t weekday = BCD::bcd_to_int(time.weekday) - 1; //v3
      if (day < 1) {
        uint8_t month = BCD::bcd_to_int(time.month);
        --month;
        if (month < 1) {
          month = 12;
          int8_t year = BCD::bcd_to_int(time.year);                  
          --year;
          if (year < 0) {
            year = 99;
          }
          time.year = BCD::int_to_bcd(year);
        }                
        time.month = BCD::int_to_bcd(month);
        day = days_per_month(time);
      }
      time.day = BCD::int_to_bcd(day);
       time.weekday = BCD::int_to_bcd(weekday); //v3
    }

    time.hour = BCD::int_to_bcd(hour);
  }
}

uint8_t sample_input_pin() {
  const uint8_t sampled_data =
    dcf77_inverted_samples ^ (dcf77_analog_samples? (analogRead(dcf77_analog_sample_pin) > 200)
    : digitalRead(dcf77_sample_pin));

  //digitalWrite(dcf77_monitor_pin, sampled_data); // removed v3
  digitalWrite(ledpin(dcf77_monitor_led), sampled_data); // v3
  return sampled_data;
}

void setup() {
  digitalWrite(led15, HIGH); // turn 15 sec clocks off at start
 

  lcd.begin(20,4);   // initialize the lcd for 20 chars 4 lines, turn on backlight 
 // using namespace Internal; // v3 Removed
  //*************
 // lcd.backlight(); // backlight on not needed as controlled by 7 MAX2719
  lcd.setCursor(0,0); //Start at character 0 on line 0
  lcd.print("DCF77 Longcase Clock");  





  //***************
  Serial.begin(9600);
  /*  Serial.println();
   Serial.println(F(" DCF77  Longcase  Clock "));
   Serial.println(F("(c) Brett Oliver 2014"));
   Serial.println(F("http://www.brettoliver.org.uk"));
   Serial.println(F("Based on the DCF77 library by Udo Klein"));
   Serial.println(F("www.blinkenlight.net"));
   Serial.println();
   Serial.print(F("Sample Pin:     ")); 
   Serial.println(dcf77_sample_pin);
   Serial.print(F("Inverted Mode:  ")); 
   Serial.println(dcf77_inverted_samples);
   Serial.print(F("Analog Mode:    ")); 
   Serial.println(dcf77_analog_samples);
   Serial.print(F("Monitor Pin:    ")); 
   Serial.println(dcf77_monitor_pin);
   Serial.print(F("Timezone Offset:")); 
   Serial.println(timezone_offset);
   Serial.println();
   Serial.println();
   Serial.println(F("Initializing..."));
   */
 // pinMode(dcf77_monitor_pin, OUTPUT); // removed v3

  pinMode(dcf77_sample_pin, INPUT);
  digitalWrite(dcf77_sample_pin, HIGH);


  //*******************

  //pinMode(led15, OUTPUT);
  pinMode(quartz01, OUTPUT);
  pinMode(quartz02, OUTPUT);
  pinMode(moon01, OUTPUT);
  pinMode(moon02, OUTPUT);
  pinMode(hrmin01, OUTPUT);
  pinMode(hrmin02, OUTPUT);
  pinMode(ledpin(dcf77_monitor_led), OUTPUT); // v3
  
 
  //pinMode(17, INPUT); // infrared switch
  pinMode(advanceSw,INPUT);
  pinMode(retardSw,INPUT);
  pinMode(moonphaseAdv,INPUT);
  //pinMode(moonphaseAdvfast,INPUT);
  pinMode(mooncountSecAdv,INPUT);
  pinMode(moondisAdv,INPUT);
  pinMode(moonReset,INPUT);
  pinMode(LCDdispCtrl,INPUT);
  pinMode(step15,INPUT); // 15 second (hour min) man advance
 // pinMode(stepSecs,INPUT); // secs man advance
  

  //*******************

  DCF77_Clock::setup();
  DCF77_Clock::set_input_provider(sample_input_pin);


  // Wait till clock is synced, depending on the signal quality this may take
  // rather long. About 5 minutes with a good signal, 30 minutes or longer
  // with a bad signal
  
  
  
  
  for (uint8_t state = Clock::useless; //v3 Mod
         state == Clock::useless || state == Clock::dirty; //v3 Mod
         state = DCF77_Clock::get_clock_state()) {

    // wait for next sec
    Clock::time_t now; //v3 mod
    DCF77_Clock::get_current_time(now);

    // render one dot per second while initializing
    static uint8_t count = 0;
    Serial.print('.');
    ++count;
    if (count == 60) {
      count = 0;
      Serial.println();
    }
  }
}

void paddedPrint(BCD::bcd_t n) {
  Serial.print(n.digit.hi);
  Serial.print(n.digit.lo);
}
void LCDpaddedPrint(BCD::bcd_t n) { 
  lcd.print(n.digit.hi); 
  lcd.print(n.digit.lo); 
}

void loop() {

  Clock::time_t now; //v3 mod

  DCF77_Clock::get_current_time(now);
  Timezone::adjust(now, timezone_offset);
  
 
  if (now.month.val > 0) {


    //***********
    // get month & day values
    dayval = now.day.val, DEC;
    monthval = now.month.val, DEC;
    // Serial.print(" day ");
    // Serial.print(now.day.val, DEC);
    //  Serial.print(" month ");
    //  Serial.print(now.month.val, DEC);
    //   Serial.print(' ');
    // get month & day values
    // Quartz clock driver
    // toggle Quartz drive 7 & 8 evey second
    secsmiss = seconds - secondsnow;

    if (secsmiss ==-59 || secsmiss ==-60 && seconds ==0) // takes account of seconds rollover -59 or leap second -60
    {
      secsmiss = 1;
    } 





 // If not andvancing seconds then use this (normal)
   if (secsmiss >=1 && seconds !=60) // if zero or less seconds pulse need to be missed. Leap second missed if seconds = 60
    {
      secondsmotor (); // function steps quartz motor
    }




    if (secsmiss < 1 || seconds == 60) //records time of extra sec second (quart motor needs to loose a second)

    {
      extracount = extracount + 1; //increment extra count total (1sec needs to miss pulse)
      hourextra = hours;
      minuteextra = minutes;
      secondextra = seconds;
      yearextra = years;
      monthextra = months;
      dayextra = days;

    }


    if (secsmiss > 1) //records time of miss second (quart motor needs to add a second)
    {
      misscount = misscount + 1; //increment Miss count total (1sec has missed extra pulse)
      hourmiss = hours;
      minutemiss = minutes;
      secondmiss = seconds;
      yearmiss = years;
      monthmiss = months;
      daymiss = days;

    } 



    secondsnow = seconds; 



    if (hours== 6 && minutes == 10 && seconds == 01) // resets miss second counter to 0 at 6:10:01
    {
      misscount = 0;
      extracount = 0;
    }


    // }
    // Enable below to analize missed pulses on serial monitor
    //Serial.print(" ");
    // Serial.print("secsmiss ");
    //  Serial.println(secsmiss);
/*

    Serial.print("Slow Seconds ");
    Serial.print(misscount);
    Serial.print(" ");
    Serial.print(hourmiss);
    Serial.print(":");
    Serial.print(minutemiss);
    Serial.print(":");
    Serial.print(secondmiss);
    Serial.print(" ");
    Serial.print(daymiss);
    Serial.print("/");
    Serial.print(monthmiss);
    Serial.print("/");
    Serial.println(yearmiss);


    Serial.print("Fast Seconds ");
    Serial.print(extracount);
    Serial.print(" ");
    Serial.print(hourextra);
    Serial.print(":");
    Serial.print(minuteextra);
    Serial.print(":");
    Serial.print(secondextra);
    Serial.print(" ");
    Serial.print(dayextra);
    Serial.print("/");
    Serial.print(monthextra);
    Serial.print("/");
    Serial.println(yearextra);


*/




    // end missing second pulse

    // ################################################################################ 
    
    
  

    /*
 Serial.println(F("confirmed_precision [Hz], target_precision v,total_adjust [Hz], frequency [Hz]"));
     Serial.print(DCF77_Frequency_Control::get_confirmed_precision());
     Serial.print(F(", "));
     Serial.print(DCF77_Frequency_Control::get_target_precision());
     Serial.print(F(", "));
     Serial.print(DCF77_1_Khz_Generator::read_adjustment());
     Serial.print(F(", "));
     Serial.print(16000000L - DCF77_1_Khz_Generator::read_adjustment());
     Serial.print(F(" Hz, "));
     
     */

    //***************
    // signal quality

    //  signalQual = DCF77_Clock::get_prediction_match();
    //  if(signalQual == 255 || signalQual == 0 )
    //  {
    // signalQual = 00;
    //  }
    // else
    //{
    //  signalQual = (signalQual * 2) -1;
    //}
    // Serial.print (" Signal Match ");
    // Serial.print (signalQual);
    // Serial.print ("% ");

    // display 7 segment intensity value on LCD
   // lcd.setCursor(0,2);
    //lcd.print("7 Seg Int ");
   // lcd.print("Brightness ");
  //  segIntensity(intensity); // adds leading zero to 7 segment intensity
    

      // end display 7 segment intensity value on LCD   


    // #################################
    //LDR start

  //  ldrValue = analogRead(ldr);
    
   //  Serial.print (" LDR Value ");
   //  Serial.print (ldrValue, DEC);
   //  Serial.print (" ");
  //  intensityValue();
    // Serial.print ("Intensity ");
  //   Serial.print (intensity);
  //   Serial.print (" ");
     
    //LDR finish
    //#################################
    //Serial.print (now.second.val);
    // Serial.print (" ");
    lcd.setCursor(17,2);
    //lcd.print(" Wait  ");
    switch (DCF77_Clock::get_clock_state()) {
      // case DCF77::useless: Serial.print(F("useless ")); break;
      case Clock::useless: //v3 mod
      lcd.print(F(" Fail  ")); 
      break;
      // case DCF77::dirty:   Serial.print(F("dirty: ")); break;
      //  case DCF77::synced:  Serial.print(F("synced: ")); break;
      // case DCF77::locked:  Serial.print(F("locked: ")); break;
      case Clock::dirty: //v3 mod  
      lcd.print(F("Dty")); 
      break;
      case Clock::synced:  //v3 Mod
      lcd.print(F("Syn")); 
      break;
      case Clock::locked: //v3 mod 
      lcd.print(F("Lck")); 
      break;
    }

    //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    // Get hours minutes and seconds variables
    hours = BCD::bcd_to_int(now.hour);


    minutes = BCD::bcd_to_int(now.minute);


    seconds = BCD::bcd_to_int(now.second);

    years = BCD::bcd_to_int(now.year);

    months = BCD::bcd_to_int(now.month);

    days = BCD::bcd_to_int(now.day);


 

    /*  Serial.print (" H,M,S ");
     Serial.print (hours2);
     
     Serial.print (hours1);
     Serial.print (":");
     
     
     Serial.print (minutes2);
     
     Serial.print (minutes1);
     Serial.print (":");
     
     
     Serial.print (seconds2);
     
     Serial.print (seconds1);
     Serial.print (":");
     
     */
    lcd.setCursor(6,3);



    // 7 segment


    //****************

    lcd.setCursor(0,0);

    LCDpaddedPrint(now.hour);
    lcd.print(":");
    LCDpaddedPrint(now.minute);
    lcd.print(":");
    LCDpaddedPrint(now.second);
    lcd.print("  ");

    LCDpaddedPrint(now.day);
    lcd.print("/");
    LCDpaddedPrint(now.month);
    lcd.print("/");
    lcd.print("20");
    LCDpaddedPrint(now.year);





    //****************
    /*
    
     paddedPrint(now.hour);
     Serial.print(':');
     paddedPrint(now.minute);
     Serial.print(':');
     paddedPrint(now.second);
     Serial.print(' ');
     
     paddedPrint(now.day);
     Serial.print('/');
     paddedPrint(now.month);
     Serial.print('/');
     Serial.print(F("20"));
     paddedPrint(now.year);
     
     
     */



    // const int8_t offset_to_utc = timezone_offset + now.uses_summertime? 2: 1;
    const int8_t offset_to_utc = timezone_offset + (now.uses_summertime? 2: 1);

    // Serial.print(F(" GMT"));

    summertest = (abs(offset_to_utc)); // equals 1 if summertime and 2 if wintertime 

    if (summertest ==2) // if wintertime make summertest =0
    {
      summertest = 0;
    }

    //**************
    lcd.setCursor(15,3);
    lcd.print("GMT+");
    // UTCcheck= offset_to_utc;
    // lcd.print(UTCcheck);
    lcd.print(summertest);
   
  }

 
  // Quality display on LCD
  if (seconds >= 0 && seconds <= 10)
  { 
    signalmatch(); // Quality factor
  }

  else if (seconds == 11)
  {
    blankrow3(); //Blanks row 3
  }

else if (seconds == 12)
  {
    lcd.setCursor(0,3);
  lcd.print("Quartz         ");
  }
  else if (seconds >= 13 && seconds <= 23)
  {
    precision(); //quartz confirmed and target precision
  }

  else if (seconds == 24)
  {
    blankrow3(); //Blanks row 3
  }

 else if (seconds >=25 && seconds <= 35 )
  {
      lcd.setCursor(0,3);
      
  signalmatch(); // Quality factor
  }

  else if (seconds == 36)
  {
    blankrow3(); //Blanks row 3
  }
 
  else if (seconds == 37)
  {
    lcd.setCursor(0,3);
  lcd.print("Quartz         ");
  }
  else if (seconds >= 38 && seconds <= 48)
  {
    precision(); //quartz confirmed and target precision
  }

  else if (seconds == 49)
  {
    blankrow3(); //Blanks row 3
  }

 else if (seconds >=50 && seconds <= 59 )
  {
      lcd.setCursor(0,3);
      
  signalmatch(); // Quality factor
  }

 
  // End of Quality display on LCD
  //$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$



  // winter to summer change // clocks go forward#################################
  advanceSwval = digitalRead(advanceSw);   // read advanceSw switch
  // 240 x 15 sec pulses need to be added to normal 15 second pulses to advance by 1 hour
  if (pulsecount < 240 && (seconds == 0 || seconds == 15 || seconds == 30 || seconds == 45) && (advanceSwval == 1 || pulseOn == 1))
    // summertest is 1 in summertime and  0 in wintertime  advance on 00 ,15,30 &45 secs does not advance pulse count

  {
    // sumwinSwval = 1;
    pulseOn = 1;
    //Serial.println(" 15 second test ");

     hrminmotor(); // function steps hr min motor

  }

  else if (pulsecount < 240 && (advanceSwval == 1 || pulseOn == 1)) // summertest is 1 in summertime and  0 in wintertime 

  {
    // sumwinSwval = 1;
    pulseOn = 1;
    pulsecount = pulsecount + 1;

     hrminmotor(); // function steps hr min motor
  }

  else  
  {
    digitalWrite(hrmin01, LOW); // set the hr min motor drive 1 pin LOW
  digitalWrite(hrmin02, LOW); // set the hr min motor drive 2 pin LOW
    pulseOn = 0; // 
    // sumwinSwval = 0;
    pulsecount = 0;
  } 

  // end winter to summer chaange advance ##############################



  //  summer to winter change clocks go back#################################
  retardSwval = digitalRead(retardSw);   // read retardSw switch

  if (retardcount < 240 && retardSwval == 1) // 240 x 15 sec pulses need to be ignored to retard by 1 hour
  {
    retardOn = 1;
  }

  else if (retardcount < 240 && (seconds == 0 || seconds == 15 || seconds == 30 || seconds == 45) && (retardSwval == 1 || retardOn == 1))
  {
    retardOn = 1;
    retardcount = retardcount + 1;
  }

  else if (retardcount < 240 && (retardSwval == 1 || retardOn == 1))
  {
    retardOn = 1;

  }

  else
  {
    retardOn = 0; 
    // sumwinSwval = 0;
    retardcount = 0;
  }  



  // end summer to winter change clocks go back#################################  





  // Display of Title and 30 second pulse correction on row 01 ##############################

  if (retardcount > 0 || retardcount == 119 ) //Prints correction pulse number
  {
    lcd.setCursor(0,1); 
    lcd.print("Winter Retard  ");
    lcd.setCursor(15,1);



    retard(retardcount); // add leading 0 <99
    retardtens(retardcount); // add another leading 0 less than 10
    lcd.print(retardcount);
  }
  else if (retardcount == 240 )  //Prints blank when correction pulse is on last number
  {
    lcd.setCursor(0,1); 
    lcd.print("                    ");
    // lcd.print(retardcount);
  } 


  else if (seconds >= 0 && seconds <= 5 && pulsecount == 0) // Ignored while clock is correcting forward or retarding
  {

    lcd.setCursor(0,1); //Start at character 0 on line 0
    lcd.print("DCF77 Longcase Clock");      

  }
  else if(seconds > 05 && seconds <= 10 && pulsecount == 0) // Ignored while clock is correcting forward or retarding
  {
    lcd.setCursor(0,1); //Start at character 0 on line 0
    //lcd.print(" DCF77 Master Clock ");  
    lcd.print(" Brett Oliver v16.0 ");
  }

  else if(seconds == 11 && pulsecount == 0) // Ignored while clock is correcting forward or retarding
  {
    lcd.setCursor(0,1); //Start at character 0 on line 0

    lcd.print("                    ");
  }

  else if(seconds > 11 && seconds <= 13 && pulsecount == 0) // Ignored while clock is correcting forward or retarding
  {
    lcd.setCursor(0,1); //Start at character 0 on line 0

    // lcd.print("       Pulses       ");
    lcd.print("   1 Second Clocks  ");

  }



  else if(seconds > 13 && seconds <=59 && pulsecount == 0) // Ignored while clock is correcting forward or retarding
  {
    lcd.setCursor(0,1); //Start at character 0 on line 0
    lcd.print("  Slow "); // miss pulse detected so extra 1 second motor pulse added
    lcd.print(misscount);

    lcd.print(" ");
    lcd.print("   Fast ");        
    lcd.print(extracount);
  }

  else if (pulsecount > 0 || pulsecount == 239 ) //Prints correction pulse number
  {
    lcd.setCursor(0,1); 
    lcd.print("Summer Advance ");
    lcd.setCursor(15,1);
    //lcd.print(pulsecount);

    advance(pulsecount); // add leading 0 <99
    advancetens(pulsecount); // add another leading 0 less than 10
    lcd.print(pulsecount);
  }
  else if (pulsecount == 240 )  //Prints blank when correction pulse is on last number
  {
    lcd.setCursor(0,1); 
    lcd.print("                    ");
    //lcd.print(pulsecount);
  } 


  // sum win test print
  //sumwinSwval = digitalRead(sumwinSw);   // read sumwinSw switch
  /* Serial.print(" Sum Win Pulse No ");
   Serial.print(pulsecount);
   Serial.print(" ");
   
   Serial.print("Sum Win SW ");
   Serial.print(sumwinSwval);
   Serial.print(" ");
   Serial.print("Pulse On ");
   Serial.print(pulseOn);
   Serial.print(" ");
   Serial.print("Retard On ");
   Serial.print(retardOn);
   Serial.print(" ");
   Serial.print("Retard Count ");
   Serial.print(retardcount);
   Serial.print(" ");
   Serial.print("Summer Test ");
   Serial.print(summertest);
   Serial.print(" ");
   */

  // end sum win test print 
  //  

  // winter to summer change // clocks go forward#################################

  // End Display of Title and 30 second pulse correction on row 01 ##############################
  //-----------------------------------------------------------------------
  // Moon Phase Dial Display
  mooncountSec = mooncountSec + 1;
 // moontime = 86490 - mooncountSec; //counts down to next phase change
  moontime = mooncountSec; // counts up to next phase change
  // calc/display hour min sec to next moon phase 
  h = int (moontime/3600);
  over = moontime % 3600;
  m = int (over/60);
  over = over % 60;
  s = int (over);
  /*
  Serial.print ("Sec Pulses: ");
  Serial.println (mooncountSec);
  Serial.print ("Sec Pulses: ");
  Serial.println (moontime);
  
  Serial.print ("Next Phase: ");
  Serial.print (h, 0);
  Serial.print ("h ");
  Serial.print (m, 0);
  Serial.print ("m ");
  Serial.print (s, 0);
  Serial.print ("s ");
  
  Serial.println ();
*/
  
  
  //
  
  
  
   // set in this order so Moon motor steps each second until 60 (0)
 /*
   if ( moonphase == 60 ) // was 61
  {
  // delay(100); // add delay so seconds mtor can operate again in the same second
    moonmotor (); // function steps moon motor
  // moonphase = moonphase + 1;
   lcd.setCursor(5,2); //Start at character  on line 2
    lcd.print("  "); // Blank old moon phase  
  moonphase = 0; //resets moonphase count so it displays day 1 (2/60th)on the dial
  moonphasedisp = 0; //resets moonphase LCD count so it displays day 1 (2/60th)on the dial
 // mooncountSec = 1;
  
  }
  */
  /*
  if ( moonphase == 60 )
  {
  // delay(100); // add delay so seconds mtor can operate again in the same second
    moonmotor (); // function steps moon motor
   moonphase = moonphase + 1; 
  }
  
  */
  
  
  // steps moon phase display to 0 and resets mooncountSec when moonphase is 59 and a half
  
  
   // if ( mooncountSec == 43245 && moonphase == 59 )
  if ( moonphase == 59 )
  {
  moonmotor (); // function steps moon motor
  lcd.setCursor(5,2); //Start at character  on line 2
    lcd.print("  "); // Blank old moon phase  
    
  //  moonphase = moonphase + 1;
  //  moonphasedisp = moonphasedisp + 1;
    
   moonphase = 0; //resets moonphase count so it displays day 1 (2/60th)on the dial
  moonphasedisp = 0; //resets moonphase LCD count so it displays day 1 (2/60th)on the dial 
 mooncountSec = 1;
  }
 
  
   if ( mooncountSec == 43245 )
 
  {
  moonmotor (); // function steps moon motor
    moonphase = moonphase + 1;
  }
  
  
  // steps moon phase display and resets mooncountSec after 86490 1 sec pulses (1 day 1min 25sec)
...

This file has been truncated, please download it to see its full contents.

Custom parts and enclosures

"Atomic" Longcase Clock Schematic
Shematic

Schematics

Schematic
Shematic
Vero 01
Vero
Vero01 back
Veroback

Comments

Similar projects you might like

Arduino Clock

Project tutorial by Arduino_Scuola

  • 25,641 views
  • 0 comments
  • 11 respects

MultiFunctional Clock

Project in progress by 3 developers

  • 15,853 views
  • 2 comments
  • 28 respects

Simple wall clock using Adafruit 1/4 60 Ring Neopixel

Project tutorial by antiElectron

  • 6,692 views
  • 0 comments
  • 34 respects

Analog Clock using 1Sheeld Graphical LCD

Project tutorial by Ahmed El-Hinidy

  • 2,069 views
  • 0 comments
  • 7 respects

Digital Clock with Arduino, RTC and Shift Register 74HC595

Project tutorial by LAGSILVA

  • 20,727 views
  • 16 comments
  • 46 respects

Complete Digital Clock Including Alarm and Motion Sensor

Project tutorial by LAGSILVA

  • 12,680 views
  • 11 comments
  • 50 respects
Add projectSign up / Login