Home    Master Clock   LCD Master Clock    Kenley Weather

Section   Top      Specs  About    Leap Second   Case   Winter/Summer Correction    Summer Time Video  Switches  Dial   Dial Design  Movements Mods    Movement Mounting   TFT Display Sound Module  Import Sound    Vero Board   Schematic   Code

 

 

 

 

Arduino controlled DCF77 Synchronized Astronomical Regulator Clock

I have added Arduino controlled DCF77 synchronization to  my old Slave Regulator Clock.

The Arduino decodes the DCF77 time signal from Germany using Udo Klein's DCF77 library v3

The Arduino Code is based on my Master Clock code and has been completely re-written for Udo Klein's V3 library by Peter Hillyer.

Peter has added code for a 2.2" TFT SPI display, auto summer winter correction of the Lavet type stepping motors for the hours/minutes and sound via a JQ6500 sound module controlled over the serial port.

 

below Peter Hillyer's TFT code on a 2.2" TFT display

 

 

 

 

 

Specs

DCF77 Synchronized time

Uses Udo Klein's DCF77 v3 library

Full Westminster Chime and hours

Chimes can be Off, always On & Timed

Manual control of volume

Automatic correction of Summer & Winter Time (manually triggered)

Hours, Minutes and Seconds displayed on Lavet type stepper motors

Clock and DCF77 status monitored on internal 2.2" SPI TFT display

Batt backup from 3 1.5v Alkaline AA batteries

Separate 30second slave/sync output

 

 

 

 

 

 

About Astronomical Regulator Clocks
Astronomical Regulator Clocks had unusual dials, with a large minute hand and smaller seconds and hour hands and were used in observatories around the world. This dial layout is designed to minimise mistakes when reading the time. Original versions of these clocks were very accurate, extremely well engineered and are very rare.
 

Dial Designs
The dials on these clocks all seem to have different designs with some starting at 0 or ending in 60, some with roman numerals and some with Arabic numerals or a mixture of both.

 



