Home    Pragotron Dial Backlight  Master Clock   LCD Master Clock    Kenley Weather

Section   TOP  Specs   Power   Arduino Pin Outs   Switches  PS100 mod  PJ42 Movement  Movement Operation   Sound Module  Add Sound Files H Bridge   Step Up Module   TFT  Speaker  Summer Advance  Winter Retard   DCF77 Repeater    Vero Board    Schematic  Arduino Code

 

 

 

 

Arduino Pragotron Master Clock &  Slave Driver

 

 

 

 

Many Pragotron clocks supplied from antique shops or architectural salvage dealers have had their original 1 minute drive movements removed and replaced by quartz movements. This is because they were designed to be driven off a Master Clock from a 12v to 60v supply.

Although they work perfectly well and keep reasonable time these quartz modified clocks lose their original look and feel as they now tick once a second and no longer have the animated step forward of the hands and soft "clunk" sound once every minute.

This circuit enables the original movement to be used, powered from an Arduino Microprocessor from 5 volts and has a modular design to keep the design simple and build time short.

below left 49cm Pragotron PJ42 mounted on a wall with the backlight controller mounted underneath                     below & right 44cm Pragotron C401 mounted in the other end of the same room

Both clocks are synchronized to the DCF77 "Atomic" Clock and will step forward on the minute in unison as they where originally designed to.

Pragotron PJ42                   

 

 

 

 

below  looped animation shows the restored clock stepping forward 1 minute at 00 seconds. Note the 5cm TFT display has been enlarged for clarity against the large 44cm Pragotron dial

 

 

 

 

 

 

Circuit Features/Functions

This clock circuit uses an Atmega 328 microprocessor to drive Pragotron type clock movements from a 5 volt supply.

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 and sound via a JQ6500 sound module controlled over the serial port.

It should also be able to drive other clock movements that require a 1 minute alternating pulse to drive them.

Pragotron movements have different drive voltages so a step up voltage module is used to set the correct voltage for the movement is use.

A DCF77 receiver module is used to synchronize time to the "Atomic" Clock in Germany and clock functions and controls are monitored on a 2.2" TFT display.

The circuit is built onto Vero Board and can be located remote from the clock movement that is being driven.

An off the shelf Arduino Uno can be used but will required a 16MHz quartz crystal added in place of it's built in 16MHz resonator.

Other Arduino boards can also be used but must use a quartz crystal for the microprocessor timings  as this clock design uses Udo Klein's DCF77 library.

See details here UNO Mod

Switches are mounted on the Vero Board to start Auto summer advance and also winter retard.

Full Westminster Chimes including volume are controlled by the Arduino and played through a JQ6500 sound module and chimes can be set to  On, Off and timed.

Battery backup is provided by 3 x 1.5v Alkaline batteries and will keep the clock running without chimes during power cuts.

This circuit also includes a DCF77 "repeater" and "multiplier" circuit and can provide DCF77 pulses at 5volts to other clocks.

This and the Westminster chimes cam be omitted if not required. 

Full details can be found in the sections below.

 

 

below completed Vero Board layout with all modules mounted and 1 min cycle of TFT display

 

 

 

 

 

 

Power Requirements

I use a common 12 volt power supply unit to drive many different types of clocks. This 12 volt supply is then stepped down locally at each clock with a 5 volt module to supply power for the control board and various modules. If you are running just this one clock then any regulated 5volt supply

will do as long as it can supply the current for your circuit use.

 

below my common power supply has 18 individually fused 12v circuits each with a 2A fuse. 9 of these are battery backed up.

Clock boards also have their own on board fuse.

The configuration of modules in my particular circuit requires approximately 300mA to run at max power. This is with a 24v movement connected and chimes at a volume loud enough for a single room without disturbing other rooms in the house.  If you are

driving higher voltage movements and or sounding chimes at very high volumes then current draw will go up. As can be seen from the table below normally with the display off, the clock not stepping and not chiming the clock will draw 59mA.

Once per minute for around 0.5sec while the clock advances 1 minute the current will rise to 174mA. If the chime is set then every 15 minutes for a few seconds as the Westminster Chime quarters are sounded the current will rise to 64mA to 174mA (depending on set volume).

Max current of upto 300mA is drawn on the hour for 0.5 seconds as the clock advances 1 minute and chimes out the hours.

I have fitted a 650mA fuse on the main board and also a thermal fuse in the clock movement as my common power supply can output many amps.

 

below measured current draw

  Current mAmps
Options Normal Clock stepping Chiming Low Chiming High Stepping & Chiming Low Vol Stepping & Chiming High Vol
Initial Sync Sound On Display On 85 NA NA NA NA NA
Initial Sync Sound On Display Off 75 NA NA NA NA NA
Initial Sync No Sound Display On 68 NA NA NA NA NA
Initial Sync No Sound Display Off 59 NA NA NA NA NA
Sync'd, Sound On Display On 85 200 90 200 212 300
Sync'd, Sound On Display Off 68 183 73 183 195 283
Syn'd No Sound Display Off 59 174 NA NA NA NA

 

 

 

 

 

 

 

 

 

Control Switches

The clock has 9 switches all mounted on the main circuit board

Switch Type   Function
Reset 1 way non locking press On   Reset Atemega 328 & TFT display
Chime Mode 1 way non locking press On   Selects Chime mode between On Green, On preset times only Yel and Off 
Advance 1 way non locking press On   Advances the clock by 1 hour
Retard 1 way non locking press On   Retards the clock by 1 hour
Cancel Adv/Rtd 1 way non locking press On   Cancels advance or retard
Motor Sync 1 way non locking press On   When first powered up or if the clock hands have been manually adjusted synchronises the motor with the H Bridge output
Volume- / Prev 1 way non locking press On   Press to play previous sample on sound module Press and hold while playing to lower volume
Play/Stop 1 way non locking press On   Press to play current sample on sound module Press again to stop playing
Volume+ / Next 1 way non locking press On   Press to play next sample on sound module Press and hold while playing to increase volume

 

below mouse over to show switch locations on main Vero board

 

 

 

 

 

 

 

 

 

 

 

 

 

Atmega 328 Pin connections

 

IC Pin IDE Pin Function
1 Reset Reset DCF77 Analyzer & TFT Display
2 0 Rx
3 1 Tx & Tx for sound module
4 2 Retard
5 3 Reset Advance/Retard
6 4 Chime Mode
7 NA 4.4v
8 NA Gnd
9 NA Xtal1
10 NA Xtal2
11 5 Advance
12 6 30 second pulse
13 7 Clock 01
14 8 Clock 02
15 9 TFT DC
16 10 TFT CS
17 11 MOSI
18 12 MISO
19 13 SCK
20 NA 4.4v
21 NA AREF
22 NA Gnd
23 A0 enable01
24 A1 DCF LED
25 A2 Motor Sync
26 A3 Spare
27 A4 DCF77 I/P
28 A5 Chime Busy

 

 

 

 

 

 

Removing the old quartz movement

To get to the old movement to remove it place the clock on it's face on a soft cloth to protect it from damage, see picture below.

Remove four bolts highlighted by yellow arrows and take out the four L shaped brackets and single hanging bracket on the top.

Carefully remove the dial with the quartz movement attached from the clock case.

The dial is very thin and is easily bent if handled incorrectly. First remove the minute hand by undoing the brass grub screw under the minute hand holding it to the minute shaft. See red arrow.

The hour hand should be a friction fit and can be gently pulled off. Now unscrew the collar holding the quartz movement to the dial and remove the movement.

On my clock the old Pragotron fixing bolts were filled down and glued over the fixing holes on the dial. Remove these bolts and discard.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Pragotron PS100 Movement Modification - adding a thermal fuse

I use a common power supply to drive my many clocks and these can deliver 8 amps of current. Each clock has it's own fuse but as a failsafe I have fitted a thermal fuse inside the PS100 movement in case the coil overheats.

