GPS Clock

A simple desk clock that gets extremely accurate time from GPS

Similar projects worth following
GPS is best known as a ubiquitous, accurate positioning system (obvious from the name), but the way it actually works requires distributing hyper-accurate time information. This makes it possible (and, actually, pretty easy) to make a clock that you never have to set as long as it gets good GPS reception.

A lot of folks on the Internet have made GPS clocks by simply parsing the NMEA data from the receiver. But getting the full accuracy GPS can provide requires also making use of the PPS signal from the receiver. And if you're going to bother with GPS, why not shoot for maximal accuracy?

This clock achieves an accuracy of no worse than 200 µs away from GPS time.

GPS time information is often precise down to the tens of nano-seconds. But if all you want to do is display time for humans, you probably would be satisfied with 4 orders of magnitude less precision, which is what this design achieves - 125 µs accuracy (with v5 hardware) with 100 ms granularity.

Over the course of the project, the clock has undergone a number of design revisions. The original design paired an ATTiny841 with a MAX6951 display driver. The 6951 connects to up to 8 seven-segment common cathode LED display digits (with decimal). This project is going to use 7 of them (6 large and 1 small for the 10th of a second digit), plus two LEDs for AM and PM, 4 for two colons between the 3 large groups, and a FIX LED from the GPS module. There are also two pushbuttons for parameter setting (timezone, DST config and display brightness).

The GPS module has a bidirectional UART and a PPS output. What complicates things just slightly is that the NMEA timecodes on the serial port describe the current second, meaning they happen just after the PPS signal. The serial codes aren't precise enough to do more than name the current second. Actual timing needs to come from the PPS signal. So the serial data needs to have a second added to it, and the result is stored in a buffer in RAM. The PPS interrupt transfers that buffer to the display chip. For tenths of a second, a timer in the controller is used for interpolation. The correct counting rate is determined by counting how many timer ticks occur between adjacent PPS interrupts and dividing that by 10. The timer runs from the controller's RC oscillator, configured for 8 MHz, with a divide-by-8 prescale - so approximately 1 MHz. The controller has interrupt handlers for timer overflow and input capture (for the PPS). The overflow interrupt increments a high order word, allowing us to keep a 32 bit counter. Even at a 1 MHz counting rate, it takes more than an hour to overflow a 32 bit counter, and that gives us microsecond measurement granularity. The RC oscillator's short term stability should be good enough for the purpose, and its stability beyond 2 seconds is mooted by the way it's being used.

The 6951 chip communicates with SPI. It requires a chip select line to be driven low and then 16 bits of data is clocked in. The write format is 8 bits of address and 8 bits of data. The chip has a bunch of registers inside that allow the display to be fully configured and even dimmed. The chip has a resistor that's used to configure the chip for constant-current for each LED segment to insure correct brightness. The chip's maximum SPI clock speed is over 20 MHz, and the highest SPI rate we can use with an 8 MHz ATTiny is 4 MHz, so we can run it flat out without worry. Our accuracy claim (~200 µs) stems from the fact that a full display update over SPI takes approximately 70 µs start to finish, and that happens in the PPS capture interrupt handler once a second that takes around 100 µs before the display updates commence (we do go to the trouble of updating the least significant digits first so that hopefully most of the time the only actual changes take place much faster). The accuracy of the tenth updates is likely much better, but keep in mind that their timing is estimated via interpolation, so that's based on the stability of the 8 MHz RC oscillator in the controller over τ 2s.

The brains of the clock is an ATTiny841, simply because I have a bunch of them and they have a UART (actually, two). It runs from the internal 8 MHz oscillator. The entire circuit runs from a 3.3 volt supply, so 12 MHz is the maximum frequency that could be used, but that would require adding an external crystal for no other reason (and rearranging the pins to allow the crystal to be connected).

The GPS module is the Skytraq Venus838LPx-T timing module. The board has an edge-mount SMA connector for an external antenna, and passes 5v (or 3.3v selectable with a solder jumper) active...

Read more »

Adobe Portable Document Format - 87.25 kB - 12/12/2021 at 18:32


sch - 555.97 kB - 12/12/2021 at 18:32


brd - 172.46 kB - 12/12/2021 at 18:32


