2017/11/06

GPS Synchronized RTC Clock


/*
* BuildAGPSControlledClock20171106.ino modified by Befun Hung 
* -----------------------------------------------------------
* MyFeatherGPS.ino
*
* GPS-driven, digital clock with local and UTC time on 7-segment LED display.
*
* Author: Bruce E. Hall, W8BH
* Date: 01 May 2016
* Hardware: ATMEL 32u4 microcontroller on Adafruit "Feather" board
* Maxim DS 3212 real-time-clock on Adafruit Featherwing
* 4-digit 7-segment displays mounted on Adafruit I2C backpacks
* GPS module (Adafruit Ultimate GPS or UBLOX Neo-6M module)
* Software: Arduino 1.6.5
* Code size: about 17200 bytes
*
* The system keeps time in UTC, supplied by GPS and/or RTC when GPS is unavailable.
* Local time automatically tracks daylight saving time for selected time zone.
* USB serial port outputs time and GPS/RTC status messages.
* Dual display (local & UTC) very handy for a ham radio shack.
*
*/

#include <TimeLib.h>                    // github.com/PaulStoffregen/Time
#include <TinyGPS.h>                   // arduiniana.org/libraries/TinyGPS/
#include <Wire.h>                       // needed for I2C
#include <SoftwareSerial.h>
#include <DS1307RTC.h>                  // github.com/PaulStoffregen/DS1307RTC

#define TIME_SYNC_INTERVAL 180          // 180 seconds = 3 min between GPS time sync attempts
#define CYCLES_PER_RTC_SYNC 20          // 20 cycles = 1 hr between gps->rtc sync

#define RXPin 2
#define TXPin 3
#define ConsoleBaud 115200
#define GPSBaud 9600

// #define SerialGPS Serial1            // use hardware UART on 32u4 Feather
SoftwareSerial SerialGPS(RXPin, TXPin);

// Select timeZone offset from UTC:
const int timeZone = 8;                 // Taipei, Taiwan

TinyGPS gps;                           // gps string-parser object
time_t prevDisplay = 0;                 // when the digital clock was displayed
int cyclesUntilSync = 0;                // counts number of cycles until RTC sync
time_t dstStart = 0;                    // start of DST in unix time
time_t dstEnd = 0;                      // end of DST in unix time
bool gpsLocked = false;                 // indicates recent sync with GPS
int currentYear = 0;                    // used for DST

void setup()
{
   Serial.begin(ConsoleBaud);            // start USB serial output
   SerialGPS.begin(GPSBaud);            // open UART connection to GPS
   //manualTimeSet();                   // allow temporary time setting before gps kicks in
   setSyncProvider(timeSync);           // specify time setting routine
   setSyncInterval(TIME_SYNC_INTERVAL); // periodically get time from gps/rtc
}

//
// Here are the basic clock routines:
// "Loop" updates the display when a new time is available
// "TimeSync" keeps system time in UTC, and updates it with GPS data.
// If GPS is unavailable, time is updated from RTC chip instead.
//

void loop()
{
   while (SerialGPS.available())         // look for GPS data on serial port
   {
      int c=SerialGPS.read();            // get one character at a time
      gps.encode(c);                     // and feed it to gps parser
   }
   if (timeStatus()!= timeNotSet)        // wait until time is set by sync routine
   {
      if (now() != prevDisplay)          // dont update display until time changes
      {
         prevDisplay = now();            // save current time
         updateDST();                    // check DST status
         displayLocalTime();             // show local time on LEDs
         displayUTC();                   // show UTC time on LEDs
         printTime(prevDisplay);         // send time to USB serial port
      }
   }
}

time_t timeSync()
// will change global variable "gpsLocked" according to ability to sync with GPS
// returns time as UTC
{
   tmElements_t tm;
   time_t t = 0;
   int yr;
   unsigned long fixAge;
   
   gpsLocked = false;                               // assume failure to read GPS
   gps.crack_datetime(&yr, &tm.Month, &tm.Day,      // get UTC time from GPS
      &tm.Hour, &tm.Minute, &tm.Second,
      NULL, &fixAge);
      
   if (fixAge==TinyGPS::GPS_INVALID_FIX_TIME)      // GPS online but no satellite fix
   {
      Serial.println("\nNo GPS fix, using RTC");
      t = RTC.get();                                // ...so get from RTC instead
   }
   else if (fixAge > 2000)                          // GPS is offline
   {
      Serial.println("\nGPS offline, using RTC");
      t = RTC.get();                                // ...so get from RTC instead
   }
   else                                             // GPS time is good
   {
      gpsLocked = true;                             // flag success
      tm.Year = yr-1970;                            // convert calendar years to unix years
      Serial.print("\nGPS time OK, ");
      Serial.print(gps.satellites());
      Serial.print(" sats. ");
      if (cyclesUntilSync <=0)                      // time to update RTC yet?
      {
         if (RTC.write(tm))                         // update real-time clock chip
            Serial.print("RTC updated.");
         else Serial.print("RTC update failed.");
         cyclesUntilSync = CYCLES_PER_RTC_SYNC;     // reset counter
      }
      Serial.println();
      t = makeTime(tm);                             // convert to time_t
   }
   cyclesUntilSync --;                              // count-down another cycle
   return t;                                        // return unix time in UTC
}