I chose my favourite parts from each dial type (Arabic minutes ending on 60, Arabic seconds ending on 60 and Arabic 24 hour ending in 24. My preferred layout was then drawn up in TurboCAD. Any other vectored drawing package can be used.

 

 

 

 

 

 

 

Leap Second Detecting & Adjustment

Leap seconds are detected and clocks automatically adjusted.

The leap second injected on 31/12/2016 has been detected as a gained second on the TFT display below.

 

 

This screen shows the actual time and date it was detected 23:59:60 31/12/16.

The second has been injected by adding an extra second after 23:59:59.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

Case

 

 

The case was from an old English School Dial Clock. The old dark stained case has been stripped back

and bleached to remove the rosewood stain and lighten it several shades.

 

 

 

 

 

 

The dial bezel and surround are hinged for access.

 

 

 

 

The side access door is now used for basic clock setting on initial setup.

 

 

 

 

 

 

 

 

Clock Operation

Once the clock is set the Arduino keeps it in sync with the DCF77 time on the TFT display.

 

Winter Time correction

The code is set to wait for a manual trigger (retard switch pressed) before setting the clock back an hour. When pressed the seconds and hour hand stop. Really only the hour hand needs to stop but I stop the seconds hand and keep the minute hand going to indicate the clock is in "winter retard mode". The TFT display counts down the missed  seconds from 3600 and when it reaches zero the hour and seconds hands restart with the hour hand now being an hour retarded.

below when the "Retard" switch is operated the display changes to show the retard count. When this count reaches 0 the hour and second hands restart

 

 

 

Summer Time correction

The code is set to wait for a manual trigger (advance switch pressed) before setting the clock forward an hour. When pressed the seconds and hour hand start stepping forward 2 times a second. Really only the hour hand needs to be advanced but I advance the seconds hand and keep the minute hand going normally to indicate the clock is in "summer advance mode". The TFT display counts the extra  seconds from 3600 and when it reaches zero the hour and seconds hands stop advancing the hour hand now being an hour advanced.

 

 

 

 

Fully automatic Summer Winter Correction

If you just want auto control and 2 less switches to wire as per Peter's original design just modify the code.

 

 

 

 

 

Control Switches

The are a large number of switches in this clock many of them can be left out to make the construction simpler especially SW1,SW2 & SW3 as these were part of my original slave clock wiring.

The clock movements can also be modified to include the current limiting variable resistor within the movement taking up less space on the main boards.

Name Type Function
SW1  2 way centre off non locking Man stepping of seconds with SW2 set to Manual
     
SW2 2 way locking Auto/manual control of seconds
     
SW3 1 way locking Hours movement On or Off
     
Reset 1 way non locking press On Resets Arduino and TFT display
     
Chime Mode 1 way non locking press On Selects Chime mode between On Green, On times Yel, Off 
     
Advance 1 way non locking press On Sets clock into Summer Adavance Mode
     
Retard 1 way non locking press On Sets Clock into Winter Retard Mode
     
Sec Reset 1 way non locking press On Stops analogue movements and then restarts @ 0 seconds
     
Vol-/Prev 1 way non locking press On/Long Press Short press play prev sound, long press volumn down
     
Vol+/Next 1 way non locking press On/Long Press Short press play next sound, long press volumn up
     
Play/Stop 1 way non locking press On Toggles play/stop current sound

 

 

 

 

 

Dial

The dial has been designed and drawn up using a Cad package and printed onto inkjet transfer paper.

The designs is then applied to the off white painted dial blank.

 

 

 

 

 

 

 

Dial Drawing

You will have to make your own dial and the easiest way is to design it on a CAD program. A 12" dial will fit nicely on A3 paper so you may have to take it to a printer if you don't have access to one. You can print the dial in 2 halves on A4 paper and then join them together. I print out a few designs on plain paper and try them out on the clock to see which I prefer.

 

fig.1

There are probably many ways to draw dials and many different types of software to draw it. I use TurboCAD and I start with the minute ring starting from a point in the middle of the dial. First draw a circle the actual size of the dial. This is your cutting guide for the finished dial. Then draw 2 circles centered on the middle point of the dial. These form the inner and outer edge of the minute dial ring.

fig.2

Starting at the top of the dial draw a vertical line from the inner and outer minute ring. Select this line and then using "radial copy" copy this line 60 times with a 6° step angle (360°/60=6°).

fig.3

The minute numbers are then added.

Choose the font and the size of numbers you require then draw a circle as before but add half the size of the numbers and a few millimeters to allow for the space between the numbers and the minute dial. You will now have the minute dial and a circle around it. Add a cross to the top of this circle. The radial copy as before with 12 copies with a step angle of 30° (360°/12=30°). You should now have the circle with 12 points for your 5 minute intervals marked out. Starting with the number 12 and selecting it's middle point place it on the top cross. Do the same with the number 5 but rotate it by 30°, the number 10 rotated by 60°, the number 15 rotated by 90° until you get to the number 55 rotated by 330°.

Draw center points for seconds and hour dials and using the same technique you used for the minute dial draw the seconds and hour dials.

fig.4

Draw dial labels as required. These can be anything you want. Often these included the makers name and city/town of manufacture.

Delete all guide lines and points but leave in the 3 center points as these will be used on a paper print as a template for the dial spindle holes.

The paper template is fitted over the dial and the 3 center points are marked with a centre punch or nail and hammer. Pilot holes are drilled and then these positions are also marked on the movement mounting bar see the section "mounting the quartz movements". Increase the drill size in steps until the final spindle hole size is reached on the dial and movement mounting bar.

The final design is then printed out on A3 Lazertran inkjet paper as a giant decal. The centre points will allow you to position the clear decal over the three spindle holes.

The original dial is first rubbed down to remove all old paint and lettering. It is then primed and painted an off white colour (white looks terrible in an old clock case). The dial decal is then applied to the blank dial and then when dry 3 coats of acrylic varnish are sprayed over the top. This makes the decal background go clear so the off white dial can be seen with the dial lettering on top. See the instructions that come with the Lazertran paper for full details.

 

 

 

 

 

 

 

Modifying the Movements

U.T.S. Quartz Clock Movement Hack

 

 

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 1 part.

 

Turn the Quartz PCB and motor section over to reaveal 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.

 

 

Solder the wires to the "Clk Motor Coil" terminals on the Lavet type stepping motor driver board mounted in the battery bay.

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.

 

 

 

 

 

 

 

Mounting the Movements

An aluminium bar is recessed into the wooden dial surround and holes are drilled to take the 3 Lavet motor movements.

 

 

 

 

Mounting the movements onto this bars hides the movement mounting nuts which would spoil the antique look of the dial.

 

 

 

The mounting bar is fixed to the wooden dial surround using small wood screws.

 

The dial is then fixed to the outside of the dial surround as normal hiding the movement mounting bolts.

 

 

 

 

 

 

 

TFT Module

The 2.2" LCD in the TFT01 is a IL19341 and has a 240 * 320 resolution.

 

 

 

Pins

§  SDO: Serial clock output

§  LED: 3.4V Power Supply pin

§  SCL: Serial clock input

§  SDA / SDI: Serial data input

§  DC: Data / Command selection

§  RST: Reset, Low level active

§  CS: Chip Selection, Low level active

§  GND: Ground

§  VDD33: 3.3V Power Supply pin

 

A level converter is used between the Arduino and some of the TFT display pins as they require 3.3v.

See schematic for details.

 

 

 

 

 

 

 

Sound Module

 

General Communication

The device (appears to) accepts commands at any time.  Commands consist of 4 or more bytes,

Each command starts with byte 0x7E

Followed by a byte indicating the number of  bytes which follow including the terminating byte (including termination)

Followed by a byte indicating the command to execute

Followed by an optional first argument byte

Followed by an optional second argument byte

Followed by the byte  0xEF as the termination byte

for example, the command ”PLAY” (0x0D) is constructed with the following 4 bytes

0x7E – Start Byte

0x02 – 2 Bytes Follow

0x0D – Command Byte

0xEF – Termination Byte

and the command  to play a specific file (0x012) has two arguments (folder number and file number) so it looks like this

0x7E – Start

0x04 – 4 Bytes Follow

0x12 – Command

0x02 – 1st Argument  (in this case,  “Folder 02”)

0x03 – 2nd Argument (in this case,  “File 003”)

0xEF – Termination Byte

Please note that you are not sending ASCII characters here, but those raw bytes (ie 0x7E is 8 bits, not 4 characters!).

Normal commands provide potential response of two ascii characters  “OK” and maybe  “ERROR”, but generally ignore responses to normal commands (it’s best to clear your serial buffer before and after issuing a normal command).

Query commands return an unsigned integer as hexadecimal characters (ie if the response is the integer 1234, then the response is the 4 ASCII characters  “04D2”, so yes, the commands are sent as raw bytes, and the response is ASCII).

 

 

Send commands

 

0x0D – Play, No Arguments

0x0E – Pause, No Arguments

0x01 – Next, No Arguments

0x02 – Prev, No Arguments

0x03 – Play file by index number, 2 Arguments.  The index number being the index in the FAT table, or upload order.  Argument 1 = high 8 bits of index number, Argument 2 = low 8 bits of index number.

0x0F – Change folder. 1 Argument.  Argument 1 = 0x01 for Next Folder, 0x00 for Previous Folder. 

0x12 – Play file by folder and name, 2 Arguments.  This applies to SD Card only where you have folders named 01 through 99, and files in those folders named 001.mp3 through 999.mp3.  Argument 1 = folder number, Argument 2 = file number.  Note that arguments are a single byte, so effectively I think you can only access up to file 255.mp3 in any folder.

0x04 – Vol Up, No Arguments

0x05 – Vol Dn, No Arguments

0x06 – Set Volume, 1 Argument.  Argument 1 = byte value from 0 to 30

0x07 – Set Equalizer Mode, 1 Argument.  Argument 1 = byte value 0/1/2/3/4/5 for Normal/Pop/Rock/Jazz/Classic/Bass (actually  “Base” in the datasheet but I think they mean Bass)

0x11 – Set Loop Mode, 1 Argument.  Argument 1 = byte value  0/1/2/3/4 for All/Folder/One/Ram/One_Stop – I don’t know what  “Ram” is, it’s not Random, it seems the same as  “One”.

0x09 – Set the source, 1 Argument.  Argument 1 = 0x01 for SDCard and 0x04 for the on board flash memory.

0x0A – Sleep mode, No Arguments.  Supposedly a low power mode.

0x0C – Reset, No Arguments.  It’s advisable to wait 500mS or so after issuing this.

 

 

 

 

 

Parameter Query

None of the query commands have arguments. 

 

0x42 – Get Status. Response integer (as hexadecimal ascii characters) 0/1/2 for Stopped/Playing/Paused.  Note that  built in memory never  “Stops”, it only  “Pauses” after playing a track.  And when playing you occasionally seem to get the odd  erroneous “Paused” response, it may be power issues, but in the Arduino library I sample this command several times to get a  “consensus” of results!

0x43 – Get Volume.  Response integer (as hexadecimal ascii characters) from 0 to 30.

0x44 – Get Equalizer. Response integer (as hexadecimal ascii characters) from 0 to 5 (see set equalizer above for definitions).

0x45 – Get Loop. Response integer (as hexadecimal ascii characters) from 0 to 4 (see set loop above for definitions).

0x46 – Get Version.  Response appears to be an integer (as hexadecimal ascii characters).

0x47 – Count files on SD Card. Response integer (as hexadecimal ascii characters).

0x49 – Count files in on board flash memory.  Response integer (as hexadecimal ascii characters).

0x53 – Count folders on SD Card. Response integer (as hexadecimal ascii characters).

0x4B – Get the index number (FAT table) of the current file on the SD Card.  Response integer (as hexadecimal ascii characters).

0x4D – Get the index number MINUS ONE (!!) of the current file on the on board memory.  Response integer (as hexadecimal ascii characters).

0x50 – Get the position in seconds of the current playing file.  Response integer (as hexadecimal ascii characters).

0x51 – Get the total length in seconds of the current playing file.  Response integer (as hexadecimal ascii characters).

0x52 – Get the name of the current file on the SD Card.  Response ASCII characters.  Note that this will return a name even if the file is not playing, even if a file from the on board memory is playing, even if the SD Card has been removed… !  It’s also not really the file name, it lacks the file extenstion separator for a start (.), and is probably 8[nodot]3 max length.

 

 

 

 

 

 

 

 

Adding Sound to the Module

Plug the module into your PC and run the file MusicDownloader.exe on the module.

 

 

Click on the 2nd tab then clock on the button to the right of the black box

 

A file requester will open. Select your sound files and clock on Open

 

 

Go back to the first tab and click the button after a short delay the files will start to be copied

 

When complete you will get this message

 

 

Further info on this module can be found here JQ9500 info

 

 

 

 

 

 

Sound Files

Download the sound files here

001.mp3 to 012.mp3 are the hour chimes including the 4th qtr Westminster chime, 013.mp3 is the 1st Qtr chime, 014.mp3 is the 2nd Qtr chime and 015.mp3 is the 3rd Qtr chime.

 

 

 

 

 

 

 

Vero Board Layouts

As this clock is a modification to an old slave clock there are 3 separate Vero boards. If you are making it from scratch then you would probably only want 1 or 2 separate boards.

Board 1 is the main Arduino DCF77 control and TFT display board.

Board 2 is the Lavet motor driver board.

Board 3 is the clock control switch board.

Note SW1, 2 & 3 for setting the clock and the considerable amount of associated wiring can be left out as Peter Hillyer's design incorporates a set switch. I have labled it "setswitch" or "sec reset".

To use press the button and the clock stops. Set all the hour & minute hands to the next correct minute and put the seconds hand on zero. The clock will wait until the next minute and start the clock again so all hands are now in sync.

On my clock the wiring was already there so I have both Peter's and my manually setting options.

 

 

Main Control Vero board Layout without modules

note only some link wiring shown (mainly batt/eth runs) see schematic for full wiring

 

 

 

Vero board Layout with modules plugged in

 

 

 

 

Vero board rear view (flipped down)

 

 

 

Lavet Motor Driver Vero Board

 

 

Switch Vero Board

 

 

 

Board positions in clock case

 

 

 

 

 

 

 

Schematic

 

 

 

 

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

Download Code V1.6

  v1.6 30 second pulse polarity changed to sync Pong clock

 

Requires Udo Klein's V3 library  https://blog.blinkenlight.net/2015/08/01/dcf77-library-release-3-0-0/

Download here

 

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.

 

/*
  DCF77 TFT Clock
  Pete Hillyer (2016)
  
  
  This clock uses a serial routine for the Audio Module based on code from the JQ6500_Serial library by James Sleeman
  see https://github.com/sleemanj/JQ6500_Serial/ for details
  
  
  Pete Hillyer's code modified by Brett Oliver (2016)
  Modifications by Brett Oliver http://home.btconnect.com/brettoliver1/Regulator_Clock/Regulator_Clock.htm
  
  line 1158 changed to 47 to sync chime

  v1.6 30 second pulse polarity changed to sync Pong clock
  
 
  v1.5 Chime startup changed to 06:00 to 23:00 (chime will start from first 1/4 after 05:00hors)
  
  v1.4
  Chime default set to off (chimemode set to 2)
  line 313 changed to hex value for vol control
  
  v1.3
  30 second clock pulse added for driving 30 second slave clocks
  correction of 2nd coil output from v1.2
  
  v1.2 
  2nd coil output added to drive seconds motor (this will not be advanced or retarded in sum/win
  
  
  v1.1 
  man advance/retard added
  removed Change BST switch (bstSw) pin D5
  added man summer advance advanceSw D5
  added man winter retard retardSw D2
  
  V1.0 no changes
  
  based around Udo Klein's DCF77 V3.0.0 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/
*/ 


//================================================================================================================
// load required libraries

#include <dcf77.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>

using namespace Internal; //set to use Internal (namespace of DCF77 library

//================================================================================================================
// configure TFT display library
#define TFT_DC                9       // TFT DC pin to D9
#define TFT_CS                10      // TFT CS pin to D10

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

#define NOP __asm__ __volatile__ ("nop\n\t")  //define NOP instruction

//================================================================================================================
// configure DCF Library

//configure pins for DCF77 library

const uint8_t dcf77_analog_sample_pin = A4; // not used
const uint8_t dcf77_sample_pin        = 18; // DCF77 input signal
const uint8_t dcf77_inverted_samples  = 0;
const uint8_t dcf77_analog_samples    = 0;
const uint8_t dcf77_pull_up           = 1;

const uint8_t dcf77_monitor_led       = 15; // (A1)

uint8_t ledpin(const uint8_t led){
  return led;
}

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

//================================================================================================================
// I/O and other definitions

//inputs
const int setSwitch   = 3;                    // (D3) quartz clock seconds hold switch
//const int bstSw       = 5;                    // (D5) input for BST change switch DEBUG
const int retardSw    = 2;                    // (D2) input for winter retard
const int advanceSw   = 5;                    // (D5) input for summer advance
const int chimeModeSw = 4;                    // (D4) input for chime mode switch
const int chimeBusy   = 19;                   // (A5) busy signal from audio module


//outputs
const int Pulse30sec = 6;                     // 30 second slave clock pulse
const int clockCoil01 = 7;                    // (D7) clock coil pin 1
const int clockCoil02 = 8;                    // (D8) clock coil pin 2
const int clockCoil01a = A2;  // seconds drive no advance retard (A2) clock coil pin 1 
const int clockCoil02b = A3;  // seconds drive no advance retard (A3) clock coil pin 2


//================================================================================================================
// define variables

boolean retard        = false;  // true if clock going back
boolean advance       = false;  // true if clock going forward
boolean bstChanged    = false;  // bst test switch released
boolean lastBst       = false;  // BST just changed flag
boolean oneSecTick    = false;   // changes state once a second (end of <loop?)
boolean coilStatus    = false;  // clock coil status
boolean coilStatusSec    = false;  // clock coil status for seconds motor
boolean clockRun      = true;   // when false clock motor will stop used for retard and sync 
boolean firstRun      = true;   // set to false after first pass of main loop

byte dateDowCount     = 0;      // lcd date day of week counter
byte bst              = 0;      // equals 0 summertime or 2 for wintertime

//variables for chime system
byte chimeMode = 2;    // chime mode initial status (2=off, 1=day, 0=full)
byte chimeRunning = 0; // set initial chime state
byte chimeChanged = 0; //chime mode recently changed
byte chimeEnabled = 1; //chime system enabled
byte chimeIdx = 0x00; //stores the chime file index number

//variables for missed seconds (quartz needs to gain a second)
byte missedCount      = 0;  // missed seconds counter
byte hourMissed       = 0;  // hour of last missed pulse
byte minuteMissed     = 0;  // minute of last missed pulse
byte secondMissed     = 0;  // second of last missed pulse 
byte dayMissed        = 0;  // day of last missed pulse
byte monthMissed      = 0;  // month of last missed pulse
byte yearMissed       = 0;  //year of last missed pulse

//variables for gained seconds (quartz needs to lose a second)
byte gainedCount      = 0;  // gained seconds counter
byte hourGained       = 0;  // hour of last extra pulse
byte minuteGained     = 0;  // minute of last extra pulse
byte secondGained     = 0;  // second of last extra pulse
byte dayGained        = 0;  // day of last extra pulse
byte monthGained      = 0;  // month of last extra pulse 
byte yearGained       = 0;  // year of last extra pulse

int signalQual        = 0;    // computed every minute, indicates how well received signal matches locally synthesized reference signal max 50
int bstCount          = 3600; // count for bst advance\retard (3600 seconds need to be added or stopped from the clock motor)
int lastSecond        = 0;    // value of the last second
int secsMiss          = 0;    // used for calculating gained or missed seconds

//variables for last sync time
byte hourSync         = 0;    // hour of last DCF Sync  
byte minuteSync       = 0;    // minute of last DCF Sync  
byte secondSync       = 0;    // second of last DCF Sync  
byte yearSync         = 0;    // year of last DCF Sync  
byte monthSync        = 0;    // month of last DCF Sync  
byte daySync          = 0;    // day of last DCF Sync  

//variables for current time
int currentHour       = 0;    // current hour set from DCF library
int currentMinute     = 0;    // current minute set from DCF library
int currentSecond     = 0;    // current second set from DCF library
int currentyear       = 0;    // current year set from DCF library
int currentMonth      = 0;    // current month set from DCF library
int currentDay        = 0;    // current day set from DCF library
int currentWeekday    = 0;    // current weekday set from DCF library

//================================================================================================================
//***************** setup DCF77 Library ********************
namespace Timezone {
    uint8_t days_per_month(const Clock::time_t &now) {
        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) {
        // 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;
            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);
        }

        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;
            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);
        }

        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(ledpin(dcf77_monitor_led), sampled_data);
  return sampled_data;
}