To fit the fuse the case will need to be taken apart and this means drilling out the welded studs in the case  with the possibility of destroying your movement.

If you don't want/need to fit the thermal fuse then just ignore the following.

 

 

The PS100 movement case measures 60mm x 80mm x 20mm the terminals at the bottom of the movement are 10mm long and the minute shaft protrudes around 15mm

from the face of the case. The hand adjuster a further 5mm from the back of the movement.

 

 

 

The case is not designed to be opened and is secured with welded plastic studs in the four corner holes

seen from the the front of the case. To take the movement apart the plastic welds are drilled out using a 4mm drill.

The top and bottom of the case can then be carefully prised apart.

 

 

 

 

Once the case is apart you find the movement is split in two.

Below, the hour drive cog and shaft with adjustment cog along with the drive coil, rotor, stator and connector terminals.

 

 

 

Below the top half of the movement containing the minute shaft drive and rotor/adjustment cog.

Behind the minute shaft cog is a sprung metal plate that pushes the minute cog away from the plate ensuring engagement with the plastic hour drive cog.

 

 

 

Below thermal fuse fixed to the drive coil with a twisted length of wire. In the event of the drive coil overheating the fuse will trip disconnecting the coil.

The fuse wire (slate white) is connected in series to one of the brown coil wires near the terminal block.

 

 

Re-fitting the case is a bit tricky.

Put the spring plate under minute shaft cog and press in under the rotor cog and it should hold in place.

With a finger holding the hour shaft in place from the outside gently press the two parts together and with a bit of wiggling they should click into place.

Turn the hand adjuster to ensure the movement is free.

The two case parts can then be secured with plastic tape or some hot melt glue.

 

 

 

 

 

 

Pragotron PS100 Lavet Type Stepper Motor Movement Operation

 

This movement uses the Lavet type stepping motor action.

Minute hand shaft, attached cog and rotor drive cog removed for clarity.

The rotor is a permanent magnet with it's opposing poles shown by the red and green dots.

Start:

With the stator coil non energised  the stators have no magnetic field and the rotor will be stationary in it's last energised position.

When the 1 minute pulse of 24volts is applied with an opposite polarity to the previous pulse

the stators become magnetised with their North and South matching the rotor poles.

The rotor is then forced around clockwise and stops with it's North and South poles opposite the North and South poles of the stator.

The rotation of the rotor drives the movement forward 1 minute.

The 24 volt 1 minute pulse is then removed also removing the stator magnetic field. The rotor stays where is was.

 

The next minute the another 1 minute pulse is received with the opposite polarity to the previous pulse.

The stator is then energised with it's poles reversed matching the polarity of the stationary rotor.

The rotor is again forced around clockwise and stops with it's North and South poles opposite the North and South poles of the stator.

The rotation of the rotor drives the movement forward 1 minute.

The 24 volt 1 minute pulse is then removed also removing the stator magnetic field.

The rotor stays where is was and it has now turned 1 full revolution ready for the whole process to repeat from the start: above.

 

 

 

 

 

Pragotron PJ42. This large clock has a 42cm diameter dial and measures nearly half a meter total diameter and is 11cm deep. Case is of black Bakelite with a concave plastic dial.

To give an indication of the size of this clock I have shown it placed on a Tub chair for comparison.

 

Below, to backlight this clock dial see Pragotron Dial backlight controller

 

 

 

 

 

 

Modules

JQ6500 Sound Module

 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.

 

below example of clock quarter & full chimes

 

 

below clock stepping to 09:00hrs and chiming the hours

 

 

 

 

 

 

Dual H Bridge Module

I have connected my clock movement to connection 02.

Important remove link from the onboard regulator as shown as this is not used.

5v is derived from the Arduino power supply to the screw terminal marked "5v Arduino"

Ground is common ground and 24v is the stepped up supply from the step up module

Remove the link from ENA and ENB as the H bridge is enabled from the Arduino.

