Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ Electronics ➜ Microprocessors ➜ GPS digital clock

GPS digital clock

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,133 posts)  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

Message
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).

Code


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:
  Serial.begin(115200);

  // set the data rate for the SoftwareSerial port
  GPSserial.begin(4800);
  
  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)
      break;

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

  // 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)
    return;
    
   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)
     iDay++;
   else if (iHour < 0)
     iDay--;
   
   // allow for daylight savings
   if (isDaylightTime (iDay, atoi (month), atoi (year) + 2000, iHour))
     iHour++;
     
   // pull into range
   if (iHour >= 24)
     iHour -= 24;
   else
   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
     else
       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    
  else
    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;  
        break;
  
      case '\r':   // discard carriage return
        break;
  
      default:
        // keep adding if not full ... allow for terminating null byte
        if (input_pos < (MAX_INPUT - 1))
          input_line [input_pos++] = inByte;
        break;

      }  // 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.

Schematic




Notes


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


12,302 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

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