I’m making a clock that is pulling the time in via Adafruit’s Ultimate GPS module. It pulls in the time, then uses that to update a DS3231 RTC. I am using an Arduino Mega 2650. My code is based off Adafruit’s Echo and Parsing sketches.
http://kevinrye.net/files/arduino2560_0016-16.jpg
Here’s the problem: I’m also controlling the clock via an IR Remote. I’m using SparkFun’s IR remote and SEN-08554 Receiver, along with the sketch and library by Ken Shirriff that they link to:
https://www.sparkfun.com/products/11759
https://www.sparkfun.com/products/8554
The clock has an alarm menu and a settings menu that I can bring up with the remote. I can then use the remote to set options like daylight savings, time zone, turn alarm on/off, etc.
The IR stuff works flawlessly on the Mega. The GPS stuff works flawlessly on the Mega. However, they do not work very well when both in the same sketch. Sometimes I have to press a button on my remote up to 10 times before it’s picked up. Most of the time, it doesn’t pick it up at all.
Any pointers with the GPS library or the IR sketch based off Ken Shirriff’s code that causes some kind of complication when used together would be appreciated. Is there some kind of consideration I’ve missed? I even changed the GPS breakout to use another TX/RX (pins 18/19) (hardware) and the IR receiver to use TIMER5 (pin 46) per the library. Still no luck. I thought by using hardware serial instead of software serial, I’d eliminate any problems communicating with both devices.
Here’s my code:
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include "DS3231.h"
#include <Wire.h>
#include <glcd.h>
#include <fonts/SystemFont5x7.h>
#include <fonts/Arial14.h>
#include <fonts/Arial_bold_14.h>
#include <fonts/fixednums7x15.h>
#include <fonts/fixednums8x16.h>
#include <fonts/fixednums15x31.h>
#include <IRremote.h>
DS3231 RTC; //Create the DS3231 object
// If you're using a GPS module:
// 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 3
// Connect the GPS RX (receive) pin to Digital 2
// 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
// If you're using the Adafruit GPS shield, change
// SoftwareSerial mySerial(3, 2); -> SoftwareSerial mySerial(8, 7);
// and make sure the switch is set to SoftSerial
// If using software serial, keep these lines enabled
// (you can change the pin numbers to match your wiring):
//SoftwareSerial mySerial(10, 11);
//Adafruit_GPS GPS(&mySerial);
// If using hardware serial (e.g. Arduino Mega), comment
// out the above six lines and enable this line instead:
Adafruit_GPS GPS(&Serial1);
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences.
#define GPSECHO true
// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy
// Select the font for the default text area
/*
SystemFont5x7 // system font
Arial14 // proportional font
Arial_bold_14 // Bold proportional font
fixednums7x15 // fixed width font - numerals only
fixednums8x16 // fixed width font - numerals only
fixednums15x31 // fixed width font - numerals only
*/
//remote
int RECV_PIN = 46; //pin 9 is for the Mega per Timer2 in the IR library
IRrecv irrecv(RECV_PIN);
decode_results results;
#define POWER 0x10EFD827
#define A 0x10EFF807
#define B 0x10EF7887
#define C 0x10EF58A7
#define UP 0x10EFA05F
#define DOWN 0x10EF00FF
#define LEFT 0x10EF10EF
#define RIGHT 0x10EF807F
#define SELECT 0x10EF20DF
//temp
int tempC;
int tempF;
//initial condition for menu display
boolean enterNormalMode = true;
boolean enterSettingsMode = false;
boolean enterAlarmMode = false;
boolean chimeOnHour = false;
boolean alarmIsActive = false;
//settings menu
int settingsModeButtonCount;
boolean gmtSelected = false;
boolean dlsSelected = false;
boolean chimeSelected = false;
//alarm menu
int alarmModeButtonCount;
boolean aTimeSelected = false;
boolean activeSelected = false;
//compensations
int TZAdjust;
int newHours;
int DaylightSavings;
boolean DLSinEffect = false;
//year, month, date, hour, min, sec and week-day(starts from 0 and goes to 6)
//writing any non-existent time-data may interfere with normal operation of the RTC.
//Take care of week-day also.
//DateTime dt(2013, 11, 2, 22, 03, 00, 6);
DateTime dt(2015, 8, 12, 4, 15, 30, 6);
int hhRTC;
int hhAdj;
int mmRTC;
int ssRTC;
int dayRTC;
int dowRTC;
int monthRTC;
int yearRTC;
int hhGPS;
int mmGPS;
int ssGPS;
int dayGPS;
int monthGPS;
int yearGPS;
int linkStatus;
void setup() {
// connect at 115200 so we can read the GPS fast enough and echo without dropping chars
Serial.begin(115200);
Serial.println("Adafruit GPS library basic test");
// 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
GPS.begin(9600);
// uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
// GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
// uncomment this line to turn on only the "minimum recommended" data
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
// For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
// the parser doesn't care about other sentences at this time
// Set the update rate
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate
// For the parsing code to work nicely and have time to sort thru the data, and
// print it out we don't suggest using anything higher than 1 Hz
// Request updates on antenna status, comment out to keep quiet
//GPS.sendCommand(PGCMD_ANTENNA);
// 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);
delay(1000);
// Ask for firmware version
//mySerial.println(PMTK_Q_RELEASE);
Serial.println(PMTK_Q_RELEASE);
// Initialize the GLCD
GLCD.Init();
Wire.begin();
RTC.begin();
//remote
irrecv.enableIRIn(); // Start the receiver
//unhide to force time setting of the RTC
// RTC.adjust(dt); //Adjust date-time as defined 'dt' above
}
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
char c = GPS.read();
// if you want to debug, this is a good time to do it!
#ifdef UDR0
if (GPSECHO)
if (c) UDR0 = c;
// writing direct to UDR0 is much much faster than Serial.print
// but only one character can be written at a time.
#endif
}
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 = false;
} else {
// do not call the interrupt function COMPA anymore
TIMSK0 &= ~_BV(OCIE0A);
usingInterrupt = false;
}
}
uint32_t timer = millis();
void loop()
{
////////////////////////////////////////////////////////////////////////////////
//remote
if (irrecv.decode(&results)) {
if (results.value == POWER)
{
Serial.println("POWER");
syncGPSTime();
}
if (results.value == A)
{
Serial.println("A");
enterNormalMode = true;
enterSettingsMode = false;
enterAlarmMode = false;
GLCD.ClearScreen();
}
if (results.value == B)
{
Serial.println("B");
//enter menu in default state so no options are highlighted
settingsModeButtonCount = 3;
enterNormalMode = false;
enterSettingsMode = true;
enterAlarmMode = false;
GLCD.ClearScreen();
}
if (results.value == C)
{
Serial.println("C");
//enter menu in default state so no options are highlighted
alarmModeButtonCount = 2;
enterNormalMode = false;
enterSettingsMode = false;
enterAlarmMode = true;
GLCD.ClearScreen();
}
if (results.value == UP)
{
Serial.println("UP");
if(enterSettingsMode == true) {
settingsModeButtonCount++;
if(settingsModeButtonCount == 4) {
settingsModeButtonCount = 0;
}
}
if(enterAlarmMode == true) {
alarmModeButtonCount++;
if(alarmModeButtonCount == 3) {
alarmModeButtonCount = 0;
}
}
}
if (results.value == DOWN)
{
Serial.println("DOWN");
if(enterSettingsMode == true) {
settingsModeButtonCount--;
if(settingsModeButtonCount < 0) {
settingsModeButtonCount = 3;
}
}
if(enterAlarmMode == true) {
alarmModeButtonCount--;
if(alarmModeButtonCount < 0) {
alarmModeButtonCount = 2;
}
}
}
if (results.value == LEFT)
{
Serial.println("LEFT");
if(gmtSelected == true) {
TZAdjust--;
//time zone changed - resync with GPS clock
syncGPSTime();
}
if(dlsSelected == true) {
DLSinEffect = !DLSinEffect;
}
if(chimeSelected == true) {
chimeOnHour = !chimeOnHour;
}
if(aTimeSelected == true) {
}
if(activeSelected == true) {
alarmIsActive = !alarmIsActive;
}
}
if (results.value == RIGHT)
{
Serial.println("RIGHT");
if(gmtSelected == true) {
TZAdjust++;
//time zone changed - resync with GPS clock
syncGPSTime();
}
if(dlsSelected == true) {
DLSinEffect = !DLSinEffect;
}
if(chimeSelected == true) {
chimeOnHour = !chimeOnHour;
}
if(aTimeSelected == true) {
}
if(activeSelected == true) {
alarmIsActive = !alarmIsActive;
}
}
if (results.value == SELECT)
{
Serial.println("SELECT");
//set totally wrong date and time (so that a GPS sync can be tested)
DateTime dt(2011, 1, 1, 1, 1, 0, 6);
RTC.adjust(dt);
delay(10);
GLCD.ClearScreen();
}
irrecv.resume();
}
////////////////////////////////////////////////////////////////////////////////
//GPS
// in case you are not using the interrupt above, you'll
// need to 'hand query' the GPS, not suggested :(
if (! usingInterrupt) {
// read data from the GPS in the 'main loop'
char c = GPS.read();
// if you want to debug, this is a good time to do it!
if (GPSECHO)
if (c) Serial.print(c);
}
// if a sentence is received, we can check the checksum, parse it...
if (GPS.newNMEAreceived()) {
// a tricky thing here is if we print the NMEA sentence, or data
// we end up not listening and catching other sentences!
// so be very wary if using OUTPUT_ALLDATA and trytng to print out data
//Serial.println(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
return; // we can fail to parse a sentence in which case we should just wait for another
}
// if millis() or timer wraps around, we'll just reset it
//if (timer > millis()) timer = millis();
// approximately every 2 seconds or so, print out the current stats
if (millis() - timer > 10000) {
timer = millis(); // reset the timer
Serial.print("\nTime: ");
Serial.print(GPS.hour, DEC); Serial.print(':');
Serial.print(GPS.minute, DEC); Serial.print(':');
Serial.print(GPS.seconds, DEC); Serial.print('.');
Serial.println(GPS.milliseconds);
Serial.print("Date: ");
Serial.print(GPS.day, DEC); Serial.print('/');
Serial.print(GPS.month, DEC); Serial.print("/20");
Serial.println(GPS.year, DEC);
Serial.print("Fix: "); Serial.print((int)GPS.fix);
Serial.print(" quality: "); Serial.println((int)GPS.fixquality);
if (GPS.fix) {
Serial.print("Location: ");
Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
Serial.print(", ");
Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
Serial.print("Speed (knots): "); Serial.println(GPS.speed);
Serial.print("Angle: "); Serial.println(GPS.angle);
Serial.print("Altitude: "); Serial.println(GPS.altitude);
Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
}
}
//////////////////////////////////////////////////////////////
//RTC
DateTime now = RTC.now();
tempC = RTC.getTemperature();
tempF = (tempC * 9.0)/ 5.0 + 32.0; // Convert C to F
hhRTC = now.hour(); //used to check AM/PM status
mmRTC = now.minute();
ssRTC = now.second();
dowRTC = now.dayOfWeek();
dayRTC = now.date();
monthRTC = now.month();
yearRTC = now.year();
//pass RTC values into a new variable -
//this way one variable stores time in 24 hour mode and another that can be converted to 12 hour mode
//time is monitored 'in the background' in 24 hour mode using the RTC, but is displayed in an adjusted 12-hr format
hhAdj = hhRTC;
//need to add compensation for the date so the date doesn't roll over 4 hours early
//convert to 12 hr mode for display
if (hhRTC < 1) {
hhAdj = 12;
}
if ((hhRTC > 12) && (hhRTC < 24)) {
hhAdj = hhRTC - 12;
}
//DLS compensation
if (DLSinEffect == false) {
DaylightSavings = 0; //DSL not in effect, don't add an additional hour
}
else {
DaylightSavings = 1; //DSL in effect, add an additional hour
}
newHours = ((hhAdj) + TZAdjust + DaylightSavings);
////////////////////////////////////////////////////////////////////////////////
//not in menu mode
//time from RTC
if (enterNormalMode == true) {
//hour
GLCD.SelectFont(fixednums15x31);
GLCD.CursorTo(0,0);
if (newHours < 10) {
GLCD.print("0");
}
GLCD.print(newHours);
//minute
if (mmRTC < 10) {
GLCD.print(":0");
}
else {
GLCD.print(":");
}
GLCD.print(mmRTC);
//second
GLCD.SelectFont(fixednums7x15);
if (ssRTC < 10) {
GLCD.print(":0");
}
else {
GLCD.print(":");
}
GLCD.print(ssRTC);
// AM/PM indicator
GLCD.SelectFont(SystemFont5x7);
if ((hhRTC > 12) && (hhRTC < 22)) {
//PM
GLCD.print(" PM");
}
else {
//AM
GLCD.print(" AM");
}
//second line
//date
GLCD.CursorTo(0,4);
GLCD.SelectFont(Arial14);
switch(dowRTC){
case 0:
GLCD.print("Sun ");
break;
case 1:
GLCD.print("Mon ");
break;
case 2:
GLCD.print("Tue ");
break;
case 3:
GLCD.print("Wed ");
break;
case 4:
GLCD.print("Thu ");
break;
case 5:
GLCD.print("Fri ");
break;
case 6:
GLCD.print("Sat ");
break;
}
GLCD.SelectFont(fixednums7x15);
GLCD.print(monthRTC);
GLCD.print("/");
GLCD.print(dayRTC);
GLCD.print("/");
GLCD.print(yearRTC - 2000);
//temp
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(fixednums8x16);
GLCD.print(tempF);
GLCD.SelectFont(SystemFont5x7);
GLCD.print("F");
//draw line
GLCD.DrawLine(0,51,128,51,BLACK);
//GPS status
linkStatus = (GPS.fixquality);
GLCD.SelectFont(SystemFont5x7);
GLCD.CursorTo(0,7);
if (linkStatus < 1) {
GLCD.print("GPS:NO ");
}
else {
GLCD.print("GPS:YES");
}
//alarm
GLCD.print(" ALARM:");
if (alarmIsActive == true) {
GLCD.print("6:30AM");
}
else {
GLCD.print("OFF");
}
}
////////////////////////////////////////////////////////////////////////////////
//display settings menu
if (enterSettingsMode == true) {
GLCD.ClearScreen();
//settings menu header
GLCD.SelectFont(SystemFont5x7);
GLCD.CursorTo(0,0);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
GLCD.print("SETTINGS");
//draw line
GLCD.DrawLine(0,12,128,12,BLACK);
if (settingsModeButtonCount == 3) { //no option highlighted
gmtSelected = false;
dlsSelected = false;
chimeSelected = false;
GLCD.SelectFont(Arial14);
//time zone
GLCD.CursorTo(0,1);
GLCD.print("GMT:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
GLCD.print(TZAdjust);
//daylight savings
GLCD.CursorTo(0,2);
GLCD.print("DLS:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (DLSinEffect == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
//chime on hour
GLCD.CursorTo(0,3);
GLCD.print("CHIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (chimeOnHour == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
if (settingsModeButtonCount == 2) { //GMT highlighted
gmtSelected = true;
dlsSelected = false;
chimeSelected = false;
GLCD.SelectFont(Arial_bold_14);
//time zone
GLCD.CursorTo(0,1);
GLCD.print("GMT:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
GLCD.print(TZAdjust);
GLCD.SelectFont(Arial14);
//daylight savings
GLCD.CursorTo(0,2);
GLCD.print("DLS:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (DLSinEffect == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
//chime on hour
GLCD.CursorTo(0,3);
GLCD.print("CHIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (chimeOnHour == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
if (settingsModeButtonCount == 1) { //DLS highlighted
gmtSelected = false;
dlsSelected = true;
chimeSelected = false;
GLCD.SelectFont(Arial14);
//time zone
GLCD.CursorTo(0,1);
GLCD.print("GMT:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
GLCD.print(TZAdjust);
GLCD.SelectFont(Arial_bold_14);
//daylight savings
GLCD.CursorTo(0,2);
GLCD.print("DLS:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
if (DLSinEffect == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
//chime on hour
GLCD.SelectFont(Arial14);
GLCD.CursorTo(0,3);
GLCD.print("CHIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (chimeOnHour == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
if (settingsModeButtonCount == 0) { //CHIME highlighted
gmtSelected = false;
dlsSelected = false;
chimeSelected = true;
GLCD.SelectFont(Arial14);
//time zone
GLCD.CursorTo(0,1);
GLCD.print("GMT:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
GLCD.print(TZAdjust);
//daylight savings
GLCD.CursorTo(0,2);
GLCD.print("DLS:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (DLSinEffect == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
//chime on hour
GLCD.SelectFont(Arial_bold_14);
GLCD.CursorTo(0,3);
GLCD.print("CHIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
if (chimeOnHour == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
//add delay to lower refresh rate on the display (remove blink)
delay(200);
}
////////////////////////////////////////////////////////////////////////////////
//display alarm menu
if (enterAlarmMode == true) {
GLCD.ClearScreen();
//settings menu header
GLCD.SelectFont(SystemFont5x7);
GLCD.CursorTo(0,0);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
GLCD.print(" ALARM");
//draw line
GLCD.DrawLine(0,12,128,12,BLACK);
if (alarmModeButtonCount == 2) {//no option highlighted
aTimeSelected = false;
activeSelected = false;
GLCD.SelectFont(Arial14);
//alarm
GLCD.CursorTo(0,1);
GLCD.print("TIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
GLCD.print("6:30 AM");
//alarm status
GLCD.CursorTo(0,2);
GLCD.print("ACTIVE:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (alarmIsActive == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
if (alarmModeButtonCount == 1) {//TIME option highlighted
aTimeSelected = true;
activeSelected = false;
GLCD.SelectFont(Arial14);
//alarm
GLCD.CursorTo(0,1);
GLCD.SelectFont(Arial_bold_14);
GLCD.print("TIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
GLCD.print("6:30 AM");
//alarm status
GLCD.CursorTo(0,2);
GLCD.SelectFont(Arial14);
GLCD.print("ACTIVE:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
if (alarmIsActive == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
if (alarmModeButtonCount == 0) {//ALARM option highlighted
aTimeSelected = false;
activeSelected = true;
GLCD.SelectFont(Arial14);
//alarm
GLCD.CursorTo(0,1);
GLCD.SelectFont(Arial14);
GLCD.print("TIME:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial14);
GLCD.print("6:30 AM");
//alarm status
GLCD.CursorTo(0,2);
GLCD.SelectFont(Arial_bold_14);
GLCD.print("ACTIVE:");
GLCD.SelectFont(SystemFont5x7);
GLCD.print(" ");
GLCD.SelectFont(Arial_bold_14);
if (alarmIsActive == true) {
GLCD.print("YES");
}
else {
GLCD.print("NO");
}
}
//add delay to lower refresh rate on the display (remove blink)
delay(200);
}
} //end of loop
////////////////////////////////////////////////////////////////////////////////
//Get GPS time
void syncGPSTime() {
//pass GPS time/date into the DS3231 RTC
//for now the day of week is hard coded until an automatic calcutaion can be made
hhGPS = (GPS.hour);
mmGPS = (GPS.minute);
ssGPS = (GPS.seconds);
dayGPS = (GPS.day);
monthGPS = (GPS.month);
yearGPS = (GPS.year);
// linkStatus;
//year, month, date, hour, min, sec and week-day(starts from 0 and goes to 6)
DateTime dt(yearGPS, monthGPS, dayGPS, hhGPS, mmGPS, ssGPS, dowRTC);
RTC.adjust(dt); //Adjust date-time as defined 'dt' above
delay(10);
GLCD.ClearScreen();
}