[Home] [Downloads] [Search] [Help/forum]

Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  GPS digital clock

GPS digital clock

Postings by administrators only.

[Refresh] Refresh page

Posted by Nick Gammon   Australia  (22,966 posts)  [Biography] bio   Forum Administrator
Date Thu 04 Apr 2013 06:27 AM (UTC)

Amended on Sun 06 Oct 2013 12:00 AM (UTC) by Nick Gammon

As daylight savings ends here (in Australia) this Sunday, I thought it would be a good time to make up a GPS clock I could take around with me as I reset the 1000 or so clocks we have here. (Just joking, but there are a lot, once you allow for clocks in ovens, microwaves, VCRs, etc.).

I had a EM-406a GPS module lying around, purchased for a day like this. Here are its pinouts:

A lot of time was wasted trying to find suitable level-converters for it, since it outputs at 2.85V (at 4800 baud) until I realized that 2.85V would count as HIGH on a serial port, without needing a level converter. And then if the processor was run at around 3.3V then anything over around 1.65V would be HIGH, which was even better. So, no level converter. :)

Then I grabbed the MAX7219 8-digit LED module that recently arrived from eBay for $10.

Connected together and assembled into a lunch box it looks like this:

The photo doesn't do the digits justice, they look higher contrast in a normal room:

The LED module is stuck to the LID:

The overall effect:

Inside is a Real Bare Bones Board (from Modern Device):

And the GPS and battery box:

Connections to the RBBB:

The Rx line on the GPS is supposed to be held high, so I used a voltage divider (two resistors) to get around 2.8V from the Vcc line on the RBBB. (1.5K and 10K and divides 3.2V into 2.8V at the join of the resistors, with 10K being the one going to Gnd).

The whole thing is powered by 2 x AA alkaline batteries. They put out around 3.2V, which is enough for the processor (which is running at 8 MHz on the internal oscillator) and apparently sufficient for the GPS.

[EDIT] See below. After a few hours the 2 x AA batteries failed to power it properly. I substituted 2 x AA NiZn batteries which have a higher nominal voltage. Or you could use 3 x AA batteries and change the voltage divider a bit (eg. change 1.5K resistor to 5.6K).


The code is below, if you want to reproduce it.

A fair bit of code is working out whether it is daylight savings time or not. You may need to adjust the calculations depending on the local rules. For us right now, from the first Sunday in April to the first Sunday in October, it is not daylight savings time.

The main loop is simply the standard "get data from serial and buffer it" stuff.

I use a regular expression to parse the GPS data because I was too lazy to work out a more sophisticated way.

Then the time is adjusted by the time-zone, and then daylight saving based on the date from the GPS.

The time is shown as a 12-hour clock with the final digit being "P" for PM.

The decimal point in the "blank" digit (next to the P) is on if we have a GPS fix, otherwise off.

// GPS clock with digital read-out
// Author: Nick Gammon
// Date:   4 April 2013

// NB: Compile for Lilypad Arduino (8 MHz clock)

// Version 2. Fixes problem with detecting daylight-saving time on the change-over day.

#include <SoftwareSerial.h>
#include <Regexp.h>
#include <SPI.h>

// Adjust for your time zone. Hours + or - from UTC.
const int DST_TIME_OFFSET = 10;

// MAX7219 constants

const byte MAX7219_REG_NOOP        = 0x0;
// codes 1 to 8 are digit positions 1 to 8
const byte MAX7219_REG_DECODEMODE  = 0x9;
const byte MAX7219_REG_INTENSITY   = 0xA;
const byte MAX7219_REG_SCANLIMIT   = 0xB;
const byte MAX7219_REG_SHUTDOWN    = 0xC;
const byte MAX7219_REG_DISPLAYTEST = 0xF;

// For GPS input
SoftwareSerial GPSserial(2, 3); // RX, TX

// how much serial data we expect before a newline
const unsigned int MAX_INPUT = 100;

// send a digit or other data to the MAX7219
void sendByte (const byte reg, const byte data)
  digitalWrite (SS, LOW);
  SPI.transfer (reg);
  SPI.transfer (data);
  digitalWrite (SS, HIGH); 
  }  // end of sendByte

void setup()  
  // Open serial communications and wait for port to open:

  // set the data rate for the SoftwareSerial port
  SPI.begin ();
  sendByte (MAX7219_REG_SCANLIMIT, 7);      // show 6 digits
  sendByte (MAX7219_REG_DECODEMODE, 0xFF);  // use digits (not bit patterns)
  sendByte (MAX7219_REG_DISPLAYTEST, 0);    // no display test
  sendByte (MAX7219_REG_INTENSITY, 10);      // character intensity: range: 0 to 15
  sendByte (MAX7219_REG_SHUTDOWN, 1);       // not in shutdown mode (ie. start it up)
  // send hyphens during start-up
  for (int digit = 0; digit < 8; digit++)
    sendByte (digit + 1, 0x0A);
  }  // end of setup

// http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
// Devised by Tomohiko Sakamoto in 1993, it is accurate for any Gregorian date.
// Returns 0 = Sunday, 1 = Monday, etc.

int dayOfWeek (int d, int m, int y)
   static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
   y -= m < 3;
   return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
   }  // end of dayOfWeek
