GPS clock

Hi

Im working on a project for school where I have to build an arduino leonardo powered clock. So far I have been following code that I found on the internet (see: Arduino: showing information from a GPS on a LCD). I have successfully wired up a 16x2 LCD and it displays the first part of the code "waiting for GPS" however it never progresses to the next part of the code.

The differences that I have between to tutorial is I am using an adafruit ultimate GPS - however I am following the same inputs as specified in the code (rx to D0 and tx to D1).

The seems to have fixed and blinks once every 15 seconds.

So I am really struggling to see why it won't progress past the setup lcd portion of the code.

Here is the code:

/**

#include <LiquidCrystal.h>
#include <string.h>
#include <ctype.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int rxPin = 0; // RX pin
int txPin = 1; // TX pin
int byteGPS=-1;
char cmd[7] = "$GPRMC";
int counter1 = 0; // counts how many bytes were received (max 300)
int counter2 = 0; // counts how many commas were seen
int offsets[13];
char buf[300] = "";

/**

  • Setup display and gps
    */
    void setup() {
    pinMode(rxPin, INPUT);
    pinMode(txPin, OUTPUT);
    Serial.begin(4800);
    lcd.begin(16, 2);
    lcd.print("waiting for gps");
    offsets[0] = 0;
    reset();
    }

void reset() {
counter1 = 0;
counter2 = 0;
}

int get_size(int offset) {
return offsets[offset+1] - offsets[offset] - 1;
}

int handle_byte(int byteGPS) {
buf[counter1] = byteGPS;
Serial.print((char)byteGPS);
counter1++;
if (counter1 == 300) {
return 0;
}
if (byteGPS == ',') {
counter2++;
offsets[counter2] = counter1;
if (counter2 == 13) {
return 0;
}
}
if (byteGPS == '*') {
offsets[12] = counter1;
}

// Check if we got a , which indicates the end of line
if (byteGPS == 10) {
// Check that we got 12 pieces, and that the first piece is 6 characters
if (counter2 != 12 || (get_size(0) != 6)) {
return 0;
}

// Check that we received $GPRMC
for (int j=0; j<6; j++) {
if (buf[j] != cmd[j]) {
return 0;
}
}

// Check that time is well formed
if (get_size(1) != 10) {
return 0;
}

// Check that date is well formed
if (get_size(9) != 6) {
return 0;
}

// TODO: compute and validate checksum

// TODO: handle timezone offset

// print time
lcd.clear();
for (int j=0; j<6; j++) {
lcd.print(buf[offsets[1]+j]);
if (j==1) {
lcd.print("h");
} else if (j==3) {
lcd.print("m");
} else if (j==5) {
lcd.print("s UTC");
}
}

// print date
lcd.setCursor(0, 1);
for (int j=0; j<6; j++) {
lcd.print(buf[offsets[9]+j]);
if (j==1 || j==3) {
lcd.print(".");
}
}
return 0;
}
return 1;
}

/**

  • Main loop
    */
    void loop() {
    byteGPS=Serial.read(); // Read a byte of the serial port
    if (byteGPS == -1) { // See if the port is empty yet
    delay(100);
    } else {
    if (!handle_byte(byteGPS)) {
    reset();
    }
    }
    }

thanks for any help also I am verryyyyy new to this. :0

Your handle_byte function isn't very useful because it returns zero no matter what kind of error occurs. I'd change the function so that it returns zero if it succeeds and then each of the error returns can be assigned a different number.
Having done that, in the loop function use something like this:

    int ret_error = handle_byte(byteGPS); 
    if (ret_error) {
      Serial.print("ERROR: ");
      Serial.println(ret_error);
      reset();
    }

This should give you some idea where things are going wrong.

Pete
P.S. Post your code in [code] [/code] tags.
See how to post code properly

tech_boi:
So far I have been following code that I found on the internet (see: Arduino: showing information from a GPS on a LCD)...

...The differences that I have between to tutorial is I am using an adafruit ultimate GPS

Why don't you try to get it talking by first using the code from Adafruit?

The GPS you bought defaults to 9600 baud.

tech_boi:
arduino leonardo powered clock.
,,,
Here is the code:
int rxPin = 0; // RX pin
int txPin = 1; // TX pin
...
byteGPS=Serial.read(); // Read a byte of the serial port

The Arduino LEONARDO has two serial ports:

Primary serial port ==> connected to USB ==> In your program it is "Serial"
Seccondary serial port ==> connected to D0/D1 ==> In your program it is "Serial1"