void manualTimeSet()
// Use this routine to manually set the clock to a specific UTC time.
// Set each tm element to desired value
// Since time is automatic set from gps, this routine is mainly for debugging
{
   tmElements_t tm;
   tm.Year = 2016 - 1970;                           // Year in unix years
   tm.Month = 11;
   tm.Day = 6;
   tm.Hour = 5;
   tm.Minute = 59;
   tm.Second = 30;
   RTC.write(tm);                                   // set RTC to desired time
}

//
// The following routines support automatic daylight saving time adjustment
//
// Daylight Saving Time constants:
// Please set according to current DST transition times
//
// define DST start value same as end value to disable DST time
//
#define DST_START_WEEK 2                            // Second Sunday
#define DST_START_MONTH 3                           // in March
#define DST_START_HOUR 7                            // at 2AM EST = 0700 UTC
#define DST_END_WEEK 1                              // First Sunday
#define DST_END_MONTH 11                            // in November
#define DST_END_HOUR 6                              // at 2AM EDT = 0600 UTC

time_t timeChange(int hr, int wk, int mo, int yr)
// returns unix time of DST transition according to hr, wk, mo, and yr
// Routine first calculates time on first day of month, at specified time
// then adds number of days to first Sunday, then number of additional weeks
{
   tmElements_t tm;                                 // set up time elements struct
   tm.Year = yr - 1970;                             // need unix year, not calendar year
   tm.Month = mo;
   tm.Day = 1;                                      // start on first day of month
   tm.Hour = hr;                                    // use UTC hour, not local hour
   tm.Minute = 0;
   tm.Second = 0;
   time_t t = makeTime(tm);                         // convert to unix time
   int daysTillSunday = (8 - weekday(t)) % 7;       // how many days until first Sunday?
   t += (daysTillSunday + (wk-1)*7)*SECS_PER_DAY;   // adjust time for additional days
   return t;
}

void printDST()
// debug function to show DST start & end times
{
   Serial.print("DST starts at ");
   printTime(dstStart);
   Serial.print("DST ends at ");
   printTime(dstEnd);
}

void initDST(int yr)
// establish start and end times for DST in a given year. Call at least once a year!
{
   dstStart = timeChange(DST_START_HOUR, DST_START_WEEK, DST_START_MONTH, yr);
   dstEnd = timeChange(DST_END_HOUR, DST_END_WEEK, DST_END_MONTH, yr);
}

void updateDST()
{
   int yr = year();                                // what year is it?
   if (yr != currentYear)                          // a new year started, need new DST info
   {
      initDST(yr);                                 // find DST times for new year
      currentYear = yr;                            // save current year
   }
}

bool isDST(time_t t)                               // returns TRUE if time is in DST window
{
   return ((t >= dstStart) && (t < dstEnd));
}

time_t localTime()                                 // returns local time, adjusted for DST
{
   time_t t = now();                               // get UTC time
   if (isDST(t)) t += SECS_PER_HOUR;               // add DST correction
   t += (timeZone * SECS_PER_HOUR);                // add timeZone correction
   return t;
}

//
// The following routine support time display
//

void printTime(time_t t)
// send time string to serial monitor
{
   Serial.print(hour(t));
   printDigits(minute(t));
   printDigits(second(t));
   Serial.print(" ");
   Serial.print(month(t));
   Serial.print("/");
   Serial.print(day(t));
   Serial.print("/");
   Serial.print(year(t));
   Serial.println(" UTC");
}

void printDigits(int digits)
// utility function for digital clock display: prints preceding colon and leading 0
{
   Serial.print(":");
   if(digits < 10)
   Serial.print('0');
   Serial.print(digits);
}

// display UTC time on display device
void displayUTC()
{
   Serial.print(hour());
   Serial.print(":");
   Serial.print(minute());
   Serial.print(":");
   Serial.print(second());
   Serial.println(" utc");
}

// display local time on display device
void displayLocalTime()
{
   time_t t = localTime();                         // convert system time (UTC) to local time

   Serial.print(hour(t));
   Serial.print(":");
   Serial.print(minute(t));
   Serial.print(":");
   Serial.print(second(t));
   Serial.println(" Local");
}

No comments:

Post a Comment