Adobe Portable Document Format - 93.29 kB - 11/03/2019 at 06:19


sch - 587.64 kB - 11/03/2019 at 06:20


View all 9 files

  • 2 × 5MM TH LED
  • 4 × 3MM TH LED
  • 1 × 0805 SMD LED
  • 1 × 150Ω 0805 resistor
  • 1 × 330Ω 0805 resistor

View all 40 components

  • Changes coming

    Nick Sayer12/05/2021 at 05:51 0 comments

    The default baud rate for the serial I/O for the PX1100T is 115,200 baud. Which makes sense given how much more information gets sent given the quad constellation support. That means there has to be a #define in the code to switch from one module to the other.

    In addition, there are some message changes. $GPRMC becomes $GNRMC, $GPGSA likewise become $GNGSA, but it's sent once for each constellation, with an ID field. The biggest change, though, is that $GPGSV turns into $GxGSV, where x is either P, L, A or B for GPS, GLONASS, Galileo or Beidou respectively.

    With support for up to 5 GSV messages per constellation there can be up to 80 satellites. This means slightly rearchitecting the SNR display in the menu system. Now it's 3 2 digit numbers: the total number of visible satellites (the number of satellites in all the $GxGSV messages), the total number of satellites that are being used for the fix (the sum of the "satellites used" values in all of the $GNGSA messages) and the maximum SNR for any single satellite (from $GxGSV). Lastly, there's the GPS mode indication from $GNRMC. This has a bunch of possible values, but the two that are likely to actually show up are "A" for Autonomous mode or "d" for Differential mode (meaning that at least one SBAS is contributing to the fix). Differential mode is ostensibly what you want to see, though for this particular application it's probably not very significant (it can improve the location fix potentially by an order of magnitude, but I don't know how much this results in improvement to the PPS jitter. And for this clock that hardly matters in any event).

    Preliminary tests suggest that the quad constellation support results in improved indoor reception, although a good outdoor antenna placement is still strongly recommended.

  • New GPS receiver

    Nick Sayer11/27/2021 at 22:32 0 comments

    Well, between the pandemic and the fire at the Japanese oscillator factory, SkyTraq has told me that they're going to have to EOL the Venus838LPx-T module that I've been using in damn near everything. I've got about 120 of them left, so there's plenty of time, but I have also gotten ahold of their replacement, the PX1100T, which looks to be a whole lot more convenient all around.

    Firstly, it's the same price at Q:100. It supplies antenna power on its own, so there's no need for the external on-board bias-T. TBD is it's behavior when faced with a short circuit on the antenna jack. We'll see. It would suck, but in principle we could add a blocking cap and do our own external bias-T with our proven circuit. But even better is that the module is compatible with all four of the existing GNSS systems today - GPS, GLONASS, Galileo and Beidou. It also comes as a castellated PCB module, so no more LGA, which was a fiddly assembly process for me. It has fewer connections, and fewer support components on the board. The one big disadvantage is that there is no FIX LED output pin.

    I could get rid of it. For the GPS Clock it's a bit redundant, but for the GPSDOs it's somewhat handy to have it, so I've decided to add support for emulating it on a currently unused pin of the XMega.

    This isn't too hard to do - just hit the OUTTGL register's bit for the pin in question in the PPS ISR as long as the GPS Lock indication is set. And any place where you clear the lock indication, hit the OUTSET register on the same pin. That yields the same behavior as before - on for bad GPS, flashing at 1/2 Hz for good.

  • New feature: SNR report screen

    Nick Sayer04/20/2020 at 20:13 0 comments

    I've added a new feature to the v5 firmware... the first screen in the menu system is now a signal quality report. The hour digits say "nX" where X is the number of satellites in view (this is reported by GPGSV). Numbers higher than 9 will be hex characters, so A, b or C. Normally this should be C (12). The minute/second digits are Sxx where xx is the highest SNR value reported in the active GPGSV sentence fragments - that is, the best SNR value reported. The 10th of a second digit will show the number of satellites that are currently being used for the fix (that is, the number of PRNs listed in the GPGSA sentence).

    In general, you want an absolute bare minimum of 4 satellites contributing to the fix. With a good antenna this number should be more like 8 or 9. You'll likely always see the number of satellites in view as 12, as that's simply taken from the almanac as the 12 closest ones. SNR values above 40 are excellent. 35-40 are good, 30-35 are barely adequate, and under 30 are bad. The receiver is configured with a default SNR mask of 27, so numbers below that mean that no satellites are good enough to use.

    If you have a v5 or v6 clock and a PDI capable programmer, you can update the flash yourself. If not, you can send your clock to me and I'll do it for you.

  • Build report 6.0

    Nick Sayer11/03/2019 at 06:18 0 comments

    The version 6.0 boards are a success.

    I built one with red LEDs and it's quite good. It's a touch dimmer than 5.0 was, but I think that's overall a good thing - the versions up to now were a little too bright insofar as the low end of the brightness range was still too bright. With the per-segment 30 mA current limit there is absolutely no brightness variation of the AM/PM LEDs when the colons blink, which was a small issue before. There's no trace of ghosting with the 1 kΩ pull-downs on the anodes (though to be fair it wasn't an effect that was noticeable with red anyway. I'll build a blue one and then we'll see).

    Moreover, it works normally even with 9 volts supplied on the input power (thanks to the wide-input LDO for the 3.3v rail). The LDO did start to get a bit warm, however, and this was with the antenna disconnected, so it wasn't supplying any antenna power. This means that the board could conceivably be used as the brains behind very large displays where the Vf of the segments is much higher.

  • A dramatic simplification

    Nick Sayer10/19/2019 at 21:31 0 comments

    I don't know why I haven't thought of this before.

    There's no reason to be so fussy about figuring out the precise voltage for the displays. The high side driver chip is perfectly happy to be fed by 5v and use 3.3v input levels from the controller. Given that, there's no reason not to just put series resistors in the anode lines. In that universe, there's also no reason we can't just use an LDO instead of a switching supply for the controller and GPS receiver - we use the same arrangement in the talking clock and it works fine.

    Better yet, they make 30 mA current limit "diodes" that we can use instead of resistors. That way we don't have to tailor the resistors for the LEDs' Vf - we can source any color we can get in the correct pinouts and let the current limiter dynamically tailor itself to the Vf of the displays.

    I'm going to try this architecture and if it works, it'll be the follow-on version to v3.1 in the store.

    EDIT: One difficulty I had not fully considered was that the current regulator has an overhead voltage of 1.8 volts. Given a 5v input, that means a maximum Vf of 3.2v, which is right where the blue LEDs sit. The 5V input voltage was necessary because it was the maximum voltage of the 3.3v LDOs I've got handy, but it turns out you can get pin-compatible wide input LDOs. One factor to consider going that route is the maximum power dissipation of the LDO, but back-of-the-envelope calculations with the NCP718SN330T1G show that the input voltage can go all the way up to about 9 volts before power dissipation becomes a concern. That suggests that running with blue LEDs would be best with a 6 volt supply, but there are folks who have asked about using very large LEDs with much higher Vf. It turns out that's not a problem at all with this new design. The high-side switch and current limiters can handle an input of up to something like 45 volts, and if you remove the on-board LDO and supply 3.3v through the PDI header on the board, it'll work just fine. In this configuration you'd probably also want to solder the antenna power jumper to the 3.3v side or open it and use an external bias-T so as to not feed too high a voltage to your antenna.

  • Recent developments

    Nick Sayer10/14/2019 at 16:12 0 comments

    With a new design for a 3D printed/printable case, I'm going to ditch the long-shaft buttons and go with normal ones. There will be a button extension that will sit in the back of the case between the actual switch and the back panel. This should make life much easier for my assembler.

    In playing with rastered LED matrices in other contexts, I've learned that ghosting can be sometimes caused by the internal LED capacitance self-powering the LEDs after the raster on-period has ended. The workaround for this is resistors from the anode(s) to ground. I've added at least footprints for these to the board, though whether they'll be universally populated or not remains undecided.

    Lastly, I've ordered a board with footprints for non-right-angle through-hole antenna and power connectors. Going this way would allow the antenna and power to come into the back rather than the sides. Some validation with a case design will be needed to decide whether or not this will be the way forward, but it's at least worth an experiment or two.

  • Long term LED reliability with v5

    Nick Sayer03/06/2019 at 16:56 0 comments

    I've been running some v5 boards as a test for a while now, and I've had a couple of digits on different displays begin to fail (the failure mode is they start to be markedly dimmer than the rest). The only reasonable explanation is that I'm over-driving them, so that means the Vf must be reduced slightly so that the displays are more reliable.

    Fortunately, the store inventory of v3.1 boards is still holding up, so there's time to repeat the experiment with a different feedback divider on the LED Vf buck converter.

  • Improved brightness again

    Nick Sayer11/20/2017 at 07:31 0 comments

    I figured out a way to preserve the 2 µs turn-off delay and still have a brighter display (comparable to what was there before).

    The display raster timer will now alternate between two counts - a short value for the turn-off delay time, and a longer value for the actual display.

    With this configuration, we can go back to a 10 kHz raster rate, but with a 75% duty cycle (actually, 75% of 12.5% since the display lights one digit at a time). This doesn't sound like much, but it's actually dramatic from what you can see.

    The bad news is that the accuracy spec (as you'll recall, it's the PPS ISR latency plus the worst-case display refresh) now becomes 125 µs. But again - that's a worst case value. Since a display update resets the raster, we display the least significant digits immediately, so their latency will be much closer to 25 µs. It's only the hour digit an AM/PM indicators that wind up taking the worst-case time, and they don't change nearly as often.

  • A quick idea to improve accuracy

    Nick Sayer11/05/2017 at 17:45 0 comments

    Right now the spec for the clock is 200 µs.

    I think I can turn the spec for the v5 hardware down to 100 µs.

    Recall that the accuracy spec consists of two parts: the ISR latency for the PPS signal, and the display raster update time. You have to account for both, because at the end of the PPS ISR, you've updated the "registers" that display the time, but you have to potentially wait for an entire raster cycle to occur before the display actually changes.

    Right now, the raster cycle displays all 8 digits, and there are four brightness periods for each digit, meaning a full raster cycle takes 32 interrupts, and they happen at 320 kHz.

    If you flip that around, so that the brightness cycles happen outside and the digit rasters happen inside, then the display raster frequency jumps from 10 kHz to 40 kHz, which means a full raster cycle takes not 100 µs, but 25.

    Now, there's a problem: this only works at full brightness. If you dim the display, then there will be somewhere between 25 µs and 75 µs periods of time when the display is not visible. The workaround for that is to give the code outside of the raster ISR the ability to override the raster cycling and have it start from zero right away. In principle, this should only happen once every 10th of a second (or once a second if tenths are not displayed), so the glitch in display brightness should not wind up being visible.

    I'm going to test this idea and see what the impact is.


    Turns out today is an excellent day to test changes, seeing as how it's DST transition day. For the purpose of most of the code, the fact that it's the transition month probably is what matters the most.

    The issue with this new display mechanism is that the high side switch has a turn-off specification of 2 µs. Well, with the interrupt timer for display rastering hitting every 100 counts, that means those interrupts are 3.125 µs apart. If you try and put a delay in, then essentially the main loop never gets a chance to run at all.

    The workaround is to, unfortunately, cut the raster rate in half and stick an empty slot between each lit-up slot. This also winds up reducing the brightness a bit, but frankly the maximum brightness was pretty bright, and the loss isn't too noticeable. The four brightness levels are still meaningful.

    With this change, the latency of the PPS ISR is now 26 µs. With the worst-case display update time of 50 µs, that means that the accuracy now is 76 µs, better than twice as good as it was. But more to the point, because the digit raster order goes from least-significant to most, the only really high latency digits are the least important ones. In general, you can count on the 100 ms and 1 second digit to be updated within 12.5 µs of the end of the ISR, or 38.5 µs after PPS.

    Unfortunately, these changes require the v5 display rastering system, so the improvements can't be ported back to the MAX6951 hardware.

  • Whither Holdover

    Nick Sayer11/05/2017 at 17:34 0 comments

    A few people have asked about holdover as a feature. Holdover is the ability of a radio clock (and the GPS clock is a radio clock, of course) to free-run when reception fails.

    It's not been a priority because with proper antenna placement GPS should always be available, and holding over should not be necessary. This assumption has been a driving force in keeping the hardware and firmware simple. When there's no GPS lock, you just display "no GPS" and wait.

    The problem with holdover is that the statement of how accurate the clock is gets a lot more complex. Right now, I can say that the clock is "within 200 µs." In actual fact, the clock is somewhere between 70 and 170 µs slow because it takes 70 µs for it to process the PPS pulse to completion and the display raster cycle is 100 µs long.

    But holdover introduces the possibility of longer term drift as long as reception remains unavailable. The magnitude of any potential drift will be proportional to how long ago the last sync occurred. You can "tune" the crystal oscillator while reception is available - that is, determine the exact number of clock cycles between PPS interrupts - but you need to quantify the magnitude of any potential changes in that offset.

    You can limit the scope of the problem by limiting how long you're willing to hold over before giving up. But even then, if your crystal has a 10 ppm stability, that's still 36 ms in an hour.

    You can throw money at the problem. A TCXO can provide a frequency stability of 50 ppb. That would give you a maximum drift in an hour of 180 µs. But a DOT050V adds $30 to the BOM cost (so $60 to the retail price).

    In any event, you'd definitely want to give some feedback of how long you've been holding over. You could just do this with a single LED - on constantly for GPS being available, and mostly off with a blink code indicating how long holdover has been in effect (n blinks in a row mean n time periods of holdover, and after 5 time periods the clock should give up).

    But again, all of this is just an effort to mitigate poor antenna placement. I'm just not really convinced it's worth the effort.

View all 51 project logs

  • 1
    Installing the through-hole parts (LEDs)

    When you get the board, all of the surface mount components will have been installed and programmed.

    First, install the two .56" 7-segment LED modules for the seconds and tens-of-seconds digits and the two .56" 7-segment LED modules for the hours and tens-of-hours. Make sure the modules sit flat against the board. For each module, solder a single lead and double-check that the module is oriented correctly (decimal point on the bottom) and sits flat against the board. Solder the rest of the pins.

  • 2
    Step 2

    Install the small .3" 7-segment LED module in the tenth-of-a-second spot on the right side of the board. For best results, mount the module so that the top edge lines up with the top edge of the larger modules. The easiest way to do this is to insert the module into the holes and then lay the board face down so that both the .56" modules and the .3" module are resting flat on the surface. Done correctly the 10th-of-a-second digit will be up about 1/8" from the surface of the PCB. Solder just one pin on each corner and verify that the module is straight, oriented correctly (again, decimal point at the bottom), and the top lines up with the top of the second digit before soldering the rest of the pins.

  • 3
    Step 3

    Install two 3mm LEDs for the colon between the minutes and seconds. The short lead of each (the cathode) should be towards the top. Install the two LEDs so that they're about 1/8" up from the board - that is, so that their tops are at or just below the top face of the adjacent 7 segment display. To do this, you can use the same trick as for the 10th-of-a-second digit - insert the leads and flip the board over and allow the LEDs to rest against the work surface along with the 7 segment modules. Solder one lead of each and carefully verify that each LED is the same height up from the board and is plumb in both directions (from the top and side). Once both LEDs are positioned correctly, solder the remaining leads and trim the excess lead lengths.

View all 14 instructions

Enjoy this project?



Dmitry Grinberg wrote 11/28/2016 at 23:38 point

FYI,  Page 18 of MAX6951 datasheet ( mentions that is has a character rom so this would count against you much like the rules said hd44870 LCDs' would. Judging by their diagram it is 8 bit address an d8 bit data for a 256 byte total.

  Are you sure? yes | no

Nick Sayer wrote 11/30/2016 at 22:51 point

I will disagree with the assertion that character generation tables in accessory chips should count (be they for 7seg or dot matrix), but in this case, there are 16 symbols made from 7 bits of data - that's only 16 bytes, which still fits. If you're counting the charlieplexing matrix built into the chip... well, I don't know what to say to that except that any accessory chip like this could be implemented either as an array of static gates or as an embedded microcontroller running code in ROM. It's unclear how you'd go about interpreting that under the rules one way or the other. See also:

On a more fundamental level, the GPS receiver module has firmware that can be upgraded too. If that counts, then the whole project is sunk (at least, as an entry in the 1K contest).

In the end, it will be a matter for the judges.

Regardless, I still intend to build it. The feature set I want to have is already larger than 1K anyway. :)

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates