Here's a copy of my GPS clock code. It shows everything in context.
Code: Select all
// GPSClock Rick Comito et al.
#define VERSION "Ver. 1.00.201400412 Rick Comito"
//
// Version history:
// 1.00 - add AM / PM indicator and call it done
// 0.90 - General code optimization and tweaking
// 0.80 - Display colon between HH and MM. Add light sensor dimming.
// 0.70 - Display satellite count and fix status on display 1
// 0.60 - Display HHMM on display 0 and SS on display 1
// 0.50 - Incorporate standard / daylight time change
// 0.40 - Increase _SS_MAX_RX_BUFF to allow for faster polling.
// 0.30 - Incorporate timezone.h. Adjust UTC to local standard time
// 0.20 - Move TX and RX pins to 8 and 9 to allow program upload without wireing changes
// 0.10 - Parse time and date from GPS data and send to serial monitor
// 0.00 - Just read the GPS module and send the data to the serial monitor
//
// This clock uses a GPS module to get the time and date. We need the date so we know when to
// adjust for Standard Time/Daylight Saving Time.
//
// Thanks to the crews at http://www.adafruit.com and http://www.arduino.cc for all of the ideas,
// source code shared, and the great products and support.
//
// This clock uses two 4 x 7 segment displays on I2C backplanes to display hours, minutes, seconds,
// and number of satellites seen. The first is addressed at 0x70 (default) and the second is addressed
// at 0x71 by shorting the address pads at A0.
//
// It uses an optional light sensor to adjust the display brightness based on ambient lighting.
//
// This code started with code written by Jay Doscher. Turns out he used the same microcontroller and
// GPS module that I had decided on. The slick interrupt stuff is his. Thanks for the jumpstart Jay.
// http://www.polyideas.com/blog/2012/12/16/gps-clock-using-an-arduino-micro
//
//=========================================================================\\
// Parts list: \\
// Arduino Micro http://www.adafruit.com/products/1086 \\
// Adafruit Ultimate GPS module http://www.adafruit.com/products/746 \\
// Two 4 x 7 segment displays with I2C backplanes: \\
// 1.2" http://www.adafruit.com/products/1270 \\
// .56" http://www.adafruit.com/products/865 \\
//=========================================================================\\
// Optional parts: \\
// Light sensor http://www.adafruit.com/products/1384 \\
// GPS external antenna http://www.adafruit.com/products/960 \\
// uFL to SMA adapter http://www.adafruit.com/products/851 \\
//=========================================================================\\
// One stop shopping. Pick them up today at the Adafruit electronics shop
// and help support open source hardware & software!
#include <SoftwareSerial.h> // Part of the Arduino standard library.
#include <Wire.h> // Part of the Arduino standard library.
#include <Time.h> //http://www.arduino.cc/playground/Code/Time
#include <Timezone.h> //https://github.com/JChristensen/Timezone Thanks to Jack Christensen (Nice work)
#include <Adafruit_GPS.h> //https://github.com/adafruit/Adafruit-GPS-Library
#include <Adafruit_GFX.h> //https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_LEDBackpack.h> //https://github.com/adafruit/Adafruit-LED-Backpack-Library
// Increase _SS_MAX_RX_BUFF in SoftwareSerial.h from 64 to 128 in order to be able to pole the GPS
// faster than 1HZ. Thanks to Wayne Holder over at SparkFun.com for this one. He did it a little
// differently, but I got the idea from him.
#ifdef _SS_MAX_RX_BUF
# undef _SS_MAX_RX_BUF
# define _SS_MAX_RX_BUF 128
#endif
#define TIMETOUPDATE 10 // Milliseconds between clock updates. Update frequently so there's no delay
// between second rollovers (ticks).
//#define LIGHTSENSOR // Defined if this build has a light sensor
//#define WAKEUP // Run our "Teddy Ruxpin" boot-up routine
// Debugging flags - Allows for eight levels of debugging
#define DB 0b00000000 // Debugging off
#define DBGPS 0b00000001 // Show raw GPS sentences (also good for show and tell)
#define DBSATS 0b00000010 // Fix status and satellite count
#define DBUTC 0b00000100 // UTC time and date
#define DBLOCAL 0b00001000 // Local time and date
#define DBDISP 0b00010000 // Data to be sent to the displays
#define DBBRIGHT 0b00100000 // Display brightness settings
byte DEBUG = DB | DBGPS; // OR in the levels that you need e.g: DEBUG = DB | DBGPS | DBSATS | DBCLOCK;
// Timezone rules. Select yours from the following. If you need space, comment out the rest.
// Atlantic
TimeChangeRule aDST = { "ADT", Second, Sun, Mar, 2, -180 }; // UTC - 3 hrs.
TimeChangeRule aSTD = { "AST", First, Sun, Nov, 2, -240 }; // UTC - 4 hrs.
// Eastern
TimeChangeRule eDST = { "EDT", Second, Sun, Mar, 2, -240 }; // UTC - 4 hrs.
TimeChangeRule eSTD = { "EST", First, Sun, Nov, 2, -300 }; // UTC - 5 hrs.
// Central
TimeChangeRule cDST = { "CDT", Second, Sun, Mar, 2, -300 }; // UTC - 5 hrs.
TimeChangeRule cSTD = { "CST", First, Sun, Nov, 2, -360 }; // UTC - 6 hrs.
// Mountain
TimeChangeRule mDST = { "MDT", Second, Sun, Mar, 2, -360 }; // UTC - 6 hrs.
TimeChangeRule mSTD = { "MST", First, Sun, Nov, 2, -420 }; // UTC - 7 hrs.
// Arizona - Does not observe daylight time
TimeChangeRule azDST = { "MST", Second, Sun, Mar, 2, -420 }; // UTC - 7 hrs.
TimeChangeRule azSTD = { "MST", First, Sun, Nov, 2, -420 }; // UTC - 7 hrs.
// Pacific
TimeChangeRule pDST = { "PDT", Second, Sun, Mar, 2, -420 }; // UTC - 7 hrs.
TimeChangeRule pSTD = { "PST", First, Sun, Nov, 2, -480 }; // UTC - 8 hrs.
// Alaska
TimeChangeRule akDST = { "AKDT", Second, Sun, Mar, 2, -480 }; // UTC - 8 hrs.
TimeChangeRule akSTD = { "AKST", First, Sun, Nov, 2, -540 }; // UTC - 9 hrs.
// Hawaii-Aleution
TimeChangeRule haDST = { "HADT", Second, Sun, Mar, 2, -540 }; // UTC - 9 hrs.
TimeChangeRule haSTD = { "HAST", First, Sun, Nov, 2, -600 }; // UTC - 10 hrs.
// Hawaii - Does not observe daylight time
TimeChangeRule hiDST = { "HDT", Second, Sun, Mar, 2, -600 }; // UTC - 10 hrs.
TimeChangeRule hiSTD = { "HST", First, Sun, Nov, 2, -600 }; // UTC - 10 hrs.
// Mine is US Eastern Time Zone (Boston, Detroit)
TimeChangeRule myDST = eDST;
TimeChangeRule mySTD = eSTD;
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr; //pointer to the time change rule, used to get TZ abbrev
time_t utc, local; // Universal and local time in time_t format
// Connect the GPS Power pin to 5V
// Connect the GPS Ground pin to ground
// If using software serial (sketch example default):
// Connect the GPS TX (transmit) pin to Digital 8
// Connect the GPS RX (receive) pin to Digital 7
// If using hardware serial (e.g. Arduino Mega):
// Connect the GPS TX (transmit) pin to Arduino RX1, RX2 or RX3
// Connect the GPS RX (receive) pin to matching TX1, TX2 or TX3
#define TX 8
#define RX 7
SoftwareSerial mySerial(TX, RX);
Adafruit_GPS GPS(&mySerial);
// If using hardware serial (e.g. Arduino Mega), comment
// out the above four lines and enable this line instead:
//Adafruit_GPS GPS(&Serial1);
boolean firstFix = false; // Clock has had it's first satellite fix and the GPS onboard RTC is set.
// The two 4 x 7 segment displays are at I2C addresses 0x70 and 0x71
Adafruit_7segment matrix0, matrix1 = Adafruit_7segment();
byte display0 = 0x70;
byte display1 = 0x71;
#ifdef LIGHTSENSOR
// Light sensor pin
uint8_t sensorPin = A0; // Wire the sensor "OUT" pin to A0
#endif
// Set aside a small buffer to build sprintf() formatted debugging messages
char sbuffer[64] = "";
// This keeps track of whether we're using the interrupt.
// On by default!
boolean usingInterrupt = true;
// Stumbled across this in a thread on arduino.cc. Sets the fix interval.
// Match this with your PMTK_SET_NMEA_UPDATE_xxHZ. Thanks Kas.
#define PMTK_SET_NMEA_FIX_1HZ "$PMTK300,1000,0,0,0,0*1C"
#define PMTK_SET_NMEA_FIX_5HZ "$PMTK300,200,0,0,0,0*2F"
#define PMTK_SET_NMEA_FIX_10HZ "$PMTK300,100,0,0,0,0*2"
void setup(void)
{
// 9600 NMEA is the default baud rate for Adafruit MTK GPS - some use 4800
GPS.begin(9600);
// Turn off all data output while we set our preferences.
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_OFF);
delay(100);
// Turn off antenna status
GPS.sendCommand(PGCMD_NOANTENNA);
// Set the GPS update rate
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ);
// Set the GPS fix interval
GPS.sendCommand(PMTK_SET_NMEA_FIX_5HZ);
// Turn on GPS RMC (recommended minimum) and GGA (fix data) sentences
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
// Initialize, clear, and dim the displays.
// Note: my display0 is not quite as bright is display1, so set 0 to level 1, and 1 to level 0
matrix0.begin(display0);
matrix0.clear();
matrix0.setBrightness(1);
matrix1.begin(display1);
matrix1.clear();
matrix1.setBrightness(0);
// Set the satellite count to 0
matrix1.writeDigitNum(4, 0);
matrix0.writeDisplay();
matrix1.writeDisplay();
#ifdef LIGHTSENSOR
// The light sensor needs a 3.3v reference. Jumper 3.3v to AREF
analogReference(EXTERNAL);
#endif
// the nice thing about this code is you can have a timer0 interrupt go off
// every 1 millisecond, and read data from the GPS for you. That makes the
// loop code a heck of a lot easier!
useInterrupt(true);
// If we're debugging, connect the serial console at 115200 so we can read the GPS
// fast enough and echo without dropping chars.
if (DEBUG)
Serial.begin(115200);
delay(1000);
#ifdef WAKEUP
wakeup(); // Just a little animation thing for fun.
#endif
} // setup
uint32_t timer = millis();
void loop(void) {
uint8_t hh, mm, ss, dd, mo, yy;
// if a sentence is received, we can verify the checksum, parse it ...
if (GPS.newNMEAreceived()) {
if (!GPS.parse(GPS.lastNMEA()))
return; // If we fail to parse a sentence, we should just wait for another
}
// if millis() timer wraps around, reset it
if (timer > millis())
timer = millis();
// Update the clock approximately every TIMETOUPDATE milliseconds or so.
// this should be often enough to ensure that we don't miss any seconds
// rollover.
if ((uint32_t)millis() - timer >= TIMETOUPDATE) {
if (DEBUG & DBGPS)
Serial.println(GPS.lastNMEA());
timer = millis(); // reset the timer
// If we have a GPS fix, the RTC on the GPS board has been set and is getting updated.
if ((firstFix) || (GPS.fix > 0)) {
if (DEBUG & DBSATS) {
sprintf(sbuffer, "Fix: %d Satellites: %d\n", GPS.fix, GPS.satellites);
Serial.print(sbuffer);
}
firstFix = true;
hh = GPS.hour;
mm = GPS.minute;
ss = GPS.seconds;
mo = GPS.month;
dd = GPS.day;
yy = GPS.year;
// Set the Arduino software RTC
setTime(hh, mm, ss, dd, mo, yy);
}
// Get the time from the software RTC in time_t format
// so we can have the timezone tools do their magic.
utc = now();
if (DEBUG & DBUTC) {
sprintf(sbuffer, "%02d:%02d:%02d ", hour(utc), minute(utc), second(utc));
Serial.print(sbuffer);
sprintf(sbuffer, "%s %02d %s %d\n", dayShortStr(weekday(utc)), day(utc), monthShortStr(month(utc)), year(utc));
Serial.print(sbuffer);
}
local = myTZ.toLocal(utc, &tcr);
if (DEBUG & DBLOCAL) {
sprintf(sbuffer, "%02d:%02d:%02d ", hour(local), minute(local), second(local));
Serial.print(sbuffer);
sprintf(sbuffer, "%s %02d %s %d %s\n", dayShortStr(weekday(local)), day(local), monthShortStr(month(local)), year(local), tcr -> abbrev);
Serial.print(sbuffer);
}
updateClock(local);
#ifdef LIGHTSENSOR
setBrightness();
#endif
}
} // loop
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
volatile char c = GPS.read();
}
void useInterrupt(boolean v) {
if (v) {
// Timer0 is already used for millis() - we'll just interrupt somewhere
// in the middle and call the "Compare A" function above
OCR0A = 0xAF;
TIMSK0 |= _BV(OCIE0A);
usingInterrupt = true;
}
else {
// do not call the interrupt function COMPA anymore
TIMSK0 &= ~_BV(OCIE0A);
usingInterrupt = false;
}
} // useInterrupt
// updateClock
//
// Update the clock displays.
// This function gets passed time_t localtime which is the current time adjusted for timezone and
// standard / daylight time with the great tools built by Jack Christensen.
//
// If you're building any kind of clock and you want to display local time, check out
// https://github.com/JChristensen/Timezone Nice work Jack. Thanks.
//
// The displays get cleared in setup(). We'll show the number of satellites we can see,
// but leave the time blank until we get our first satellite fix. We can't count on the
// RTC if this is the first time the code has been run, or if it has had a recent battery
// change (or doesn't have one installed). Besides, it adds a little drama to the clock
// when it's first plugged in. You see the satellite count incrementing, then BAM, clock
// display.
#define PMDOT 0x08 // Leftmost lower dot for PM indicator
#define AMDOT 0x04 // Leftmost upper dot for AM indicator
#define COLON 0x02 // Colon between HH and MM
void updateClock(time_t localtime) {
int hhmm; // Hours and minutes
int secs; // Seconds
uint8_t sats; // How many satellites we can see
int ssss; // Second and satellites displayed as ss.ss
static int lasthhmm = 0; // Hour and minute last time we set the displays
static int lastssss = 0; // Second and satellites last time we set the displays
byte ampmdot; // Code to present AM or PM dot indicator
boolean showdot; // Show the dot in ss.ss if we still have a fix
sats = GPS.satellites;
// If we haven't established our first satellite fix yet, leave the hours, minutes and
// seconds blank, but display how many satellites (if any) we can see.
if(!firstFix) {
ssss = sats;
if (ssss != lastssss) {
matrix1.print(ssss);
matrix1.writeDisplay();
}
}
// We've had at least one satellite fix, so update the displays if they need it.
else {
// Display 12 hour time (My bride doesn't do military time)
hhmm = ((hour(localtime) % 12) * 100) + minute(localtime);
if (hhmm < 100) // Midnight hour
hhmm += 1200;
ssss = (second(localtime) * 100) + sats;
showdot = (GPS.fix > 0) ? true : false;
// Update displays if something has changed.
if (hhmm != lasthhmm) {
matrix0.print(hhmm);
ampmdot = (hour(localtime) < 12) ? AMDOT : PMDOT;
matrix0.writeDigitRaw(2, COLON | ampmdot);
matrix0.writeDisplay();
}
// Handle this display one digit at a time to preserve leading zeros.
if (ssss != lastssss) {
matrix1.writeDigitNum(0, (ssss / 1000));
matrix1.writeDigitNum(1, (ssss / 100) % 10, showdot); // true turns on the dot in ss.ss
matrix1.writeDigitNum(3, (ssss / 10) % 10);
matrix1.writeDigitNum(4, ssss % 10);
matrix1.writeDisplay();
}
if (DEBUG & DBDISP) {
sprintf(sbuffer, "%4d %04d\n", hhmm, ssss);
Serial.print(sbuffer);
}
}
lasthhmm = hhmm;
lastssss = ssss;
} // updateClock
#ifdef LIGHTSENSOR
// setBrightness
// Set the display brightness based on ambient light.
// Sensor readings range from 1 to 1024
// Display brightness ranges from 0 (dim) to 15 (bright).
// The 1.2" and .56" displays have slightly different brightness
// so we'll try to adjust accordingly.
# define BRIGHT 15 // Maximum setting
# define DIM 0 // Minimum display brightness setting
# define HILIMIT 850 // Upper raw limit to set bright setting
# define LOLIMIT 100 // Lower raw limit to set dim setting
# define BIAS0 1 // Extra brightness to add to display 0
# define BIAS1 -2 // Extra dimness to add to display 1
void setBrightness()
{
static uint8_t lastsetting = 0; // Last display0 setting
uint8_t bsetting0 = BIAS0; // Brightness setting for display 0
uint8_t bsetting1 = 0; // Brightness setting for display 1
// read the raw value from the sensor:
uint8_t rawvalue = analogRead(sensorPin);
// Set brightness accordingly
if (rawvalue >= HILIMIT) {
bsetting0 = BRIGHT;
bsetting1 = BRIGHT + BIAS1;
}
else if (rawvalue <= LOLIMIT) {
bsetting0 = DIM + BIAS0;
bsetting1 = DIM;
}
if (bsetting0 != lastsetting) {
matrix0.setBrightness(bsetting0);
matrix1.setBrightness(bsetting1);
}
if (DEBUG & DBBRIGHT) {
sprintf(sbuffer, "Raw %d Settings %d %d\n", rawvalue, bsetting0, bsetting1);
Serial.print(sbuffer);
}
lastsetting = bsetting0;
} // setBrightness
#endif
#ifdef WAKEUP
// wakeup
//
// A little "Teddy Ruxpin" inspired animation.
void wakeup() {
uint8_t i;
for (i = 0; i < 2; i++) {
closeeyes(500);
openeyes(250);
}
while(!GPS.fix) {
if (GPS.newNMEAreceived())
GPS.parse(GPS.lastNMEA());
lookaround();
//matrix0.clear();
}
} // wakeup
// Segment layout:
//
//The decimal point is mapped to 0x80.
//
// 0x01
// ----
// 0x20 | |0x02
// | |
// ----
// 0x10 |0x40|0x04
// | |
// ----
// 0x08
void closeeyes(uint8_t dly) {
matrix0.writeDigitRaw(1, 0x08);
matrix0.writeDigitRaw(3, 0x08);
matrix0.writeDisplay();
delay(dly);
} // closeeyes
void openeyes(uint8_t dly) {
matrix0.writeDigitRaw(1, 0x08 | 0x10 | 0x40 | 0x04);
matrix0.writeDigitRaw(3, 0x08 | 0x10 | 0x40 | 0x04);
matrix0.writeDisplay();
delay(dly);
} // openeyes
void lookaround() {
matrix0.clear();
matrix0.writeDigitRaw(0, 0x40 | 0x20 | 0x01 | 0x02);
matrix0.writeDigitRaw(1, 0x40 | 0x20 | 0x01 | 0x02);
matrix0.writeDisplay();
delay(1000);
//matrix0.clear();
//matrix0.writeDigitRaw(0, 0);
//matrix0.writeDigitRaw(1, 0x40 | 0x20 | 0x01 | 0x02);
//matrix0.writeDigitRaw(3, 0x40 | 0x20 | 0x01 | 0x02);
//matrix0.writeDisplay();
//delay(50);
matrix0.clear();
matrix0.writeDigitRaw(3, 0x40 | 0x20 | 0x01 | 0x02);
matrix0.writeDigitRaw(4, 0x40 | 0x20 | 0x01 | 0x02);
matrix0.writeDisplay();
delay(1000);
} // lookaround
#endif
That should get you started.