//================================================================================================================
//
// SETUP
//
//================================================================================================================
void setup() {

  //configure i/o port direction
  //inputs
  pinMode(dcf77_sample_pin, INPUT);
  pinMode(setSwitch, INPUT);
  pinMode(chimeBusy, INPUT);
  pinMode(chimeModeSw, INPUT);
  pinMode(retardSw, INPUT_PULLUP);
  pinMode(advanceSw, INPUT_PULLUP);
  //pinMode(bstSw, INPUT_PULLUP);
  
  //outputs
  pinMode(ledpin(dcf77_monitor_led), OUTPUT);
  pinMode(clockCoil01, OUTPUT);
  pinMode(clockCoil02, OUTPUT);
  pinMode(clockCoil01a, OUTPUT);
  pinMode(clockCoil02b, OUTPUT);
  pinMode(Pulse30sec, OUTPUT);
  
  //set default i/o  states
  digitalWrite(dcf77_sample_pin, dcf77_pull_up);
        
  //start serial port for data
  Serial.begin(9600);
  //start TFT display
  tft.begin();

  /*
  // read TFT diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  */

  //startup display defaults (320x240)
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);

  //display DCF sync status information
  setCursor(10,0);
  setTextSize(3);
  setTextColour(ILI9341_WHITE, ILI9341_BLACK);
  tft.print(F("DCF77 Clock v1.6"));
  setCursor(10,30);
  setTextSize(2);
  tft.print(F("Brett Oliver (2016)"));
  drawFastHLine(0, 50, 320, ILI9341_RED);
  setCursor(10,60);
  setTextSize(2);
  tft.print(F("Aquiring DCF Sync..."));
  setTextSize(6);
  setTextColour(ILI9341_CYAN, ILI9341_BLACK);

  //initialise audio chime module
  delay(10);
 // sendCommand(0x06, 0x1a, 0); //set volume to max avoiding clipping 
  sendCommand(0x06, 0x02, 0); //Brett test set vol to level 2 low (2nd param 0x02)
  
  delay(500);
  sendCommand(0x11, 0x04, 0); //set loopmode to once
  //delay(500);
  //sendCommand(0x0c, 0, 0); //reset command
  delay(100);

  //print initialisation messages on serial monitor
  /*
  Serial.println();
  Serial.println(F("DCF77 TFT Clock v1.6"));
  Serial.println(F(" Brett Oliver 2016"));
  Serial.println(F("Based on the DCF77 library by Udo Klein"));
  Serial.println();
  Serial.print(F("Sample Pin:      ")); 
  Serial.println(dcf77_sample_pin);
  Serial.print(F("Monitor Pin:     ")); 
  Serial.println(ledpin(dcf77_monitor_led));
  Serial.print(F("Timezone Offset: ")); 
  Serial.println(timezone_offset);
  Serial.println();
  */
  Serial.println(F(""));
  Serial.println(F("Initializing..."));
 // make sure Pong clocks are not sync'd at startup
  digitalWrite(Pulse30sec, LOW); // 30second clock pulse to sync Pong clock
  //start DCF Sync
  
  DCF77_Clock::setup();
  DCF77_Clock::set_input_provider(sample_input_pin);

  //DISABLE SECTION BELOW TO DEBUG
  ///*
  // Wait till clock is synced, depending on signal quality this may take a while
  // About 5 minutes with a good signal, 30 minutes or longer with a bad signal
  for (uint8_t state = Clock::useless;
        state == Clock::useless || state == Clock::dirty;
         state = DCF77_Clock::get_clock_state()) {

    // wait for next sec
    Clock::time_t now;
    DCF77_Clock::get_current_time(now);
    static uint8_t count = 0;
    
    // update sync time on display 
    setCursor(70,140);
    printTFT(currentMinute);
    tft.print(F(":"));
    printTFT(count);
    
    count++;
    
    Serial.print(F("."));   // render one dot per second while syncing to serial monitor
    if (count == 60) {
      count = 0;
      currentMinute++;
      if(currentMinute > 99) currentMinute = 0;
      Serial.println();
    }      
  }
  //*/
     
  // prepare TFT display for main program loop
  tft.fillScreen(ILI9341_BLACK);                // clear display
  setTextSize(2);       
  setCursor(10,0);                              
  setTextColour(ILI9341_WHITE, ILI9341_BLACK);  
  tft.print(F("Current Time  Day \\ Date"));    
  drawFastHLine(0,50,320,ILI9341_WHITE);        // draw separator line under time and date
  drawFastHLine(0,92,320,ILI9341_WHITE);        // draw separator line for "Signal status"
  setCursor(75,85);
  tft.print(F(" Signal status "));
  setCursor(22,110);
  tft.print(F("Sync       Quality    %"));
  drawFastHLine(0,145,320,ILI9341_WHITE);       // draw separator line for "BST 1hr 1min 30s 1sec"
  setCursor(17,138);
  tft.print(F(" BST  1hr 1min 30s 1sec "));
  drawFastHLine(0,202,320,ILI9341_WHITE);       // draw separator line for "Hr Chime Qtr"
  setCursor(45,195);
  tft.print(F(" Hr    Chime    Qtr "));
  fillRect(48,220,40,14,10565);           //draw chime 60 LED OFF
  fillRect(235,220,40,14,10565);          //draw chime 15 LED OFF
  
  
  
} // end of setup