So if you have connected your GPS to D0/D1, you read your GPS by reading from "Serial1" and not from "Serial".

byteGPS=Serial1.read();         // Read a byte from the GPS module connected on Leonardo DO/D1

Thanks guys - you have got me a lot farther. I have modified the code so that it pulls data from serial1 and

"Serial1.begin(9600);" so that it reads at 9600 baud. However the LCD still reads waiting for GPS even after

the GPS has fixed. Does anybody have any ideas? Here is my code

/**
 * Code to display the time & date from a GPS receiver on a LCD.
 *
 * This code was inspired by http://arduino.cc/en/Tutorial/LiquidCrystal and
 * http://playground.arduino.cc/Tutorials/GPS
 *
 * For more information, see http://quaxio.com/arduino_gps/
 */
 
#include <LiquidCrystal.h>
#include <string.h>
#include <ctype.h>
 
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
 
int rxPin = 0; // RX pin
int txPin = 1; // TX pin
int byteGPS=-1;
char cmd[7] = "$GPRMC";
int counter1 = 0; // counts how many bytes were received (max 300)
int counter2 = 0; // counts how many commas were seen
int offsets[13];
char buf[300] = "";
 
/**
 * Setup display and gps
 */
void setup() {
  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);
  Serial1.begin(9600);
  lcd.begin(16, 2);
  lcd.print("waiting for gps");
  offsets[0] = 0;
  reset();
}
 
void reset() {
  counter1 = 0;
  counter2 = 0;
}
 
int get_size(int offset) {
  return offsets[offset+1] - offsets[offset] - 1;
}
 
int handle_byte(int byteGPS) {
  buf[counter1] = byteGPS;
  Serial1.print((char)byteGPS);
  counter1++;
  if (counter1 == 300) {
    return 0;
  }
  if (byteGPS == ',') {
    counter2++;
    offsets[counter2] = counter1;
    if (counter2 == 13) {
      return 0;
    }
  }
  if (byteGPS == '*') {
    offsets[12] = counter1;
  }
 
  // Check if we got a <LF>, which indicates the end of line
  if (byteGPS == 10) {
    // Check that we got 12 pieces, and that the first piece is 6 characters
    if (counter2 != 12 || (get_size(0) != 6)) {
      return 0;
    }
 
    // Check that we received $GPRMC
    for (int j=0; j<6; j++) {
      if (buf[j] != cmd[j]) {
        return 0;
      }
    }
 
    // Check that time is well formed
    if (get_size(1) != 10) {
      return 0;
    }
 
    // Check that date is well formed
    if (get_size(9) != 6) {
      return 0;
    }
 
    // TODO: compute and validate checksum
 
    // TODO: handle timezone offset
 
    // print time
    lcd.clear();
    for (int j=0; j<6; j++) {
      lcd.print(buf[offsets[1]+j]);
      if (j==1) {
        lcd.print("h");
      } else if (j==3) {
        lcd.print("m");
      } else if (j==5) {
        lcd.print("s UTC");
      }
    }
 
    // print date
    lcd.setCursor(0, 1);
    for (int j=0; j<6; j++) {
      lcd.print(buf[offsets[9]+j]);
      if (j==1 || j==3) {
        lcd.print(".");
      }
    }
    return 0;
  }
  return 1;
}
 
/**
 * Main loop
 */
void loop() {
  byteGPS=Serial1.read();         // Read a byte of the serial port
  if (byteGPS == -1) {           // See if the port is empty yet
    delay(100);
  } else {
    if (!handle_byte(byteGPS)) {
      reset();
    }
  }
}

I would change the following

int handle_byte(int byteGPS) {
  buf[counter1] = byteGPS;
  Serial1.print((char)byteGPS);

to output the byte on Serial instead of Serial1, so you can read it on the serial monitor (via USB serial.) Then you will know if the Arduino is getting anything at all from the GPS.

There's probably a bug in the handle_byte function but you aren't going to figure it out because it doesn't tell you anything when things do go wrong.
Reread my previous post.

Pete

With GPS, there are just too many things to go wrong in the receive process, to debug in tandem with a new sketch. You really must verify that you are receiving valid NMEA sentences before proceeding. Unless you like to waste your time.

The safest way to do this, is to load a sketch that echoes the stream and look at it directly on the serial monitor.

OK - I am using the adafruit leonardo echo sketch to read out all the data from the GPS into serial. Its working, as far as I can tell. I can't get it to pull from serial1 but it does work on pins 7 and 8.

Well if you can't get it on serial1 and that's what drives the sketch, then that's your problem right there.

tech_boi:
I can't get it to pull from serial1

Did you perhaps mix up RX and TX? They need to be "crossed".

So the TX (transmit) pin on the GPS module is to be connected to the RX (receive) pin on the Arduino board.

Here is some programming example I tested with an UNO and "Serial1" is emulated by using AltSoftSerial. Should also work with Leonardo (but not tested) and with LEONARDO the hardware Serial1 will be used.

This sketch does not only extract the UTC time from the GPS, but can also correct the time using a "timezone" setting and format the time in hh:mm:ss formatting.

include <Arduino.h>
#define TIMEZONE 2  // time zone in hours relative to UTC time

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)

