
What's not to like about the idea? A GPS module is the perfect companion to the humble clock and ensures the time is always accurate and never needs adjusting (unless you live in an area that has daylight savings).
Better still, the GPS module does not require a "lock" to multiple satellites - just one or more. "I want to build one!" I hear you say - well read on...
Strings are continuously sent from a GPS device and are formatted in easy-to-use NMEA standard. Different NMEA sentences have different fields of information. For this project, I don't care where the clock is positioned on the earth, nor do I have any concern for its altitude or the date. I just want the time information.
Before you throw this project in the "too hard basket", it might be handy to know that the developer of Swordfish (David Barker) created a module which automatically retrieves NMEA sentences (via the use of interrupts). The module can be found in the User Module Pack and is called "NMEA.bas". Here's an example of filtering all unwanted data, retrieving only time information:
Dim Time as String(12)
Sub GetGPSTime()
If NMEA.GetItem(NMEAItem) And NMEAItem.Valid Then
NMEA.GetField(NMEAItem,0,Field)
If Field = "$GPRMC" Then
NMEA.GetField(NMEAItem, 1, Field)
Time = Field
EndIf
EndIf
End Sub
I had LOTS of issues with the NMEA.bas library stomping all over shared RAM variables, and decided to create my own module rather than debug Davids. It's versatile, and should work with any GPS module. It is called NMEA2.bas and full use/download links can be found in this article.

It really doesn't matter which GPS module you use. I am using a LS20031 GPS Module as I have one handy. For ease of use, selecting a 5V tolerant device would probably be the way to go. Mine is 3.3 volts, so I had to work around that feature.
Another aspect to keep in mind is that GPS signals are quite hard to receive indoors. Some modules simply do not offer the gain required. The LS20031 was able to consistently "lock-on" anywhere in my house, while my TomTom was only able to near windows.
With that in mind, most GPS modules have a built in RTC and will continue to output RMC sentences (which contain time information) regardless of satellite reception. If that's your case, then you will need to move the clock near a window every few months to sync the time.
Next on the list of things with a clock is a legible display. I've made a couple of clock iterations over time, and found the price of large segment displays to be a little hefty. And then I stumbled onto the 4" Segment Displays from Sure Electronics. $16 for a board with two 4" displays and all the necessary driving components was an absolute steal.