I have connected ENB to my Arduino as I am using output 2. Whenever ENB is set to 5v from the Arduino whatever 5v voltage polarity set on IN3 & IN4 pins

from the Arduino is sent out at 24v from the H bridge module to step the clock on 1 minute.

 



 

 

H bridge Module specifications

L298N as main chip
Low heat,outstanding anti-interference performance.
High working power to 46v,large current can reach 3A MAX and continue current is 2A, power to 25w.
Can drive one 2-phase stepper motor, one 4-phase stepper motor or two DC motors.
Built-in 78M05,get power from drive power,however, when drive power over 12V, please use the external 5v power as power supply.
Large capacity filter capacitance,afterflow protection diode, more stable and reliable.
Specification:

Double H bridge drive
Chip: L298N (ST NEW)
Logical voltage: 5V
Drive voltage: 5V-35V
Logical current: 0mA-36mA
Drive current: 2A(MAX single bridge)
Max power: 25W
Size:43 x 43 x 26mm(LxWxH)
Note:

This module has a built-in 5v power supply, when the driving voltage is 7v-35v, this supply is suitable for power supply
DO NOT input voltage to +5v supply interface, however leading out 5v for external use is available.
When ENA enable IN1 IN2 control OUT1 OUT2
When ENB enable IN3 IN4 control OUT3 OUT4

 

 

 

 

 

 

 

Voltage Step up Module

This module takes the 5 volts from the Arduino power rail and steps it up to the 24volts required to drive the Pragotron movement coil via the H Bridge module.

The exact step up voltage is adjusted by turning the preset resistor.

 

 

 

 

 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.

 

 

The display is split into 5 sections.

The first shows the current decoded time and date from the DCF77 transmitter

 

The 2nd row  shows the following information:

Makers name and S/W version

Time first synchronised. This will show you the length of time the clock has been running.

Quartz accuracy and Quartz frequency. The quartz crystal is only used when the DCF77 signal is lost and while the clock is in sync the quartz crystal in continuously "tuned" to the highly accurate DF77 signal.

This row will show the actual "tuned" frequency of the quartz crystal along with the accuracy in Htz with 1Htz being the best.

Seconds lost or gained. This will show the number of times the clock has been out of sync with the DCF77 clock and auto corrected for more and less than 1 second.

This is useful if the clock is driving 1 second slave clocks as they could be out of sync if the auto correction fails. The auto correction adds or removes a 1 second pulse to maintain sync of the 1 second slaves.

You would expect a 1 second loss to be registered for example when a leap second is injected.

 

The 3rd row shows the DCF77 signal status and quality

 

The 4th row has the BST (british summer time) indicator and slave pulse output monitor

 

The 5th row is the chime status monitor. Chime is green when chimes are always on, yellow when on timer and off when chimes are off.

 

below TFT display over 1 minute in normal clock mode

 

 

 

 

 

 

 

Speaker

The JQ6500 sound module can directly drive an 8Ω speaker of up to 3watts.

I have mounted a small speaker it is a piece of plywood and added a Pragotron Logo with Lazertrans inkjet paper.

The speaker is then mounted on the wall behind the clock to sound the chimes. The speaker can be mounted remote from the clock if required.

 

 

 

Summer Advance

To correct the clock for summer time (advance by 1 hour) just press the advance button on the main control panel. The Pragotron clock will start to advance and the display will show "BST advance count" and will count down as the clock advances an hour.

During summer advance if the current seconds get to 0 the advance count will stop for 1 count to ensure the clock advances exactly 1 hour.

Once the advance count reaches 0 the clock stops advancing and the TFT display reverts to normal.

 

The video below shows the clock advancing with the enlarged TFT display on the left showing current time and advance count.

 

 

 

 

 

 

 

Winter Retard

These clock movements can not be stepped backwards so they are retarded by stopping the drive pulses for 1 hour.