#include <AltSoftSerial.h>
// AltSoftSerial always uses these pins:
//
// Board          Transmit  Receive   PWM Unusable
// -----          --------  -------   ------------
// Teensy 3.0 & 3.1  21        20         22
// Teensy 2.0         9        10       (none)
// Teensy++ 2.0      25         4       26, 27
// Arduino Uno        9         8         10
// Arduino Leonardo   5        13       (none)
// Arduino Mega      46        48       44, 45
// Wiring-S           5         6          4
// Sanguino          13        14         12
AltSoftSerial Serial1;
#endif


void setup() {
  Serial.begin(9600); // begin USB-Serial to Serial Monitor
  Serial.println("Test Begin");
  Serial1.begin(9600); // begin GPS Serial
}

char* getGPSline()
{
  static char buf[81];
  static byte count=0;
  if (!Serial1.available()) return NULL;
  char c=Serial1.read();
  if (c>=32 && count<sizeof(buf)-1)
  {
    if (count==0) memset(buf,0,sizeof(buf));  
    buf[count]=c;
    count++;
  }
  else if (c=='\n')
  {
    count=0;
    return buf;
  }
  return NULL;
}

long readTime(char* line)
{
  char GPRMC[]="$GPRMC,";
  if (strstr(line,GPRMC)!=line) return -1; // no valid time is returned as -1
  return atol(&line[strlen(GPRMC)]);
}

void loop() {
  char* gpsLine=getGPSline();
  long time;
  if (gpsLine != NULL)
  {
//    Serial.println(gpsLine); // debug: print each GPS line
    time=readTime(gpsLine);
    if (time>=0)
    {
      time= (time+TIMEZONE*10000L)%240000L; // correct by local timezone
      int hours= time/10000L;
      time= time%10000L;
      int minutes= time/100;
      int seconds= time%100;
      char timebuf[10];
      snprintf(timebuf,sizeof(timebuf),"%02d:%02d:%02d", hours, minutes, seconds);
      Serial.println(timebuf); 
    }
  }
}

BTW: At the end of each NMEA sentence there is a CRC checksum. Until now, this sketch does NOT test the CRC checksum. But it would be possible to add NMEA CRC checksum testing.

Edit: I just noticed that the timezone correction will only work correctly with positive Timezones UTC+TZ. If your timezone is negative, you'd need a small change with the timezone correction.

Jurs - thanks this is so helpful. I tried to simply include the LCD library and tried to get it to print the "timebuf" output to an lcd. This didn't work - is there some way I need to "repackage" the data so that it will display on a small 16x2 lcd? I tried both lcd.print(timebuf) and lcd.println(timebuf)

EDIT: now trying to attach: http://www.arduino.cc/en/Tutorial/LiquidCrystalSerial to the end of the existing code.

I am getting what appears to be an echo output on the LCD.

tech_boi:
Jurs - thanks this is so helpful. I tried to simply include the LCD library and tried to get it to print the "timebuf" output to an lcd. This didn't work - is there some way I need to "repackage" the data so that it will display on a small 16x2 lcd? I tried both lcd.print(timebuf) and lcd.println(timebuf)

...

I am getting what appears to be an echo output on the LCD.

Your "echo" output is the NMEA "raw data".
That line is too long to show on LCD.
NMEA lines can be up to 79 chars wide.
Your LCD is just 16 chars wide.

You better only show the timebuf on LCD.
For doing so you need to position the cursor where you want to start.
Left column, first row would be: 0,0
Left column, second row would be: 0,1

This should work for LCD output:

  snprintf(timebuf,sizeof(timebuf),"%02d:%02d:%02d", hours, minutes, seconds);
  Serial.println(timebuf); 
  lcd.setCursor(0,0);
  lcd.print(timebuf);

What about your timezone? Is it UTC+0, UTC+hours, UTC-hours?
Is there a daylight saving time during summer months?