//================================================================================================================
//
// LOOP
//
//================================================================================================================
void loop() {
  
  //DISABLE SECTION BELOW TO DEBUG
  ///*
  // wait for next sec
  Clock::time_t now;

  DCF77_Clock::get_current_time(now);
  Timezone::adjust(now, timezone_offset);

  // populate hours,mins,secs,years,months,day & day of week variables
  currentHour = BCD::bcd_to_int(now.hour);
  currentMinute = BCD::bcd_to_int(now.minute);
  currentSecond = BCD::bcd_to_int(now.second);
  currentyear = BCD::bcd_to_int(now.year);
  currentMonth = BCD::bcd_to_int(now.month);
  currentDay = BCD::bcd_to_int(now.day);
  currentWeekday = BCD::bcd_to_int(now.weekday);
  //*/
  
  //ENABLE TO DEBUG
  //DEBUGCounters();
  
  // print time and date to serial port
  /*
  Serial.print(currentHour);
  Serial.print(F(":"));
  Serial.print(currentMinute);
  Serial.print(F(":"));
  Serial.print(currentSecond);
  Serial.print(F(" "));
  Serial.print(currentDay);
  Serial.print(F(":"));
  Serial.print(currentMonth);
  Serial.print(F(":"));
  Serial.println(currentyear);
 */
 
 
 
  // display time and date on top row of tft display  
  displayTimeDate();
  displayStatusLeds();
  
  // check switch to sync clock seconds
  if(digitalRead(setSwitch) == LOW)       //clock set switch pressed
  {
    clockRun = false;                         //stop clock motor from running
    bstCount = 3600;                      //reset BST advance\retard system
    lastBst = bst;
    retard = false;
    advance = false;
  }
  else if(currentSecond == 1) clockRun = true;
        
  // process for missed\gained seconds and pulse clock coil
  if (firstRun == true) //first pass of program loop set secsmiss to 1, record time and date of DCF sync
  {
    secsMiss = 1;
    hourSync = currentHour;
    minuteSync = currentMinute;
    secondSync = currentSecond;
    yearSync = currentyear;
    monthSync = currentMonth;
    daySync = currentDay; 
  }
  else  secsMiss = currentSecond - lastSecond;
          
  if (secsMiss == -59 || secsMiss == -60 && currentSecond == 0) // takes account of seconds rollover -59 or leap second -60
  {
    secsMiss = 1;
    
  } 
   if (secsMiss >=1 && currentSecond !=60 ) // if zero or less seconds pulse need to be missed. Leap second missed if currentSecond = 60
  {
    pulsesecCoil(); // ignore advance retard for seconds motor (sec clock motor pulse only)
   
  }
  
  
  if (secsMiss >=1 && currentSecond !=60 && !retard && clockRun == true) // if zero or less seconds pulse need to be missed. Leap second missed if currentSecond = 60
  {
    pulseCoil(); // normal 1 sec clock motor pulse
    if(advance) extraPulse(); //if clocks have gone forward pulse clock again (3600 times)
  }
  
  if (secsMiss < 1 || currentSecond == 60) //records time of gained second (clock motor misses 1 pulse)
  {
    gainedCount++;                //increment gained count total
    hourGained = currentHour;           //record time and date of last gained second
    minuteGained = currentMinute;
    secondGained = currentSecond;
    yearGained = currentyear;
    monthGained = currentMonth;
    dayGained = currentDay; 
  }
    
  if (secsMiss > 1)                //records time of miss second (clock motor needs extra pulse)
  {
    missedCount++;                //increment Miss count total
    if(advance || retard){        //if in advance or retard add or remove pulses
      if(advance) bstCount++;     //advance: add pulse
      else bstCount--;            //retard: remove pulse
    }
    else extraPulse();            //not in adv or retard pulse clock motor again
    hourMissed = currentHour;           //record time and date of last missed second
    minuteMissed = currentMinute;
    secondMissed = currentSecond;
    yearMissed = currentyear;
    monthMissed = currentMonth;
    dayMissed = currentDay;
  } 
  if (missedCount > 99) missedCount = 0; // resets miss second counter to 0 after 99 pulses
  if (gainedCount > 99) gainedCount = 0; // resets miss second counter to 0 after 99 pulses

  lastSecond = currentSecond; 

  // Enable to display missed\gained on serial DEBUG USE ONLY 
  //DEBUGMissedGained();   // MAY CAUSE TIMING ERRORS

  // DISABLE BST SECTION BELOW TO COMPILE IN DEBUG MODE
  ///*
  // Compute and display BST information
  const int8_t offset_to_utc = timezone_offset + (now.uses_summertime? 2: 1);

  // enable to 2 lines below to activate 
  bst = (abs(offset_to_utc)); // if summertime bst will be 1 
  if (bst == 2) bst = 0; // if wintertime bst will be 2 so change it to 0
  // draw BST led 
  if (bst == 0) fillRect(25,162,40,14,10565);       //grey = off
  else fillRect(25,162,40,14,ILI9341_RED);          //red = on
  
  // the line below is enabled for debug use
  //changeBST();    //this is a test switch only for debug use
  // ################################### Brett test remove
  /*
  if(firstRun){             //if first pass of program loop ensure lastBST value = current value first computed
    lastBst = bst;
  }
  else {
    if ((lastBst == 1 && bst == 0) || (retard == 1 && bstCount > 0)) {   //summer to winter clock goes back (misses 3600 pulse)
      retard = true;
      bstCount--; 
    }
    else if ((lastBst == 0 && bst == 1) || (advance == 1 && bstCount > 0)) {   //winter to summer clock goes forward (add 3600 pulses)
      advance = true;
      bstCount--;
    }
    else {
      bstCount = 3600;
      retard = false;
      advance = false;
    }
    lastBst = bst;
  }
*/
// ########################################### Brett 

// Man advance/retard

 if (digitalRead(retardSw) == LOW || (retard == 1 && bstCount > 0)) {   //summer to winter clock goes back (misses 3600 pulse)
      retard = true;
      bstCount--; 
    }
    else if (digitalRead(advanceSw) == LOW || (advance == 1 && bstCount > 0)) {   //winter to summer clock goes forward (add 3600 pulses)
      advance = true;
      bstCount--;
    }
    else {
      bstCount = 3600;
      retard = false;
      advance = false;
    }
  

// Man advance/retard




  if(retard || advance){
    setTextSize(2);
    if(bstCount == 3599){                           // if the count is 3599 process just started
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      setCursor(30,60);                             // set print position to start of line
      setTextColour(ILI9341_WHITE, ILI9341_BLACK);  // set text colour to White on Black 
      if(retard) tft.print(F("BST Retard count:"));
      else tft.print(F("BST Advance count:"));
    }
    setCursor(246,60);
    setTextColour(ILI9341_CYAN, ILI9341_BLACK);   // set text colour to Cyan on Black 
    if(bstCount < 1000) tft.print(F("0"));
    else if(bstCount < 100) tft.print(F("00"));
    else if(bstCount < 10) tft.print(F("000"));
    tft.print(bstCount);
  } else  displayMultiFunction();                   // display normal multifunction strip
  
  //*/

  // displays signal quality and sync status LED on TFT
  // display Sync status LED
  switch (DCF77_Clock::get_clock_state()) {
    case Clock::useless: fillRect(87,110,40,16,10565);; break;
    case Clock::dirty:   fillRect(87,110,40,16,ILI9341_RED);; break;
    case Clock::synced:  fillRect(87,110,40,16,ILI9341_GREEN);; break;
    case Clock::locked:  fillRect(87,110,40,16,ILI9341_YELLOW);; break;
  }

  //display signal quality as percentage  
  signalQual = DCF77_Clock::get_prediction_match();
  if(signalQual == 255 || signalQual == 0 ) signalQual = 00;
  else  signalQual = signalQual * 2;

  //display signal quality once a minute
  if(currentSecond == 1){
    if(signalQual > 99) setCursor(245,110);
    else if(signalQual > 9 && signalQual < 100 )  setCursor(257,110);
    else if(signalQual < 10)  setCursor(269,110);
    fillRect(240,107,40,20,ILI9341_BLACK);        //blank previous value
    setTextColour(ILI9341_GREEN, ILI9341_BLACK);
    tft.print(signalQual);
  }

  // chime control section
  setChimeMode();

  // run chime functions if enabled
  if(chimeEnabled == 1){
    quarterHourChime();
    hourChime();
  }

  // reset chime system 
  if (digitalRead(chimeBusy) == LOW){  //if the audio module has started set the run flag // was LOW
    chimeRunning = 1;
  }
  else if ((chimeRunning == 1) || (currentMinute == 1 || currentMinute == 16 || currentMinute == 31 || currentMinute == 46))
  //if the audio module has played, the run flag would be set so turn off LEDS and reset flag
  //force LEDS off and flag reset if not done within 1 minute (audio module failed to start)
  {
    fillRect(48,220,40,14,10565);           //draw chime 60 LED OFF
    fillRect(235,220,40,14,10565);          //draw chime 15 LED OFF
    chimeRunning = 0;
  }
  
  // miscellaneous stuff before end of <loop>    
  firstRun = false;  
  oneSecTick =!oneSecTick;
  
    
} // end of <loop>