To correct for winter time (retard by 1 hour) press the retard button on the main control panel. The TFT display will show "BST Retard count:60" and the Pragotron clock will stop.

As each minute passes the Retard count will decrease by 1 until it reaches 0 when the Pragotron Clock will restart and the TFT display reverts to normal . The clock will be exactly 1 hour retarded.

 

The time-lapse animation below speeded up 180 times shows the retard count decreasing to 0 as an hour of current time passes.

 

 

 

 

 

DCF77 Signal Repeater/Multiplier

This is optional and is only required is you want to feed many DCF77 clocks from 1 DCF77 signal and is mounted on the main vero board.

The circuit uses a 6 way inverter. The 3.3v DCF77 signal from the receiver comes into the 1st inverter

and is inverted and changed to 5v. This is then fed to the remaining 5 inverter inputs. The output from each of these 5 inverters is then exactly the same as the

incoming 3.3v DCF77 signal but is at 5v. Each output can be then sent to different DCF77 clocks.

 

 

 

 

Circuit Board

Construction

The circuit is designed around prebuilt modules and is very easy to build onto Vero Board.  The board below shows all components mounted ready for final wiring.

The white temporary labelling strips helps position wires and components and is removed once they are all in place. The modules plug into the installed headers on the board once wiring is complete.

 

 

 

 

 

 

 

 

 

below actual Vero Board showing wiring (mouse over for module labels) note 3.3v PSU module is not plugged in as I use the 3.3v supply from my Regulator clock housed in the same 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.

 

 

 

 

 

Arduino Code

Download Code V2.4

A standard Arduino Uno can be used but must have a Quartz Crystal added instead of a resonator to work with Udo Klein's DCF77 Library. See details here UNO Mod

 

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

Download here

 

 

 

 

/*
  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
  v2.4 remove current second in retard and advamce & line 674 bstcount leading 0s changed
  v2_3 correct display error on retard
  v2_2 cleaned up version of v2.1
  v2.1 changed way mins step by using same as motorsync. Mins now step at 0 secs with no polarity setting at 55 seconds retard count set to 60 advance also 60
  v2.0 Motor reversal switch added to sync motor "motorSync"
  v1.9 clean out old redundant code eg 2nd quartz output summer advance working set to 63 now steps 1 hour 1 min then waits 1 min
  v1.8 retard count working needs full test
  v1.7 summer advance set to 61 but missed 2 pulses
  v1.6 summer winter correction
  v1.5 extraPulse renamed enablePulse
  v1_4 use NOP for short delay
  v1_3 1 sec output every min
  v1_2 add pulse time 
  v1_1 
  v1_0 as TFT clock
  line 1158 changed to 47 to sync chime
 
   Chime startup changed to 06:00 to 23:00 (chime will start from first 1/4 after 05:00hors)
   Chime default set to off (chimemode set to 2)
  30 second clock pulse added for driving 30 second slave clocks
  correction of 2nd coil output from v1.2
  
 
  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
const int motorSync   = A2;                   // reverses motor polarity


//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 enableCoil01 = A0;  // when high (A0) ENA on H bridge is enabled so polarity on clockCoil01 and clockCoil02 is sent to clock movement
unsigned long previousMillis = 0;        // will store last time H bridge was enabled was updated (used for timing of enable pulse)
const long interval = 200;           // interval of enable pulse (milliseconds)


//================================================================================================================
// 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          = 60; // count for bst advance\retard (60 mins 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(motorSync, 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(enableCoil01, 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 v2.4"));
  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 v2.4"));
  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 30 sec clocks are off at startup
  digitalWrite(Pulse30sec, HIGH); // 30second clock pulse to drive slave clock via monostable
  //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);
  //*/

 // Reverse motor polarity used to set motor in sync with the Arduino driver press until motor steps. Used after first power up or manually adjusting hands.
 if (digitalRead (motorSync) == LOW && currentSecond != 00)
 {
  pulseCoil();
 enablePulse();
 //Serial.println("motor reverse switch");
  }

 /*
  Serial.println(currentSecond);
  Serial.print("bstCount");
  Serial.println(bstCount);
 Serial.print("advance");
 Serial.println(advance);
  Serial.print("retard");
 Serial.println(retard);
  */
  /*
  //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);

Serial.print(F("previousMillis"));
 Serial.println(previousMillis);
 */
 /*
Serial.print(F("clockCoil01"));
 Serial.println(clockCoil01);
  Serial.print(F("enableCoil01"));
 Serial.println(enableCoil01);

 */
 
  // 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 = 60;                      //reset BST advance\retard system was 3600
    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;
    
  } 
   /* Removed as 1 second coil pulse not required
   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
  //{
   
   // if(advance) pulsesecCoil(); // advance every second if clocks are going forward 
 // }

      if (advance == true && currentSecond !=00) 
      {
      pulseCoil(); // advance every second if clocks are going forward 
      enablePulse();
      //Serial.println("pulseCoil");
  
      }


      
  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 (extraPulse every .5 second not required for 1 min clocks)
    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 && bstCount == 60)
 {
  bstCount = 60; // if in retard mode bstCount can be set here to a diff number to advance count
 }
*/
 if (digitalRead(retardSw) == LOW || (retard == 1 && bstCount > 0)) {   //summer to winter clock goes back (misses 60 pulse)
     retard = true;
     // Blanks line then shows "BST Retard count:60" while waiting on count to get to 59 on 0 seconds.
     setTextSize(2);
      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 
      tft.print(F("BST Retard count:"));
      
      if (currentSecond == 00)
      {
        
      bstCount--; 
      }
      
    }
    else if (digitalRead(advanceSw) == LOW || (advance == 1 && bstCount > 0)) {   //winter to summer clock goes forward (add 60 pulses)
      advance = true;
      
     
      if (currentSecond != 00)
      {
      bstCount--; 
      }
      
    }
    else {
      bstCount = 60;// changed to 60 from 3600 from minute pulse
      retard = false;
      advance = false;
      digitalWrite(enableCoil01, LOW);// enable coil output turned off as clock not advancing
    }
  