If you also want to have the correct date, you not only have to consider the timezone for calculating local time, but also calculating local date. A GPS receiver always returns UTC time and UTC date only.

For example, I'm in Germany. In winter my local time is UTC+1, but during summer months local time is UTC+2. This means: In summer I'm at midnight 00:00 two hours before UTC time, and also my date switches to the next day two hours before the next day switches in UTC time.

So if you always want to have correct time AND correct date, you will need some date handling in your sketch, too. Please let me know about your timezone and possibly daylight saving time scheduling, then I could include that.

So if you always want to have correct time AND correct date, you will need some date handling in your sketch, too. Please let me know about your timezone and possibly daylight saving time scheduling, then I could include that.

[/quote]

I live in california. PST time zone.

As for the daylight savings: Time Zone & Clock Changes in San Francisco, California, USA

tech_boi:
I live in california. PST time zone.

As for the daylight savings: Time Zone & Clock Changes in San Francisco, California, USA

I see. US daylight saving time from second Sunday in March until first Sunday in November. Different date than DST in Europe. But somewhat similar.

And in addition, other than in Europe, the DST switching hour in the US is at a fixed local time and not at a fixed UTC time.

So I'm just trying to get what this is in UTC (as UTC is what the GPS module provides):

DST begin: second Sunday in March 02:00 local time (-8) is 10:00 UTC (same day)
DST end: first Sunday in November 02:00 local time (-7) is 09:00 UTC (same day)
At least the switching day is the same in local time and in UTC time.

I now have to think over it to find some efficient code for automatic DST switching.
Or perhaps do some googling, too.
I'll be back when I'm ready with some additional code for automatic local time calculation, including DST switching automatically.
But it may take some days.

(rx to D0 and tx to D1).

Probably, the wrong way around.

The first thing to do, is write a very simple sketch which just copies the output from the gps to the serial monitor, to check that the gps is working and connected properly.

void loop() {
  byteGPS=Serial1.read();         // Read a byte of the serial port
  if (byteGPS == -1) {           // See if the port is empty yet
    delay(100);
  } else {
    if (!handle_byte(byteGPS)) {
      reset();
    }
  }

This bit of code looks quite stupid , in about 6 different ways. Where did you get such an idea from ?

When the gps is sending you strings of about 100 chars quite quickly, you don't want to be including long do-nothing delays in your code. Reseting the arduino each time you get a character that is wrong, is also a very poor idea.

for my purposes at this point just getting the right time and not worrying about DST is fine. My time zone is UTC -7, how would I modify the code to work with this? Thanks

Right now its outputting at UTC +2 (germany time zone)

tech_boi:
for my purposes at this point just getting the right time and not worrying about DST is fine. My time zone is UTC -7, how would I modify the code to work with this? Thanks

Right now its outputting at UTC +2 (germany time zone)

essentially I don't understand how this bit of the code works:

time= (time+TIMEZONE*10000L)%240000L; // correct by local timezone

Nevermind. I got this part of the code to work. It now reads the right time.

tech_boi:
essentially I don't understand how this bit of the code works:

time= (time+TIMEZONE*10000L)%240000L; // correct by local timezone

Nevermind. I got this part of the code to work. It now reads the right time.

Essentially the time is read into a 'long' variable with 6 digits, where the first 2 digits are hours, the next 2 digits are minutes and the last 2 digits are seconds.

The number 202430 would be 20:24:30 when displayed.

So:
240000 represents 24 hours, 0 minuts, 0 seconds
10000 represents 1 hour, 0 minutes, 0 seconds

The term 'TIMEZONE*10000' therefore is the local time offset in your timezone.

But watch out: If TIMEZONE is negative, the whole result may become negative!
In your case: During the first 7 hours of the day, the expression '(time+TIMEZONE*10000L)' is negative and you will calculate a negative time from it.

That's not very useful.

So with negative TIMEZONE values you perhaps make sure that the value is always positive and calculate:

time= (time+240000L+TIMEZONE*10000L)%240000L; // correct by local timezone

That should create the right time. Also during the first hours of the day.

In the US this 24-hour time format is called "military time format".
Do you want that?

It would also be possible to get 12-hour AM/PM time format with some additional calculations.

What other functions do you need for the clock?
Time counting up by using the microcontroller frequency, even after the GPS signal is lost?
Or don't you need any backup time counting, and when GPS signal is lost, the clock stands still?
Possibly "day of the week" display?

I'm currently working on automatic DST switching.
Looks promising.
But it is a little bit tricky to get the DST switching accurate to the second.