//================================================================================================================
//
// FUNCTIONS after here
//
//================================================================================================================

//================================================================================================================
//
// displayTimeDate
//
// called from <loop>
//================================================================================================================
// displays time and date information on TFT display
void displayTimeDate(){
  //display current time
  setCursor(10,22);
  setTextSize(3);
  setTextColour(ILI9341_CYAN, ILI9341_BLACK);
  printTFT(currentHour);
  tft.print(F(":"));
  printTFT(currentMinute);
  tft.print(F(":"));
  printTFT(currentSecond);
  
  //display date & day of week
  setCursor(170,22);
  setTextColour(ILI9341_GREEN, ILI9341_BLACK);
  //textSize(3);
  
  if (dateDowCount > 5 ){
    printTFT(currentDay);
    tft.print(F("/"));
    printTFT(currentMonth);
    tft.print(F("/"));
    printTFT(currentyear);
  }
  else{
    switch (currentWeekday){
    case 1: tft.print(F("Mon ")); break;
    case 2: tft.print(F("Tue ")); break;
    case 3: tft.print(F("Wed ")); break;
    case 4: tft.print(F("Thu ")); break;
    case 5: tft.print(F("Fri ")); break;
    case 6: tft.print(F("Sat ")); break;
    case 7: tft.print(F("Sun ")); break;
    }

    tft.print(currentDay);
    if(currentDay == 1 || currentDay == 21 || currentDay == 31) tft.print(F("st"));
    else if (currentDay == 2 || currentDay == 22) tft.print(F("nd"));
    else if (currentDay == 3 || currentDay == 23) tft.print(F("rd"));
    else tft.print(F("th"));
    if(currentDay < 10) tft.print(F(" "));
  }
  if (dateDowCount > 9) dateDowCount = 0;
  dateDowCount ++;
         
}  // end of <displayTimeDate> return to <loop>