// End Man advance/retard




  if(retard || advance){
    setTextSize(2);
    if(bstCount == 59){                           // if the count is 59 process just started  (was 3599 for 1 second clocks)
      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 < 10) 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"));
      tft.print(F(" Pragotron Master Clock"));
      break;

    case 11:
      fillRect(0,60,320,16,ILI9341_BLACK);          // blank the line
      tft.print(F(" Brett Oliver (2016) v2.4"));
      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, LOW); // 30second clock pulse to drive slave clock via monostable
  }
   else
   {
   digitalWrite(Pulse30sec, HIGH); // 30second clock pulse to drive slave clock via monostable
  }
  //1 minute led and enable pulse length
  
  if(currentSecond == 00)
  {
   
    fillRect(140,162,40,14,ILI9341_GREEN);
    
       } 

   if(currentSecond == 00 && retard == false)
  {
   pulseCoil();
   enablePulse(); // Uses NOP timer to pulse movement at 0 seconds as long as clock not in retard mode
    
    
       }   
  
  
  
  //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







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

// extraPulse not required for 1 minute clock only for 1 second clocks
//================================================================================================================
//
// enablePulse
//
// called from <loop>
//================================================================================================================
//steps clock an extra pulse (uses A NOP command since delay does not work in this sketch)
void enablePulse(){
  //wait for approx 0.5 seconds (delay command seems to be disabled by the DCF library)
  digitalWrite(enableCoil01, HIGH);// enable coil output
  for(int x = 0; x < 10 ; x++){ // x was20
    for(int y = -32678; y < 32767; y++){
     NOP;
    }
  }
  digitalWrite(enableCoil01, LOW);
  //digitalWrite(clockCoil01, coilStatus);
 // digitalWrite(clockCoil02, !coilStatus);
 // coilStatus = !coilStatus;
} //end of <enablePulse> 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 ===================================================