The above image does not give them the justice... They are physically 4 inches from bottom to top of the segment. The whole board is a little over 5.5 inches in height.
Myself and others found the display hard to read from a distance due to the white segments that were not lit (even when powered by 12 volts). I resolved this by masking the displays with a white vinyl (sticker) - that way only the lit segments would be visible and there's much less information to interpret. Here's a before photo of the segments:
And an after photo with the vinyl (sticker) applied:
Interfacing with the displays was a little interesting. Datasheet suggests how-to send data to the displays, though I had several issues with daisy chaining them. I tried a few different variations, and ended up settling with controlling each set of two separately. When (and if) I refine the daisy chaining, I will update this article! (edit: I have had some great guidance from Jerry in the Digital DIY forums and will make some changes in the near future)
Keeping in mind I am using a 3.3V GPS Module, so it only makes sense to drive the PIC at 3.3V as well. I breadboarded the project to ensure the 4" Segment Displays were 3.3V tolerant - they were.
The program is heavily commented and should be easy to follow. Feel free to post comments with any questions.
{ ***************************************************************************** * Name : GPS Clock.bas (project number 0049) * * Author : Graham Mitchell * * Notice : Copyright (c) 2010 Graham Mitchell 2010 * * : All Rights Reserved * * Date : 26/07/2010 * * : * * Version : 1.0 * * Notes : A GPS clock... There's too many benefits to not make one. The * * : time is ALWAYS accurate, and there's no need to ever make * * : adjustments. * * : * * : The initial release interfaces with a LS20031 GPS Module, * * : although, any GPS module could be used. * * : * * : NMEA sentences are monitored until a valid RMC sentence is * * : received. Time information is extracted and converted from a * * : string to decimal numbers. * * : * * : Time information is then advanced depending on the users time * * : settings. Advancements work in 15 minute increments by * * : holding the "AdjTime" button. User settings are saved to the * * : EEPROM in case of power failure. * * : * * Version : 1.1 * * : LOTS of changes.. NMEA.bas was stomping all over my variables * * : so I have written a custom interrupt to grab time fields. * * : * * : With the new found flexibility, time adjustments are handled * * : by single button presses + button debounces controlled by TMR2.* * : * * : Probably the best change has been the addition of a custom * * : library for the DE-DP004 Sure Electronics display boards. More * * : information regarding the library can be found in the module * * : comments * * : * * : Time advancements have been changed to 1 hour for ease of * * : configuration - you can change this to what ever suite you * * : * * : EEPROM is preloaded with EST time zone adjustment (+10 hours) * * : * * Version : 1.2 * * : Fixed a bug which determines end of month roll-over events * * : during time advancement. * * : * * : Added some additional commenting regarding TMR2. * * : * * : Changed the time/date display ratio to 5.25 seconds for time, * * : and 2 seconds for date. * * : * ***************************************************************************** } // define device, clock and disable MCLRE Device = 18F2520 Clock = 32 Config MCLRE = Off #option DE_Data = PORTC.3 #option DE_CLK = PORTC.5 #option DE_DIMM = PORTC.2 #option NMEA_BUFFER_SIZE = 63 Include "InternalOscillator.bas" // this module configures the internal oscillator to operate at 32Mhz from the get-go Include "USART.bas" // the USART module is required to configure the baud rate Include "NMEA2.bas" Include "EEPROM.bas" // EEPROM module for saving settings Include "UTILS.bas" // the utils module is used for the function "Utils.Digit" Include "DE_DP004.bas" // user module for the Sure Electronics DE-DP004 displays // preload EEPROM with EST time adjustment EEPROM = ($AA,$00,$00,$0A) Dim Time As DE_DP004.TTime, strTime As String(12), strDate As String(7), LastSecond As Byte, DecimalOn As Boolean // TMR2 variables Dim TMR2IE As PIE1.1, TMR2IF As PIR1.1, TMR2ON As T2CON.2, TMR2IP As IPR1.1, ButtonDebounce As Word, Debounce As Byte, mS As Word Const mS_Inc = 8, DebounceDelay = 50 Const DaysInMonth(12) As Byte = (31,28,31,30,31,30,31,31,30,31,30,31) // time adjust structure Structure TAdjust Hours As Byte Minutes As Word End Structure Public Dim Adj As TAdjust // date structure Structure TDate Day As Byte Month As Word End Structure Public Dim Date As TDate // define I/Os Dim AdjTime As PORTB.0 // save the user adjustment settings to EEPROM Sub SaveEEPROMSettings() EE.Write(0,$AA,Adj.Minutes,Adj.Hours) End Sub // load user adjustment settings from EEPROM. Should no information be found, then it loads zero for both minutes and hours. Sub LoadEEPROMSettings() Dim tmpByte As Byte EE.Read(0,tmpByte) If tmpByte <> $AA Then EE.Write(0,$AA,$00,$00,$00) EndIf EE.Read(1,Adj.Minutes,Adj.Hours) End Sub // convert a single ascii character to decimal Function CharToDec(ByVal pChar As Byte) As Byte Result = pChar - 48 End Function // check if the button "AdjTime" is pressed and increment adjustment registers as necessary Sub CheckTimeAdjust() If Adj.Hours = 10 Then Adj.Hours = 11 Else Adj.Hours = 10 EndIf SaveEEPROMSettings() End Sub // capture the last day of month roll-over, and correct Sub ValidateTheDate() Dim LastDay As Byte LastDay = DaysInMonth(Date.Month - 1) If Date.Day > LastDay Then Date.Day = Date.Day - LastDay Date.Month = Date.Month + 1 EndIf End Sub // advance the time based on the adjustment registers Sub AdjustTime() Dim ValidateDate As Boolean ValidateDate = false Time.Minutes = Time.Minutes + Adj.Minutes Time.Hours = Time.Hours + Adj.Hours While Time.Minutes > 59 Time.Minutes = Time.Minutes - 60 Time.Hours = Time.Hours + 1 Wend While Time.Hours > 23 Time.Hours = Time.Hours - 24 Date.Day = Date.Day + 1 ValidateDate = True Wend If ValidateDate Then ValidateTheDate() EndIf End Sub // convert the date from string to decimal Sub ConvertDate() Date.Day = CharToDec(strDate(1)) + (CharToDec(strDate(0))*10) Date.Month = CharToDec(strDate(3)) + (CharToDec(strDate(2))*10) End Sub // update the segment displays to show HHMM Sub DisplayTime() If Time.Seconds <> LastSecond Then LastSecond = Time.Seconds DecimalOn = Not DecimalOn EndIf DE_DP004.Write(Time, DecimalOn) End Sub // update the segment displays to show DDMM Sub DisplayDate() DE_DP004.Write(Word((Date.Day * 100) + Date.Month),false,true,false,false) End Sub // convert the passed string to decimal time Sub StringToTime(ByRef pString As String) Time.Seconds = (CharToDec(pString(4))*10) + CharToDec(pString(5)) Time.Minutes = (CharToDec(pString(2))*10) + CharToDec(pString(3)) Time.Hours = (CharToDec(pString(0))*10) + CharToDec(pString(1)) End Sub // checks for new NMEA data Function CheckForNMEA() As Boolean Result = NMEA.NewItem End Function // extracts the time field from NMEA sentence Sub GetTimeDate() NMEA2.GetField(1,strTime) NMEA2.GetField(9,strDate) NMEA.NewItem = False ConvertDate() // convert the date from string to decimal StringToTime(strTime) // convert the time from string to decimal End Sub // TMR2 interrupt - handles adjust time button debounces (low priority interrupt) Interrupt TMR2_Interrupt(1) If TMR2IF = 1 Then // check if the interrupt was from TMR2 TMR2IF = 0 // clear TMR2 interrupt flag mS = mS + mS_Inc If mS >= 6250 Then mS = 0 EndIf If Debounce = 1 Then // check to see if debounce is enabled If ButtonDebounce > DebounceDelay Then // check if debounce time has elapsed If AdjTime = 1 Then // check if button has been depressed Debounce = 0 // clear the debounce flag EndIf Else ButtonDebounce = ButtonDebounce + mS_Inc // increment button debounce variable EndIf EndIf EndIf End Interrupt // initialise TMR2 for 8-msec interrupts (32-MHz clock) Private Sub Initialise_TMR2() TMR2ON = 0 // disable TMR2 TMR2IE = 0 // turn off TMR2 interrupts PR2 = 249 // TMR2 Period register PR2 T2CON = %01111011 // T2CON 0:1 = Prescale // 00 = Prescaler is 1:1 // 01 = Prescaler is 1:4 // 1x = Prescaler is 1:16 // 3:6 = Postscale // 0000 = 1:1 postscale // 0001 = 1:2 postscale // 0010 = 1:3 postscale... // 1111 = 1:16 postscale Debounce = 0 // clear debounce flag TMR2 = 0 // reset TMR2 Value mS = 0 // reset mS register TMR2IP = 0 // set TMR interrupt priority LOW TMR2IE = 1 // enable TMR2 interrupts TMR2ON = 1 // enable TMR2 to increment Enable(TMR2_Interrupt) // enable handler End Sub // initialise the program/PIC Sub InitialisePIC() // init variables Time.Hours = 0 Time.Minutes = 0 Time.Seconds = 0 LastSecond = 255 DecimalOn = True // enable weak pullups INTCON2.7 = 0 Input(AdjTime) // load saved settings (if they exist) LoadEEPROMSettings() // initialise NMEA module USART.SetBaudrate(br38400) NMEA2.Initialise // initialise TMR2 Initialise_TMR2() End Sub // main program start... InitialisePIC() // main program loop While True If CheckForNMEA Then // check if new data has been received GetTimeDate() // extract the time field from RMC sentence AdjustTime() // adjust new time with user settings If mS < 4250 Then DisplayTime() // display the adjusted time Else DisplayDate() // display the adjusted date EndIf EndIf If AdjTime = 0 And Debounce = 0 Then // check if adjust time is pressed (and debounce is disabled) CheckTimeAdjust() // handle time adjustments ButtonDebounce = 0 // reset the button debounce variable Debounce = 1 // enable the debounce handle EndIf Wend
The project is really quite simple between the schematic and the source code. I made a small wooden box to house the equipment. Here are some completed photo's of the project:
The clock now lives at work where the old clock used to be (it was horrible for keeping the time!).![]()