// DST = Daylight Savings Time
boolean isDaylightTime (int day, int month, int year, int hour) 
  int firstSundayInApril;
  int firstSundayInOctober;

  for (firstSundayInApril = 1; firstSundayInApril <= 31; firstSundayInApril++)
    if (dayOfWeek (firstSundayInApril, 4, year) == 0)

  for (firstSundayInOctober = 1; firstSundayInOctober <= 31; firstSundayInOctober++)
    if (dayOfWeek (firstSundayInOctober, 10, year) == 0)

  // May to September: not DST
  if (month >= 5 && month <= 9) 
    return false;
  // January to March, and November to December: is DST
  if (month <= 3 || month >= 11)
    return true;  
  // In April, if not yet first Sunday, still DST
  if (month == 4 && day < firstSundayInApril)
    return true;

  // In April, on first Sunday, still DST before 2 am
  if (month == 4 && day == firstSundayInApril && hour < 2)
    return true;
  // In October, if after first Sunday, is DST
  if (month == 10 && day > firstSundayInOctober)
    return true;

  // In October, on first Sunday, is DST after 2 am
  if (month == 10 && day == firstSundayInOctober && hour >= 2)
    return true;
  // some date in April or October that did not pass the above tests
  return false; 
  } // end of isDaylightTime  

// here to process incoming serial data after a terminator received
void process_data (char * data)
  // for now just display it
  Serial.println (data);

  MatchState ms;
  ms.Target (data);    
  char hour [5]; 
  char mins [5];  
  char secs [5]; 
  char valid [5];
  char day [5];
  char month [5];
  char year [5];

  char result = ms.Match ("^$GPRMC,(%d%d)(%d%d)(%d%d)%.%d+,(%a),.-,.-,.-,.-,.-,.-,(%d%d)(%d%d)(%d%d)");
//                                   HH    MM    SS    ms valid   lat  long spd crs  DD   MM    YY

  if (result != REGEXP_MATCHED)
   ms.GetCapture (hour, 0);
   ms.GetCapture (mins, 1);
   ms.GetCapture (secs, 2);
   ms.GetCapture (valid, 3);
   ms.GetCapture (day, 4);
   ms.GetCapture (month, 5);
   ms.GetCapture (year, 6);
   int iHour = atoi (hour);
   int iDay = atoi (day);
   // make time local
   iHour += DST_TIME_OFFSET;
   // if past midnight with time-zone offset adjust the day so the DST calculations are correct
   if (iHour >= 24)
   else if (iHour < 0)
   // allow for daylight savings
   if (isDaylightTime (iDay, atoi (month), atoi (year) + 2000, iHour))
   // pull into range
   if (iHour >= 24)
     iHour -= 24;
   if (iHour < 0)
     iHour += 24;
   // work out AM/PM 
   boolean pm = false;
   if (iHour >= 12)
     pm = true;
   if (iHour > 12)
     iHour -= 12;
   char buf [8];
   sprintf (buf, "%02i%s%s", iHour, mins, secs); 
   // send all 6 digits
   for (byte digit = 0; digit < 6; digit++)
     byte c = buf [digit];
     if (c == '0' && digit == 0)
       c = 0xF;  // code for a blank
       c -= '0';
     if (digit == 1 || digit == 3)
       c |= 0x80;  // decimal place
     sendByte (8 - digit, c);  
  sendByte (2, 0xF | ((valid [0] == 'A') ? 0x80 : 0));  // space, add dot if valid
  if (pm)
    sendByte (1, 0xE);  // P    
    sendByte (1, 0xF);    
  }  // end of process_data

void loop()
static char input_line [MAX_INPUT];
static unsigned int input_pos = 0;

  if (GPSserial.available () > 0) 
    char inByte = GPSserial.read ();

    switch (inByte)

      case '\n':   // end of text
        input_line [input_pos] = 0;  // terminating null byte
        // terminator reached! process input_line here ...
        process_data (input_line);
        // reset buffer for next time
        input_pos = 0;  
      case '\r':   // discard carriage return
        // keep adding if not full ... allow for terminating null byte
        if (input_pos < (MAX_INPUT - 1))
          input_line [input_pos++] = inByte;

      }  // end of switch

  }  // end of incoming data

}  // end of loop

The whole thing works surprisingly well, considering that both the GPS and the MAX7219 are running from around 3V.

[EDIT] Fixed code on 7 April 2013, to correct bug where the DST calculations did not take into account that the day may be out by one. For example, on 6 am on Sunday, if you are 10 hours ahead, it thought the day was still Saturday and thus did not change the clock.



On the cable that comes with the GPS there may be a gray wire. On mine this was on the left when plugged in (pin 6). Looking at the GPS from above (see diagram) pin 1 is on the right.

It turned out I actually have a EM-411 which is wired identically except that Tx/Rx are swapped, which is a bit of a trap.

The daylight saving calculations are for Melbourne, Australia. You could adjust the "timeOffset" variable for different time-zones, and if you live in the Northern Hemisphere the daylight saving is likely to be reversed at the very least (as Summer is in June) and may also start and end on different dates.

Battery voltage

A few more hours of testing revealed that the device failed to start up once the batteries drained a bit. Maybe 2 x AA batteries was a bit marginal.

You might want to use 3 x AA batteries instead, and maybe adjust the voltage divider a bit (eg. change 1.5K resistor to 5.6K).

Or, this worked for me ... I got a couple of 1.6V NiZn batteries from eBay:

The extra voltage they put out has kept it running ... for now. ;)

Boost converter

Further testing indicates that even that NiZn batteries were not reliable after a while. Instead I inserted a boost-converter module from eBay. This particular one cost just over $1. Try searching for "1A 3V to 5V DC-DC Converter Step Up Boost Module" or similar.

Placing that between the battery and the input to the board boosted the output of the batteries enough for the device to work more reliably.

There are other modules available which supply less current but work at lower voltages. For example "1-5V to 5V 500mA DC-DC Converter Step Up Boost Module". These cost around $1.50.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum

[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]