//================================================================================================================
//
// displayMultiFunction
//
// called from <loop>
//================================================================================================================
// displays the multifunction information strip under time and date display
void displayMultiFunction(){
  setTextSize(2);
  setCursor(10,60);                             // set print position to start of line
  setTextColour(ILI9341_WHITE, ILI9341_BLACK);  // set text colour to White on Black 

  switch(currentSecond){

    case 0:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      break;

    case 1:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      tft.print(F("  Multifunction display")); 
      break;

    case 5:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      break;
      
    case 6:
      tft.print(F("  DCF77 Regulator Clock"));
      break;

    case 11:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      tft.print(F(" Brett Oliver (2016) v1.6"));
      break;

    case 16:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      break;

    case 17:
      tft.print(F("Sync   "));
      setTextColour(ILI9341_CYAN, ILI9341_BLACK);
      printTFT(hourSync);
      tft.print(F(":"));
      printTFT(minuteSync);
      tft.print(F(":"));
      printTFT(secondSync);
      tft.print(F("  "));
      printTFT(daySync);
      tft.print(F("/"));
      printTFT(monthSync);
      tft.print(F("/"));
      printTFT(yearSync);
      break;                                      //exit routine having displayed

    case 25:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      break;
            
    case 26:
      tft.print(F("Seconds lost "));
      setTextColour(ILI9341_CYAN, ILI9341_BLACK);
      tft.print(missedCount);
      if (missedCount < 10){
        setTextColour(ILI9341_BLACK, ILI9341_BLACK);
        tft.print(F("9"));
      }
      setCursor(202,60);
      setTextColour(ILI9341_WHITE, ILI9341_BLACK);
      tft.print(F("gained "));
      setTextColour(ILI9341_CYAN, ILI9341_BLACK);
      tft.print(gainedCount);
      if (gainedCount < 10){
        setTextColour(ILI9341_BLACK, ILI9341_BLACK);
        tft.print(F("9"));
      }
      break;

    case 33:
      //display missed seconds if there are any
      if(missedCount != 0){
        fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
        tft.print(F("Lost   "));
        setTextColour(ILI9341_CYAN, ILI9341_BLACK);
        printTFT(hourMissed);
        tft.print(F(":"));
        printTFT(minuteMissed);
        tft.print(F(":"));
        printTFT(secondMissed);
        tft.print(F("  "));
        printTFT(dayMissed);
        tft.print(F("/"));
        printTFT(monthMissed);
        tft.print(F("/"));
        printTFT(yearMissed);
      }
      break;
     
    case 39:
      //display any gained seconds if missed seconds have been displayed
      if(gainedCount != 0){
        fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
        tft.print(F("Gained "));
        setTextColour(ILI9341_CYAN, ILI9341_BLACK);
        printTFT(hourGained);
        tft.print(F(":"));
        printTFT(minuteGained);
        tft.print(F(":"));
        printTFT(secondGained);
        tft.print(F("  "));
        printTFT(dayGained);
        tft.print(F("/"));
        printTFT(monthGained);
        tft.print(F("/"));
        printTFT(yearGained); 
      }
      break;

    case 45:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      break;

    case 46:
      tft.print(F(" Quartz freq "));
      setTextColour(ILI9341_CYAN, ILI9341_BLACK);
      tft.print(16000000L - Generic_1_kHz_Generator::read_adjustment());
      setTextColour(ILI9341_WHITE, ILI9341_BLACK);
      tft.print(F(" Hz")); 
      break;

    case 53:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      tft.print(F(" Quartz accuracy "));
      setTextColour(ILI9341_CYAN, ILI9341_BLACK);
      tft.print(DCF77_Frequency_Control::get_confirmed_precision());
      tft.print(F(".00"));
      setTextColour(ILI9341_WHITE, ILI9341_BLACK);
      tft.print(F(" Hz")); 
      break;
  }
} //end of <displayMultiFunction> return to <loop>


//================================================================================================================
//
// displayStatusLeds
//
// called from <loop>
//================================================================================================================
// displays status info on virtual leds
void displayStatusLeds(){
  //one second led
  if(oneSecTick) fillRect(251,162,40,14,ILI9341_BLUE);
  else {
    fillRect(251,162,40,14,10565);
  }
  //30 second led
  if(currentSecond == 00 || currentSecond == 30){
    fillRect(196,162,40,14,ILI9341_GREEN);
   digitalWrite(Pulse30sec, HIGH); // 30second clock pulse to sync Pong clock
  }
   else
   {
   digitalWrite(Pulse30sec, LOW); // 30second clock pulse to sync Pong clock
  }
  //1 minute led
  if(currentSecond == 00) fillRect(140,162,40,14,ILI9341_GREEN);
  
  //1 hour led
  if(currentMinute == 0 && currentSecond == 00) fillRect(82,162,40,14,ILI9341_YELLOW);
  
  //turn off other leds
  if(currentSecond == 1 || currentSecond == 31){
    fillRect(196,162,40,14,10565);
    fillRect(140,162,40,14,10565);
    fillRect(82,162,40,14,10565);
  }

} // end of <displayStatusLeds> return to <loop>


//================================================================================================================
//
// printTFT
//
// called from <loop>
//================================================================================================================
//utility function for digital clock display: prints leading 0
void printTFT(byte TFTdigits){
  if(TFTdigits < 10) tft.print(F("0"));
  tft.print(TFTdigits);
} //end of <printTFT> return to <loop>


//================================================================================================================
//
// TFT draw functions (a number of commonly used drawing functions)
//
//================================================================================================================
// straight horizontal lines
void drawFastHLine(int x, int y, int len, int colour){
  tft.drawFastHLine(x,y,len,colour);
}
// empty circles
void drawCircle(int x, int y, int rad, int colour){
  tft.drawCircle(x,y,rad,colour);
}
// filled circles
void fillCircle(int x, int y, int rad, int colour){
  tft.fillCircle(x,y,rad,colour);
}
// text size
void setTextSize(byte s){
  tft.setTextSize(s);
}
// text colour
void setTextColour(int fg, int bg){
  tft.setTextColor(fg,bg);
}
// print position
void setCursor(int x, int y){
  tft.setCursor(x,y);
}
// rectangles
void drawRect(int x, int y, int width,int height, int colour){
  tft.drawRect(x,y,width,height,colour);
}
// filled rectangles
void fillRect(int x, int y, int width, int height, int colour){
  tft.fillRect(x,y,width,height,colour);
}

// end of drawing functions

//================================================================================================================
//
// pulsesecCoil
//
// called from <loop>
//================================================================================================================
//steps seconds clock movement
void pulsesecCoil(){
    digitalWrite(clockCoil01a, coilStatusSec); 
  digitalWrite(clockCoil02b, !coilStatusSec);
  coilStatusSec = !coilStatusSec;
} //end of <coilPulse> return to <loop>





//================================================================================================================
//
// pulseCoil
//
// called from <loop>
//================================================================================================================
//steps clock movement
void pulseCoil(){
  digitalWrite(clockCoil01, coilStatus);
  digitalWrite(clockCoil02, !coilStatus);
  
  coilStatus = !coilStatus;
} //end of <coilPulse> return to <loop>


//================================================================================================================
//
// extraPulse
//
// called from <loop>
//================================================================================================================
//steps clock an extra pulse (uses A NOP command since delay does not work in this sketch)
void extraPulse(){
  //wait for approx 0.5 seconds (delay command seems to be disabled by the DCF library)
  for(int x = 0; x < 20 ; x++){
    for(int y = -32678; y < 32767; y++){
     NOP;
    }
  }
  digitalWrite(clockCoil01, coilStatus);
  digitalWrite(clockCoil02, !coilStatus);
  coilStatus = !coilStatus;
} //end of <extraPulse> return to <loop>


//================================================================================================================
//
// sendCommand
//
// called from <loop>
//================================================================================================================
// sends serial data to audio module JQ6500
void sendCommand(byte command, byte arg1, byte arg2)
{
  // Command structure
    /*
     * FORMAT
     * 0x7E - start
     * 0x02 - number of bytes to follow (in this ex its 2 inc termination) 
     * 0x12 - command byte
     * 0xEF - termination byte
     * COMMANDS
     * 0x0D - play
     * 0x0E - pause
     * 0x01 - next
     * 0x02 - prev
     * 0x03 - play file by index number (2 args. 1st high byte of index number, 2nd low byte of index number)
     * 0x0F - change folder (1 arg. 0x01 for next, 0x00 for previous)
     * 0x12 - play file by name (SD card version only)
     * 0x04 - vol up
     * 0x05 - vol dn
     * 0x06 - set vol (1 arg. hex value of decimal 0-30 1E = 30)
     * 0x07 - eq mode (1 arg. 0/1/2/3/4/5 (normal/pop/rock/jazz/classic/bass) byte value)
     * 0x11 - loop mode (1 arg. 0/1/2/3/4 (all/folder/one/ram?/one_stop) bye value)
     * 0x09 - source (1 arg. 0x01 = SD 0x04 = flash)
     * 0x0A - sleep
     * 0x0C - reset
     */
  
  // Most commands do not have arguments
  byte args = 0;
      
  // These ones do
  switch(command)
   {        
     case 0x03: args = 2; break;
     case 0x06: args = 1; break;
     case 0x07: args = 1; break;        
     case 0x09: args = 1; break;
     case 0x0F: args = 1; break;
     case 0x11: args = 1; break;
     case 0x12: args = 2; break;
   }

   // The device appears to send some sort of status information (namely "STOP" when it stops playing)
   // just discard this right before we send the command
   while(Serial.available()) Serial.read();
   // write commands to the serial port  
   Serial.write((byte)0x7E);
   Serial.write(2+args);
   Serial.write(command);
   if(args>=1) Serial.write(arg1);
   if(args==2) Serial.write(arg2);
   Serial.write((byte)0xEF);
} // end of <sendCommand> return to <loop>


//================================================================================================================
//
// setChimeMode
//
// called from <loop>
//================================================================================================================
// change chiming mode 
void setChimeMode(){
 if(digitalRead(chimeModeSw) == LOW)
  {
    if (chimeChanged == 0){
      chimeMode++;
      chimeChanged = 1;
      if (chimeMode > 2) chimeMode = 0; 
    }
  }
  else
  {
    chimeChanged = 0;
  } 

  if(chimeMode < 2){ //if chimeMode = 2 chimes are disabled
    // only chime if the mode is set to full or day (day is after 05:00 onwards and before upto 23:00)
    if((chimeMode == 0) || (currentHour >= 5 && currentHour <= 23)) chimeEnabled = 1;
    else{
      chimeEnabled = 0;
    }
  }
  else{
    chimeEnabled = 0;
  }

  //display chime mode on virtual LED

  switch(chimeMode){
    case 0:
      fillRect(140,220,40,14,ILI9341_GREEN);                      //draw chime LED FULL
      break;    
      
    case 1: 
      if(chimeEnabled == 0) fillRect(140,220,40,14,ILI9341_RED);  // draw chime LED DISABLED
      else fillRect(140,220,40,14,ILI9341_YELLOW);                // draw chime LED DAY
      break;        
        
    case 2: 
      fillRect(140,220,40,14,10565);                              // draw chime LED OFF
      break;             
  }        
  
  if(chimeMode == 1 && chimeEnabled == 0) fillRect(140,220,40,14,ILI9341_RED); // draw chime LED DISABLED

} // end of <setChimeMode> return to <loop>


//================================================================================================================
//
// quarterHourChime
//
// called from <loop>
//================================================================================================================
// chiming routine for 1/4. 1/2 and 3/4 Hr
void quarterHourChime()
{
  if ((currentMinute == 14 || currentMinute == 29 || currentMinute == 44) && currentSecond == 57){
    fillRect(235,220,40,14,ILI9341_RED);          //draw chime 15 LED ON
  }
  
  if ((currentMinute == 14 || currentMinute == 29 || currentMinute == 44) && currentSecond == 59){
    //load index variable with file index number
    switch(currentMinute){
      case 14: chimeIdx = 0x0d; break;
      case 29: chimeIdx = 0x0e; break;
      case 44: chimeIdx = 0x0f; break;
    }
    //send command sequence to audio module with calculated file index number
    sendCommand(0x03, 0x00, chimeIdx);
  }
} //end of quarterHourChime


//================================================================================================================
//
// hourChime
//
// called from <loop>
//================================================================================================================
// chiming routine for hourly chimes
void hourChime(){
   //if its getting near to start of the chime
   if (currentMinute == 59 && currentSecond == 45){
     fillRect(48,220,40,14,ILI9341_RED);     //draw chime 60 LED ON
   }

   //if its time for the chime to start (13 seconds to allow melody
   //to complete thus ensuring strikes start at the top of the hour)
   if (currentMinute ==  59 && currentSecond == 48){
     //calculate filenumber variable to ensure  results 1 - 12
     int chimeFile = currentHour + 1;
     if (chimeFile > 12) chimeFile = chimeFile - 12;
          
     //load chimeIdx variable with HEX value of chimeFile
     //this is the easiest way I can think of changing DEC to HEX 
     switch(chimeFile){
       case 1: chimeIdx = 0x01; break;
       case 2: chimeIdx = 0x02; break;
       case 3: chimeIdx = 0x03; break;
       case 4: chimeIdx = 0x04; break;
       case 5: chimeIdx = 0x05; break;
       case 6: chimeIdx = 0x06; break;
       case 7: chimeIdx = 0x07; break;
       case 8: chimeIdx = 0x08; break;
       case 9: chimeIdx = 0x09; break;
       case 10: chimeIdx = 0x0a; break;
       case 11: chimeIdx = 0x0b; break;
       case 12: chimeIdx = 0x0c; break;
     }
     //send command sequence to audio module with calculated file index number
     sendCommand(0x03, 0x00, chimeIdx);
   }
} //end of <hourChimes> return to <loop>




//======================================= DEBUG FUNCTIONS FOR TESTING ============================================

//================================================================================================================
//
// DEBUGMissedGained  DEBUG USE ONLY
//
// called from <loop> routine for testing only
//================================================================================================================
// prints missed \ gained information to serial port
void DEBUGMissedGained(){
  
  Serial.print(F("Secsmiss "));
  Serial.print(secsMiss);
  Serial.print(F(" Seconds "));
  Serial.print(currentSecond);
  Serial.print(F(" Last second "));
  Serial.println(lastSecond);
  Serial.print(F("Missed seconds "));
  Serial.print(missedCount);
  Serial.print(F(" "));
  Serial.print(hourMissed);
  Serial.print(F(":"));
  Serial.print(minuteMissed);
  Serial.print(F(":"));
  Serial.print(secondMissed);
  Serial.print(F(" "));
  Serial.print(dayMissed);
  Serial.print(F("/"));
  Serial.print(monthMissed);
  Serial.print(F("/"));
  Serial.println(yearMissed);
  Serial.print(F("Gained seconds "));
  Serial.print(gainedCount);
  Serial.print(F(" "));
  Serial.print(hourGained);
  Serial.print(F(":"));
  Serial.print(minuteGained);
  Serial.print(F(":"));
  Serial.print(secondGained);
  Serial.print(F(" "));
  Serial.print(dayGained);
  Serial.print(F("/"));
  Serial.print(monthGained);
  Serial.print(F("/"));
  Serial.println(yearGained);

//enable to display chime status
  /*
  Serial.println(" ");
  Serial.print(F("Chime Idle"));
  Serial.print(" ");
  Serial.print(digitalRead(chimeBusy));
  Serial.print(F(" \ "));
  Serial.print(F("Chime Running"));
  Serial.print(" ");
  Serial.print(chimeRunning);
  Serial.print(" \ ");
  Serial.print(F("Last Chime Index"));
  Serial.print(F(" "));
  Serial.println(chimeIdx);
  Serial.println(F(" "));
  */
  
} //end of <serialMissedGained>

//================================================================================================================
//
// DEBUGcounters  DEBUG USE ONLY
//
// called from <loop> routine for testing only
//================================================================================================================
// provides basic counting function for debug of display etc.
void DEBUGCounters(){
  // wait for next sec
  for(int x = 0; x < 20 ; x++){
    for(int y = -32678; y < 32767; y++){
     NOP;
    }
  }
  
  currentSecond ++;
  if (currentSecond > 59){
    currentSecond = 0;
    currentMinute ++;
  }

  if (currentMinute > 59){
    currentMinute = 0;
    currentHour ++;
  }

  if (currentHour > 23){
    currentHour = 0;
    currentDay ++;
    currentWeekday ++;
  }

  if ((currentDay == 0) || (currentDay > 31)) currentDay = 1;
  if ((currentWeekday == 0) || (currentWeekday > 7)) currentWeekday = 1;
  
} //end of <testSystem>

//================================================================================================================
//
// changeBST  DEBUG USE ONLY
//
// called from <loop> routine for testing only
//================================================================================================================
// use for advance \ retard test allows switch to change BST
/*
void changeBST(){
  if(digitalRead(bstSw) == LOW)
  {
    if (bstChanged == false){
      bst = !bst;
      bstChanged = true;
    }
  }
  else
  {
    bstChanged = false;
  }
} 
*/
//end of <changeBST>

//============================================== END OF LISTING ===================================================