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");
}
Subscribe to:
Posts (Atom)