Home Master Clock LCD Master Clock Kenley Weather
Section Top Video Construction Displays Veroboard Controls Seconds Movement Hrs Mins Movement Summer Advance Advance Video Winter Retard Sound Board PIR Connections Schematic Code
Arduino DCF77 Synchronised 12" English Dial Clock
Features
Electro-mechanical Hours, Minutes & Seconds displays
7 Segment Digital display Hours, Minutes & Seconds
Auto correction for Summer/Winter and leap seconds
Built in Decoder for the DCF77 "Atomic Clock" using the fantastic new DCF77 library written by Udo Klein.
This library keeps the clock in sync and keeps perfect time even with a massive amount of noise on the received DCF77 signal
Udo Klein's DCF77 library continually "Auto Tunes" the quartz crystal so in the rare event the signal can't be decoded the clock remains accurate within 1 sec over many days
Electro-mechanical Hours, Minutes & Seconds displays
7 Segment Digital display Hours, Minutes & Seconds
Auto correction for Summer/Winter and leap seconds
Large Secondary 4x20 I2C LCD display is used to display time & date, fast or slow seconds, summer winter correction, display brightness, sync information, signal quality, auto tune'd frequency, auto tuned quartz accuracy and summer winter time mode
Video Showing clock running
Construction
The original donor 12" dial clock with dark stained wooden dial surround.
The original dial had no seconds ring and the mechanical movement was missing.
The original donor clock with dark wooden dial surround stripped back and bleached to lighten the colour.
The completed dial and surround are mounted on a new back box constructed from plywood.
The plywood box is stained stained to match the dial surround. See side view above.
The original dial complete with stains, chips and dents has had a seconds dial added and name as required (dry transfer or Lazertran wet transfer) and a slot cut out for the digital display.
The hour hand "spade" is skeletonised to keep the digital display visible between 05:00 and 07:00 hours.
In the illustration above the dial has been cut away to show the hacked quartz seconds movement top, the 30 second slave movement middle
and the digital display bottom. The 30 second slave movement is fixed to the metal clock dial by two small screws. The quartz movement is then attached to the 30 second movement by a bracket.
The quartz movement has had the quartz control board cut away and wires connected directly to the drive motor coil.
The digital display is fixed to the wooden dial backing plate by two metal brackets.
The dial surround and bezel removed to show the clock movements/displays relative to the controls and circuit boards.
The dial and dial surround are hinged to side of the back box and can be opened and folded back to enable access to the controls and circuit boards
The controls and circuit boards are mounted in the back box.
Top right - PSU Module adjusted to give 5 volts at the board after the protection diode.
Middle - main Vero board with the Atemega 328 microcontroller and sound board module.
Bottom - LCD display module with I2C control module mounted on the back.
The quartz clock motor switch control panel is on the top left with sound and LCD backlight control switches mounted on the right.
The sound board that creates the ticking sound is wired to the small speaker that fires through the bottom of the case.
The tick-tock sound is sampled from a 1 second long case clock movement edited in Audacity down to a 1.5 second sample.
The clock plays this sample every other second so the ticking is always in sync with all the clock displays.
A LDR is mounted through a hole cut in the right side of the back box to control the 7 segment display intensity via the microcontroller.
The LCD and 7 segment digital display are turned on by a PIR detector module located on the same room as the clock when ever someone is in the room.
Displays
LCD
The LCD display is a 20x4 large character display and shows information on the clock and DCF77 decoder.
The display is housed under the hinged dial bezel.
The display has an I2C module soldered on the back to allow easy 4 wire connection and communication with the Arduino.
7 Segment
The 7 segment display module is purchased as a kit of parts. The only item missing from the kit is a 10µF electrolytic capacitor.
I just solder one on the back of the PCB across +5v and 0v.
The black plastic tape is used to mask out the 3rd and six characters so a colon separator is displayed.
The 7 segment display module is fixed to 2 aluminium brackets. The brackets then hold the display to the wooden dial bezel.
Vero Board
Main Vero board with flipped down rear view below.
The Atemega 328 microcontroller provides DCF77 decoding and display of correct time and also provides timing pulses for the sound board and 30 second pulse to the 555 timer IC.
Exact pulse length for operation of the 30 second slave for mins and hours is obtained using an adjustable resistor.
There are 2 outputs for driving slave quartz analogue movement motors, 1 is used for the analogue seconds on this clock and the other is spare both outputs are adjusted by variable resistors on each output.
Reverse polarity protection is provided by a diode so the PSU is adjusted to give 5v on the protected side of the diode.
The board has 6 on board momentary tactile switches to control the following:
Microcontroller reset.
1 Hour Retard.
The digital displays LCD and LED automatically adjust when the clocks go back to winter time. When the "Retard" button is pressed the clock waits for the next 30 second pulse and then stops the hour and minute slave from moving until 120 pulses have passed.
This will stop the analogue mins and hour movement for 1 hour exactly and then start it again. The LCD display will show "Winter Retard" and also show the number of missed 30 second pulses.
1 Hour Advance.
The digital displays LCD and LED automatically adjust when the clocks go forward for summer time. When the "Advance" button is pressed the clock starts to advance the analogue min and hour movement once per second.
The movement has to be advanced an extra 120 pulses but as this will take 2 mins to complete extra pulses will be needed to keep the movement in time as it advances. It does this by not counting advance pulses on 0 and 30 seconds.
The LCD display will show "Summer Advance" and also shows the number of advance 30 second pulses. This count will stop on 0 and 30 seconds.
Tick Tock On.
On power up or reset the clock is set to silent mode and no pulses are sent to the sound board.
On pressing this button the pulses are sent to the sound board to play the tick tock sound.
The sound board on power up does not remember the last volume setting so this ensures loud sounds are not sent out until the button is pressed for example on power interruption in the middle of the night.
Volume + & -
When the tick-tock sound is playing these buttons control the volume.
Switch Controls
The Seconds Motor is controlled by 3 switches.
Auto/Man in the " Auto " position the seconds motor is controlled by the clock. In the "Man " position the Step button is used to control the motor.
Step is a 3 position switch normally middle "Off ". Lifting the switch up and down non latching manually steps the seconds motor.
On/Off is the master control and will always turn the motor on or off.
Tick turns the Tick Tock sound on & off
LCD turns on the LCD back light.
Seconds Motor
The seconds hand is driven by a Lavet type stepping motor.
The motor is sourced from a quartz clock movement with the quartz control board cut out. The motor requires very low current to drive it and can be driven direct 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. 2 output pins from the Arduino are used to pulse the drive motor with 1 pin always the opposite to the other.
Modifying the Seconds Motor
The quartz crystal with integrated circuit board are not
required and are cut out/disconnected from the drive coil pins.
The clock movement will need to be taken apart to access the electronics inside.
There are many types of quartz movements but they usually have the same basic
components. The coil terminals need to be isolated from the rest of the
electronics and wires soldered to them so they can be driven by an external
source.
Start by removing the adjustment knob (not always necessary).
Carefully prise the case apart with your fingers or a plastic knife. On some
cases the are 2 plastic tabs that have to be prised away to release the case.
Once the lid is removed the movement is exposed. On some movements the printed
circuit board can now be accessed but on this movement the lower part of the
case has to be pulled away as well and turned over. It may be worth taken a few
pics at this stage with your mobile phone in case the small gears fall out. Note
this pic already shows the new wires connected to the coil drive pins.
This shows two connections to the integrated circuit that have been cut away and
the new wires soldered in place.
Once this is completed the movement is put back together by reversing the above.
Push on a second hand and test the clock by briefly connecting the wires across
a 1.5v battery - the second hand should step. Briefly connect the wires to the
battery again but this time reversed and the clock should step again.
There are many types of clock movement an alternative clock movement is shown
below with the top cover removed. Slide out the PCB and cut away any tacks to
the drive coil pins with a craft knife then solder wires to these pins.
To make sure there is space for the case to shut just cut a small slot in one
half ot the case to allow the new wires to pass through.
Hour and Minute Movement Control
The hour and minute movement is a 30 second slave movement. These movements were fitted to clocks in offices and factories and were stepped once every 30 seconds by a master Clock.
The parts and operation are as follows
A is the main ratchet-wheel, having 120 teeth
B the operating electromagnet
C the armature
D the armature lever
E the driving pawl, which moves the ratchet-wheel one tooth on the release of
the armature
F the driving spring, which normally holds the driving pawl in engagement with
the ratchet wheel, and the armature away from the electromagnet
G the back-stop lever, which prevents movement of the ratchet-wheel when the
armature is attracted, as might be possible from vibration or on clocks with
exposed dials - by pressure of wind on the hands
H the momentum stop, which prevents the ratchet wheel being moved more than one
tooth per impulse and, with the pawl E, locks the ratchet wheel between pulses
J the stroke-limit stop, which limits the travel of the armature, and
When electromagnet B is energized by a pulse from the Master Clock, the armature
C is attracted, pawl E is propelled to the right against the pressure of spring
F, and drops into the next tooth on ratchet-wheel A.
When the pulse ceases, spring F drives the paw! E forward, and the ratchet-wheel
rotates one tooth, equivalent to a half-minute on the dial. The ‘minute’ hand is
attached to the. ratchet-wheel which also drives the ‘hour’ hand through a train
of gears having a reduction ratio of 12 to 1.
Operating current
The operating current for the movement is 250 mA. The resistance of the
electromagnets is between 7.5 and 10 ohms and they may be connected in circuit
without reference to polarity.
Summer Advance
The digital LED and LCD clocks automatically adjust for summer and winter time.
To adjust the analogue clock for summer time (advance the clock 1hour) press the "Advance" button on the main board on the lower right of the sound board.
The clock will start to advance and the LCD display will show "Summer Advance" and also show the number of advance pulses sent.
Above the advance pulses and count are paused on the minute and half minute to keep the clock in sync. eg @ 09:06:59 the Advance Count =15, @ 09:07:00 the Advance Count =15 @ 09:07:01 the Advance Count =16
1 advance pulse count is missed out as a normal clock pulse would have happened at that time anyway.
Below once the analogue clock has advanced 1 hour the LED display will return to normal and the advance pulses will stop.
Video showing summer time advance
Winter Retard
Winter retard is started by pressing the "Retard" key on the main board on the lower left of the sound board.
When the "Retard" key is pressed nothing happens until the clock reaches 0 or 30 seconds.
Below when 0 or 30 seconds is reached "Winter Retard" is displayed on the LCD display and the retard count starts to count up.
The analogue clock is not advanced.
Below each time 0 or 30seconds is reached 1 is added to the retard count and the analogue clock does not advance.
Below once the retard count has reached 120 then the LCD display reverts to normal and the clock advances as normal.
Below Winter Retard Video
Sound
Below Audio FX Sound Board + 2x2W Amp - WAV/OGG Trigger - 16MB
16MB of storage on the board itself, so you can store up to
15 minutes of quality compressed audio. Double that if you go with mono instead
of stereo
Built in Mass Storage USB - Plug any micro USB cable into the Sound Board and
your computer, you can drag and drop your files right on as if it were a USB key
Compressed or Uncompressed audio - Go with compressed Ogg Vorbis files for
longer audio files, or uncompressed WAV files
High Quality Sound -44.1KHz 16 bit stereo. The decoding hardware can handle any
bit/sample rate and mono or stereo
11 Triggers - Connect up to 11 buttons or switches, each one can trigger audio
files to play
Stereo line out - There's a breakout for both left and right channels, at line
level, so you can always hook up to any kind of stereo or powered speaker
Five different trigger effects - by changing the name of the files, you can
create five different types of triggers which will cover a large range of
projects without any programming
This clock only uses 1 input to trigger a long case clock "tick tock" seconds sound.
The file lasts around 1.5 seconds and is triggered every other second to keep it in sync with the clock.
Download Tick Tock sound
PIR
Pyroelectric IR Infrared PIR Motion Sensor Detector Module
The clock has infrared motion detection and this is detected off a remote sensor that is used for a number of clocks in the room.
The PIR is mounted high up on a wall in a face plate and the output goes to all the clocks in the room with a PIR input.
Connections
IC Pin | IDE Pin |
Digital Dial Clock |
1 | Reset |
Reset |
2 | 0 |
Serial Read/Write |
3 | 1 |
Serial Read/Write |
4 | 2 |
30 sec |
5 | 3 |
tick |
6 | 4 |
tick tock On |
7 | NA |
4.4v |
8 | NA |
Gnd |
9 | NA |
Xtal1 |
10 | NA |
Xtal2 |
11 | 5 |
quartz03 |
12 | 6 |
quartz04 |
13 | 7 |
quartz01 |
14 | 8 |
quartz02 |
15 | 9 |
Advance Sw non lock |
16 | 10 |
Max 7219 Load |
17 | 11 |
max 7219 Clk |
18 | 12 |
Max 7219 Din |
19 | 13 |
Retard Sw non lock |
20 | NA |
4.4v |
21 | NA |
AREF |
22 | NA |
Gnd |
23 | A0 |
dcf77_monitor_pin |
24 | A1 |
dcf77_sample_pin |
25 | A2 |
Disp brightness LDR |
26 | A3 |
PIR sense |
27 | A4 |
SDA |
28 | A5 |
SCL |
Shematic
Clearing your EEPROM
If your DCF77 library held on your Arduino has become corrupted the Arduino will not "Synchronize" to a good signal and will display a low "Signal Quality %" even though the signal is perfect.
Load this small bit of code to your Arduino to erase the EEPROM then reload the clock code below. This will allow the library to restart from fresh.
Code v13.0
Requires the following libraries
LedControl.h
dcf77.h Note
this clock uses Udo Kleins Release 3 library download here DCF77
Release 3
LiquidCrystal_I2C.h
Wire.h
// Digital Dial Clock // http://www.brettoliver.org.uk // // Copyright 2014 Brett Oliver // v13 uses Udo Klein's v3 DCF77 Library // 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 <LedControl.h> #include <dcf77.h> #include <LiquidCrystal_I2C.h> #include <Wire.h> using namespace Internal; //v3 /* ***** These pin numbers will probably not work with your hardware ***** pin 12 is connected to the DataIn pin 11 is connected to the CLK pin 10 is connected to LOAD We have only a single MAX72XX. */ LedControl lc=LedControl(12,11,10,1); /* 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 UTCcheck = 0; //int 30secstep = 0: //int 30secstep1 = 0; int retardOn = 0; //int retardtens = 0; //int retard = 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 sumwinSwval = 0; // value of sumwinSw //int sumwinSw = 13; // summer winter switch pin 13 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 startextra = 0; // delays checking of extra pule intil start extra = 3 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 intensity = 0; // 7 segment intensity int ldr = A2; // LDR connected to analogue A2 pin unsigned int ldrValue; // LDR value //int hourchime = 9; //int qtrchime = 6; 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 hours2 = 0; //2nd 7 segment hour digit int hours1 = 0; //1st 7 segment hour digit int hours = 0; int minutes2 = 0; //2nd 7 segment minute digit int minutes1 = 0; //1st 7 segment minute digit int minutes = 0; int seconds2 = 0; //2nd 7 segment seconds digit int seconds1 = 0; //1st 7 segment seconds digit int seconds = 0; int secsval = 0; int minsval = 0; int hourval = 0; int led30 = 2; int tick = 3; int tickctrl = 0; int tickctrlpwr = 0; int ticktockOn = 4; //int led1day = 5; 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 quartz03 =5; // Quartz clock motor pulse 03 int quartz04 =6; // Quartz clock motor pulse 04 //******************** 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(led30, HIGH); // turn 30 sec clocks off at start tickctrlpwr = 0; // digitalWrite(qtrchime, HIGH); // turn Qtr chime off at start //digitalWrite(hourchime, HIGH); // turn Hour chime off at start //****** 7 seg /* The MAX72XX is in power-saving mode on startup, we have to do a wakeup call */ lc.shutdown(0,false); /* Set the brightness to a medium values */ lc.setIntensity(0,0); /* and clear the display */ lc.clearDisplay(0); lc.setScanLimit(0, 7); // set to 8 digits lcd.begin(20,4); // initialize the lcd for 20 chars 4 lines, turn on backlight // using namespace DCF77_Encoder; 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 Dial Clock "); //*************** Serial.begin(9600); /* Serial.println(); Serial.println(F(" DCF77 Dial 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(qtrchime, OUTPUT); // pinMode(hourchime, OUTPUT); pinMode(led30, OUTPUT); pinMode(tick, OUTPUT); pinMode(ledpin(dcf77_monitor_led), OUTPUT); //v3 // pinMode(led1day, OUTPUT); pinMode(quartz01, OUTPUT); pinMode(quartz02, OUTPUT); pinMode(quartz03, OUTPUT); pinMode(quartz04, OUTPUT); pinMode(ticktockOn, INPUT); //pinMode(17, INPUT); // infrared switch pinMode(advanceSw,INPUT); pinMode(retardSw,INPUT); pinMode(infraredSw,INPUT); //pinMode(infraredSw, INPUT); // infrared sensor output //******************* 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 digitalWrite(tick, HIGH); // turns off "Tick Tock" while clock syncs 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 (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) // if extra second pulse required { delay(100); // add delay so seconds mtor can operate again in the same second secondsmotor (); // function steps quartz motor and adds extra pulse if secmiss is >1 } */ 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 // ################################################################################ // Mute Tick Tock on startup ticktockOn = digitalRead(04);// chimes are off on power up press "chime On" switch to lock on tickctrl = ticktockOn; if (tickctrl == 1 || tickctrlpwr == 1) { tickctrlpwr = 1; // lcd.setCursor(0, 2); // lcd.print("Chime On "); } // End Mute Tick Tock on startup // Tick Tock if ( (seconds % 2) == 0 && tickctrlpwr == 1 ) { digitalWrite(tick, LOW); //play "Tick Tock" every even second (sound is under 2 secs duration) } else { digitalWrite(tick, HIGH); // odd seconds sound is off } // end Tick Tock /* 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(13,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(" Dirty ")); break; case Clock::synced: //v3 Mod lcd.print(F(" Sync'd")); break; case Clock::locked: //v3 Mod lcd.print(F(" Locked")); 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); //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% //LEDseconds(); // converts secsval to seconds // LEDminutes(); // converts minsval to minutes // LEDhours(); // converts hourval to minutes //LEDseconds1(); // converts seconds to 2 single digits for 7 segment seconds display seconds2 = floor(seconds/10); //gets 2nd seconds char from seconds for 7 segment display seconds1 = seconds - (seconds2 * 10); //gets 1st seconds char from seconds for 7 segment display minutes2 = floor(minutes/10); //gets 2nd seconds char from seconds for 7 segment display minutes1 = minutes - (minutes2 * 10); //gets 1st seconds char from seconds for 7 segment display hours2 = floor(hours/10); //gets 2nd seconds char from hours for 7 segment display hours1 = hours - (hours2 * 10); //gets 1st seconds char from hours for 7 segment display // start infrared detect ####################################### // int infraredSw = digitalRead(17); // reads infrared detector output int infraredSw = analogRead(A3); // reads infrared detector output // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V): float infraredSwval = infraredSw * (5.0 / 1023.0); Serial.print ("Infrared "); Serial.println (infraredSw); Serial.println (""); Serial.print ("Infrared Voltage "); Serial.println (infraredSwval); Serial.println (""); if (infraredSwval >=2 ) // turns displays on during the day if infrared detects movement { lc.shutdown(0, false); // turns on the 7 segment display // lcd.backlight(); // backlight on not needed as controlled by MAX2719 lcd.display(); //display on } else if (infraredSw < 2) // turns displays off during the day unless infrared detects movement { lc.shutdown(0, true); // turns off the 7 segment display and puts it into standby mode // lcd.noBacklight(); // LCD backlight off not needed as controlled by MAX2719 lcd.noDisplay(); //LCD display off } // end infrared detect ####################################### // shows 7 segment display if infrared detected or time is after 22:00 and before 08:00 /* Set the brightness to a medium values */ //lc.setIntensity(0,8); lc.setIntensity(0,intensity); // set 7 segment intensity to value of "intensity" // lc.setScanLimit(0, 5); /* and clear the display */ // lc.clearDisplay(0); lc.setChar(0,7,seconds1,false); // print 1st seconds digit on 7 segment display and "false" turns DP off lc.setChar(0,6,seconds2,false); // print 2nd seconds digit on 7 segment display and "false" turns DP off lc.setChar(0,5,'o',false); // print lower case O on 7 segment display and "false" turns DP off tape mask shows : lc.setChar(0,4,minutes1,false); // print 1st minutes digit on 7 segment display and "false" turns DP off lc.setChar(0,3,minutes2,false); // print 2nd minutes digit on 7 segment display and "false" turns DP off lc.setChar(0,2,'o',false); // print lower case O on 7 segment display and "false" turns DP off tape mask shows : lc.setChar(0,1,hours1,false); // print 1st minutes digit on 7 segment display and "false" turns DP off lc.setChar(0,0 ,hours2,false); // print 2nd minutes digit on 7 segment display and "false" turns DP off /* 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); //************** // Serial.print(offset_to_utc<0? '-':'+'); // lcd.print(offset_to_utc<0? '-':'+'); // if (abs(offset_to_utc) < 10) { // Serial.print('0'); // lcd.print('0'); // } // Serial.println(summertest); } //******************* // secsval = (now.second.val); // minsval = (now.minute.val); // hourval = (now.hour.val); // Serial.print ("Secs Val "); // Serial.print (secsval); // Serial.print (" Min Val "); // Serial.print (minsval); // Serial.print (" Hr Val "); // Serial.print (hourval); /* // Quater chime if (( minutes ==15 || minutes ==30 || minutes==45) && seconds==0) { digitalWrite(qtrchime, LOW); // lc.clearDisplay(0); // Test reset LED display on Qtr Chime to pevent corrupted display } else if (minutes ==59 && seconds==57) { digitalWrite(qtrchime, LOW); } else digitalWrite(qtrchime, HIGH); // End quater chime */ /* // Hour chime // map hours to range 1-12 const uint8_t normalized_hours = hours > 12 ? hours-12 : hours > 0 ? hours : 12; // compute chime instead of matching it digitalWrite(hourchime,minutes == 0 && (seconds & 1) == 0 && seconds< 2 * normalized_hours); // End Hour chime */ // 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 if (pulsecount < 120 && (seconds == 0 || seconds == 30) && (advanceSwval == 1 || pulseOn == 1)) // summertest is 1 in summertime and 0 in wintertime advance on 00 & 30 secs does not advance pulse count { // sumwinSwval = 1; pulseOn = 1; //Serial.println(" 30 second test "); digitalWrite(led30, LOW); // turn the PULSE ON trigger mono stable (HIGH is the voltage level) delay(10); digitalWrite(led30, HIGH); // turn the PULSE OFF trigger mono stable(HIGH is the voltage level) } else if (pulsecount < 120 && (advanceSwval == 1 || pulseOn == 1)) // summertest is 1 in summertime and 0 in wintertime { // sumwinSwval = 1; pulseOn = 1; pulsecount = pulsecount + 1; digitalWrite(led30, LOW); // turn the PULSE ON trigger mono stable (HIGH is the voltage level) delay(10); digitalWrite(led30, HIGH); // turn the PULSE OFF trigger mono stable(HIGH is the voltage level) } else { 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 < 120 && retardSwval == 1) { retardOn = 1; } else if (retardcount < 120 && (seconds == 0 || seconds == 30) && (retardSwval == 1 || retardOn == 1)) { retardOn = 1; retardcount = retardcount + 1; } else if (retardcount < 120 && (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 == 120 ) //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 Dial 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 v13.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 == 119 ) //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 == 120 ) //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 ############################## // 30 second clock pulses if ((retardOn == 0 && pulseOn ==0) && (seconds == 0 || seconds == 30)) // will only pulse if retardOn = 0 ( not in winter retard mode) { digitalWrite(led30, LOW); // turn the LED on (HIGH is the voltage level) // Serial.println(" Normal 30 sec "); } else if ((retardOn == 0 && pulseOn ==0) && (seconds != 0 || seconds !=30)) // will only pulse if retardOn = 0 ( not in winter retard mode { digitalWrite(led30, HIGH); // turn the LED off (LOW is the voltage level) } // end 30 second clock pulses /* // 1 min pulse { if (seconds == 0) { digitalWrite(led1min, HIGH); // turn the LED on (HIGH is the voltage level) } else if(seconds != 0) digitalWrite(led1min, LOW); // turn the LED off (LOW is the voltage level) } // 1 hour pulse { if (minutes == 0 && seconds== 0) { digitalWrite(led1hr, HIGH); // turn the LED on (HIGH is the voltage level) } else if (minutes != 0 || seconds !=0) digitalWrite(led1hr, LOW); // turn the LED off (LOW is the voltage level) } */ /* // 1 day pulse { if (hours == 0 && minutes == 0 && seconds == 0) { digitalWrite(led1day, HIGH); // turn the LED on (HIGH is the voltage level) } else if(hours != 0 || minutes != 0 || seconds != 0) digitalWrite(led1day, LOW); // turn the LED off (LOW is the voltage level) } */ } void secondsmotor (){ if (quartzmotor1 == LOW) { quartzmotor1 = HIGH; } else quartzmotor1 = LOW; { digitalWrite(quartz01, quartzmotor1); // set the quartz motor drive 7 pin digitalWrite(quartz03, quartzmotor1); // set the quartz motor drive 5 pin } if (quartzmotor2 == HIGH) { quartzmotor2 = LOW; } else quartzmotor2 = HIGH; { digitalWrite(quartz02, quartzmotor2); // set the quartz motor drive 8 pin digitalWrite(quartz04, quartzmotor2); // set the quartz motor drive 6 pin } } //********************** //add extra quartz pulse // if (secsmiss > 1) // if >1 pulse has been missed so extra quartz pulse is added here // ################################################################################ void segIntensity(int intensity){ if(intensity < 10) lcd.print("0"); // Print hour on first line lcd.print(intensity); } // end void advance(int pulsecount){ // leading 0 on pulse count if (pulsecount < 100) lcd.print("0"); } void advancetens(int pulsecount){ // leading 0 on pulse count if (pulsecount <10) lcd.print("0"); } //****************************** void retard(int retardcount){ // leading 0 on pulse count if (retardcount < 100) lcd.print("0"); } void retardtens(int retardcount){ // leading 0 on pulse count if (retardcount <10) lcd.print("0"); } void monthzero(int monthval) { // leading zero on month value if (monthval < 10) lcd.print("0"); // Print hour on first line lcd.print(monthval); } void dayzero(int dayval) { // leading zero on day value if (dayval < 10) lcd.print("0"); // Print hour on first line lcd.print(dayval); } // intensity of 7 segment display void intensityValue() { // if (ldrValue < 40) // { // intensity = 0; // } if (ldrValue >= 0 && ldrValue < 50) { intensity = 1; } else if (ldrValue >= 50 && ldrValue < 85) { intensity = 2; } else if (ldrValue >= 85 && ldrValue < 120) { intensity = 3; } else if (ldrValue >= 120 && ldrValue < 155) { intensity = 4; } else if (ldrValue >= 155 && ldrValue < 190) { intensity = 5; } else if (ldrValue >= 190 && ldrValue < 225) { intensity = 6; } else if (ldrValue >= 225 && ldrValue < 260 ) { intensity = 7; } else if (ldrValue >= 260 && ldrValue < 295) { intensity = 8; } else if (ldrValue >= 295 && ldrValue < 330) { intensity = 9; } else if (ldrValue >= 330 && ldrValue < 365) { intensity = 10; } else if (ldrValue >= 365 && ldrValue < 400) { intensity = 11; } else if (ldrValue >= 400 && ldrValue < 435) { intensity = 12; } else if (ldrValue >= 435 && ldrValue < 470) { intensity = 13; } else if (ldrValue >= 470 && ldrValue < 505) { intensity = 14; } else if (ldrValue >= 505) { intensity = 15; } } void blankrow3() { lcd.setCursor(0,3); lcd.print(" "); } void signalmatch() { signalQual = DCF77_Clock::get_prediction_match(); if(signalQual == 255 || signalQual == 0 ) { signalQual = 00; } else { signalQual = signalQual * 2; } lcd.setCursor(0,3); lcd.print("Sig Match "); lcd.print(signalQual); lcd.print("%"); } void precision() { lcd.setCursor(0,3); lcd.print("Accuracy "); lcd.print(DCF77_Frequency_Control::get_confirmed_precision()); lcd.print("Hz "); } /* void freqadj() { lcd.setCursor(0,3); // lcd.print("Qtz "); lcd.print(16000000L - DCF77_1_Khz_Generator::read_adjustment()); lcd.print("Hz"); } */