Loosing GPS.fix with LCD.print

I’m building a Rover that has the Adafruit Ultimate GPS module on it, I am controlling it via Xbees with a controller that has 2 joysticks, sending the servo references to the rover. I also have a LCD display that is showing me the latitude and longitude. I’ve been able to get both the control and the GPS working but not at the same time. I’ve done some troubleshooting, revising and removing parts of the code and I’ve gotten it down to this problem.

I started with Adafruits GPS Parsing sketch. If I put the LCD.print statements in with the Serial.print statements it works fine. If I put the LCD.print statements outside of the GPS logic, at end of void loop, I loose GPS.fix. I have the same problem when I try to add any other functions, like the servo control, to this sketch. My sketch, below, shows the 2 places I’ve put the LCD.print statements, the one that makes it not work is commented out.

I’m using a Arduino Mega, GPS is on Serial3, Xbee on Serial1, I’ve left Serial for monitoring.

Obviously there is some conflict here, maybe with the GPS statements or the different baud rates but I can’t seem to figure it out.

Thanks for any help
John

// Original GPS Parsing sketch from Adafruit with Wifi added
// and commented lines removed

// Add Libraries
     
#include <Adafruit_GPS.h>
#include <LiquidCrystal.h> 

// what's the name of the hardware serial port?
#define GPSSerial Serial3 // changed from Serial1

// Connect to the GPS on the hardware port
Adafruit_GPS GPS(&GPSSerial);
     
// 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 false

uint32_t timer = millis();

// initialize the lcd library with the numbers of the interface pins
LiquidCrystal lcd(33, 34, 35, 36, 37, 38);

 // temporary array for parsing data from controller
const byte numChars = 64;
char receivedChars[numChars];
char tempChars[numChars];        

    // variables to hold the parsed data from controller
int Throttle_Ref = 0;
int Steering_Ref = 0;
int ThrottleOutValue =0;
int SteeringOutValue =0;
unsigned long Comm_Time = 0;
unsigned long Comm_Time_2 =0;
int Comm_Delay_Time = 1000;
int index = 0;

boolean newData = false;

// Variables to convert GPS NMEA values to signed degrees
float deg; //Will hold positin data in simple degree format
float degWhole; //Variable for the whole part of position 
float degDec;  //Variable for the decimal part of degree
float E_Long; // Google Earth format of longitude
float E_Lat; // Google Earth format of latitude


void setup()
{
  //while (!Serial);  // uncomment to have the sketch wait until Serial is ready
  
  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

//Serial port for Xbee from controller
  Serial1.begin(9600); 

// set up the LCD's number of columns and rows:
  lcd.begin(20, 4);
     
  // 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);

  delay(1000);
  
  // Ask for firmware version
  GPSSerial.println(PMTK_Q_RELEASE);
}

void loop() // run over and over again
{
  // 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 > 2000) {
    timer = millis(); // reset the timer

      Serial.print("GPS.fix =  "); Serial.println((int)GPS.fix);
     
      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);
      
         // ******* Print to LCD display  *******
         // GPS.fix good when LCD.prints are here
    lcd.setCursor(0, 0);
    lcd.print("Lat = ");
    lcd.print(GPS.latitude, 4); // lcd.print(GPS.latitude);
    lcd.setCursor(0, 1);
    lcd.print("Lon = ");
    lcd.print(GPS.longitude, 4); // lcd.print(GPS.longitude);
    
    } // end of if (GPS.fix)
  } // end of if (millis() - timer > 2000) loop

  // Clear Serial Buffer
while (Serial.available() > 0) {
    Serial.read();
} // end while (Serial.available() 

         // ******* Print to LCD display  *******
         // GPS.fix lost when LCD.prints are here
 /*   lcd.setCursor(0, 0);
    lcd.print("Lat = ");
    lcd.print(GPS.latitude, 4); // lcd.print(GPS.latitude);
    lcd.setCursor(0, 1);
    lcd.print("Lon = ");
    lcd.print(GPS.longitude, 4); // lcd.print(GPS.longitude);
 */   
} // end void loop

If I put the LCD.print statements in with the Serial.print statements it works fine. If I put the LCD.print statements outside of the GPS logic, at end of void loop, I loose GPS.fix. I have the same problem when I try to add any other functions, like the servo control, to this sketch.

Yes, if the sketch spends all its time updating the LCD (every time through loop), it will lose some GPS characters. The GPSserial input buffer overflows because the LCD prints have not finished.

Remember that loop will run thousands and thousands of times per second. Instead of trying to update the LCD as fast as you can (every time through loop), only update the LCD when the displayed information has changed. For those prints, you should only update the LCD when the GPS data is updated: once per second (at the most). Once every two seconds is also OK.

A secondary problem is synchronizing the GPS updates (once per second) with the LCD updates (approx once every two seconds. Because the Arduino clock is not that accurate, the millis times will “drift” against the true GPS clock. Sometimes the Arduino will update the LCD between two GPS updates (the GPS quiet time), and sometimes the Arduino will update the LCD during a GPS update. If it takes too long to update the LCD, the GPSserial input buffer will overflow.

Instead, update the LCD only when the GPS information changes. Your GPS device is sending two sentences per second: RMC & GGA, according to the the configuration command you send. So you would have to wait for both of those sentences to arrive, and then start updating the LCD. Unfortunately, this is a little awkward with the Adafruit_GPS library.

My NeoGPS library groups all those sentences received into one “fix” per second. This is a structure that contains all the fields that were reported by all of those sentences. The fix structure contains both the lat & lon from RMC and the altitude from GGA (all fields described here). It will make one fix available per second, not two sentences (or more) per second.

Here is a NeoGPS version of your sketch that updates the display exactly once every two seconds:

// GPS Parsing sketch using NeoGPS with Wifi added
// and commented lines removed

// Add Libraries

#include <NMEAGPS.h>
#include <LiquidCrystal.h>

// what's the name of the hardware serial port?
#define GPSserial Serial3 // changed from Serial1

NMEAGPS GPS;
gps_fix fix;
uint8_t fixCount;

// initialize the lcd library with the numbers of the interface pins
LiquidCrystal lcd(33, 34, 35, 36, 37, 38);

// arrays for parsing data from controller
const size_t MAX_CHARS = 64;
      char   receivedChars[ MAX_CHARS ];
      char   tempChars    [ MAX_CHARS ];
      size_t count = 0;

// variables to hold the parsed data from controller
int           Throttle_Ref     = 0;
int           Steering_Ref     = 0;
int           ThrottleOutValue = 0;
int           SteeringOutValue = 0;
unsigned long Comm_Time        = 0;
unsigned long Comm_Time_2      = 0;
int           Comm_Delay_Time  = 1000;
int           index            = 0;

#define XbeeSerial Serial1



void setup()
{
  Serial.begin(115200);
  Serial.println( F("Xbee + LCD + NeoGPS test!") );


  XbeeSerial.begin(9600);


  lcd.begin(20, 4); // columns and rows

  GPSserial.begin(9600); // default baud rate for MTK GPS's- some use 4800

  // turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.send_P( &GPSserial, F("PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") );

  // Set the update rate
  GPS.send_P( &GPSserial, F("PMTK220,1000") ); // 1Hz = 1000ms between updates

}


void loop()
{
  // Read and parse characters from the GPS device
  if (GPS.available( GPSserial )) {

    // A complete fix is ready, get the latest structure of GPS fields
    fix = GPS.read();
    fixCount++;

    // Every 2 seconds (exactly), print out the current stats
    if (fixCount >= 2) {
      fixCount = 0; // reset

      Serial.print("Status: ");
      if (fix.valid.status)
        Serial.print( fix.status);

      Serial.print("\nTime: ");      // first newline is for previous print
      if (fix.dateTime.hours < 10)
        Serial.print( '0' );
      Serial.print(fix.dateTime.hours);
      Serial.print( ':' );
      if (fix.dateTime.minutes < 10)
        Serial.print( '0' );
      Serial.print(fix.dateTime.minutes);
      Serial.print( ':' );
      if (fix.dateTime.seconds < 10)
        Serial.print( '0' );
      Serial.print(fix.dateTime.seconds);
      Serial.print( '.' );
      if (fix.dateTime_cs < 10)
         Serial.print( '0' ); // leading zero for .05, for example
      Serial.print(fix.dateTime_cs);

      Serial.print( F("\nDate: ") );
      if (fix.valid.date)
        Serial << fix.dateTime;

      Serial.print( F("\nLocation: ") );
      if (fix.valid.location) {
        Serial.print( fix.latitude(), 6 );
        Serial.print( ',' );
        Serial.print( fix.longitude(), 6 );
      }

      Serial.print("\nSpeed (knots): ");
      if (fix.valid.speed)
        Serial.println( fix.speed() );

      Serial.print("\nHeading: ");
      if (fix.valid.heading)
        Serial.println( fix.heading() );

      Serial.print( F("\nAltitude: ") );
      if (fix.valid.altitude)
        Serial.print( fix.altitude() );

      Serial.print("Satellites: ");
      if (fix.valid.satellites)
        Serial.print( fix.satellites );

      // ******* Print to LCD display  *******
      // GPS.fix good when LCD.prints are here
      lcd.setCursor(0, 0);
      lcd.print("Lat = ");
      if (fix.valid.location)
        lcd.print( fix.latitude(), 6 );
      else
        lcd.print( F("    ?      ") ); // erases old values, too

      lcd.setCursor(0, 1);
      lcd.print("Lon = ");
      if (fix.valid.location)
        lcd.print( fix.longitude(), 6 );
      else
        lcd.print( F("    ?      ") );

    }
  }

  // Check for commands?
  if (lineReady()) {
    Serial.print( F("Command = '") );
    Serial.print( receivedChars );
    Serial.println( '\'' );
  }

} // loop


bool lineReady()
{
  bool          ready     = false;
  const char    endMarker = '\n';

  while (Serial.available()) {

    char c = Serial.read();

    if (c != endMarker) {
      // Only save the printable characters, if there's room
      if ((' ' <= c) and (count < MAX_CHARS-1)) {
        receivedChars[ count++ ] = c;
      }
    } else {
      //  It's the end marker, line is completely received
      receivedChars[count] = '\0'; // terminate the string
      count       = 0;    // reset for next time
      ready       = true;
      break;
    }
  }

  return ready;

} // lineReady

This will keep the display in perfect sync with the GPS atomic clock. Well, within a few milliseconds, anyway. :wink: It also eliminates any flicker you may have been seeing.

NeoGPS is smaller, faster, more reliable and more accurate than all other libraries. In this case,

The original sketch uses 12132 bytes of program space and 1200 bytes of RAM.
The NeoGPS version uses 9016 bytes of program space and 659 bytes of RAM, a significant savings.

It’s available from the Arduino Library Manager, under the menu Sketch-> Include Library-> Manage Libraries.

Cheers,
/dev

That was what I was thinking was happening but I also thought each serial port had it's own buffer and one shouldn't interfere with the other. What you're saying, if I understand correctly, is one buffer is reading data and the second buffer basically buts in so it can read it's data stopping the first buffer from completing its read.

I'll leave the LCD.prints where they are, it was more a symptom of my problem. Eventually I'll put the display on the controller and send the latitude and longitude over to monitor the rovers location.

How would I synchronise the servo references that I'm sending to the rover? I assume if I read from serial1 while GPS is updating I would have the same issue, actually I have. Currently I only send them once per second, that prevents the serial writes from over lapping. But on the rover side I want to read them as fast as I can to minimize the response delay.

Thanks for your help. I've been banging my head for a couple of weeks with this.
John

I also thought each serial port had it's own buffer and one shouldn't interfere with the other.

They do have their own receive buffers, and receiving on one does not interfere with the others.

if I understand correctly, is one buffer is reading data and the second buffer basically buts in so it can read it's data stopping the first buffer from completing its read.

Actually, it is the printing that prevents processing the receive buffer.

When you print, characters go into a transmit buffer. Compared to the processor speed (MHz), serial is very sloooooow (KHz). When you first print something, the first character starts going out. But before the first bit of the first character has even finished, the sketch has more characters to be sent. So they go into a transmit buffer. As the characters gradually get sent, they are removed from the transmit buffer.

But guess what happens if you try to print some more and the transmit buffer is full? Your sketch waits until there is room for the new print characters. It's not obvious, but your print statements have "blocked" the Arduino until the transmit buffer has room for the new print characters.

While the Arduino is twiddling its thumbs, the GPS device continues to send characters. They get stored in a receive buffer, waiting for some part of your sketch to call GPSserial.read().

Again, guess what happens if the receive buffer is already full and the GPS send another character? The Arduino drops the character. No room! This is an "input buffer overflow". Now the GPS sentence is only partially received, and it will not be parsed correctly. The Adafruit_GPS library will still say it's an ok sentence, even though characters are missing. :frowning: This is one reason I wrote NeoGPS.

So it is the prints that prevent reading quickly enough to keep up with the GPS device. This is a fundamental problem with other libraries' examples. It is very common for new programmers to break these examples with (what appears to be) a simple modification.

How would I synchronise the servo references that I'm sending to the rover? I assume if I read from serial1 while GPS is updating I would have the same issue, actually I have. Currently I only send them once per second, that prevents the serial writes from over lapping. But on the rover side I want to read them as fast as I can to minimize the response delay.

I'm not sure I understand the complete picture, but it sounds like:

  • You want to send commands from the base station to the rover frequently. Via Xbee? How often?

  • The rover must process the commands to control servos.

  • You want to send location from the rover to a base station less frequently. Once per second, via Xbee?

That makes more sense, I thought each serial port would have it's own buffer, why have 3 serial ports but one buffer, I didn't think about the printing time.

You're right on what I'm trying to do with the rover. I don't want to much of a delay between telling it to go left and it actually going left, 1 second 2 at the most. As far as priorities it would be control first, update GPS second, send location back third. I understand, now anyways, that I may not be able to have as fast as a response time as I want and be able do the other functions within that time.

Thanks again

You could send the location once every 2 seconds when you update the LCD. Or update the LCD in even seconds and send a location in odd seconds (alternating).

Maybe the rover would only send the location when requested (master-slave approach). That would let the base station monopolize the Xbee for good servo control, until it's ready to receive a location from the rover.

You could send one command that has everything: servo values and a flag that means "reply with location".

On the rover you could do something like this:

void loop()
{
  if (GPS.available( GPSserial )) {
    fix = GPS.read(); // save for later
    fixCount++;

    if (fixCount >= 2) {
      fixCount = 0; // reset
      updateLCD();
    }
  }

  // Check for Xbee commands?
  if (lineReady()) {
    Serial.print( F("Command = '") );
    Serial.print( receivedChars );
    Serial.println( '\'' );
    
    // Interpret the command
    if (servo command)
      update servo

    if (location query)
      send fix location
  }

} // loop

Lot's of ways...

Excellent suggestions mainly would be to synchronize when it reads the servo info and updates the GPS. And have the location as part of the GPS. I like the idea of using odd/even seconds do one on the odds the other on the evens, that should give both enough time. If not I'll just adjust the time till it works.

Awesome, gives me some direction to go in.

Thanks
John

Well what a pleasant surprise I got this morning. I just glanced over the code you sent back to me the other day. I assumed, and you know what that means, you had sent me an example of using the NeoGPS library. I was more concentrating on what you were explaining to me.

So this morning I sat down to look at it and see if I could get it to work, you actually rewrote my code so it would, that was awesome and of course it does work.

So now what I'm doing is trying to incorporate the servo control logic. What I did was moved your void loop to a new tab to make it a separate function. I then just call it from the main loop. Then I copied my servo logic into a second tab making that it's own function. This way I can set up the synchronizing timing by just calling each function as I need to. I didn't have time to get it all working, dam work always gets in the way of the fun stuff!

Is there a GPS reading complete type of bit? I could use that to delay calling the servo function until the GPS is finished it's process.

Thanks so very much for your help
John

Is there a “GPS reading” complete type of bit? I could use that to delay calling the servo function until the GPS is finished it’s process.

The most reliable indicator is gps.available(). Once that happens, you know the GPS is going to be quiet for “a while”. But how long will it be quiet?

That depends on two things: the GPS baudrate and the number of sentences it must send. In your case, you have enabled 2 sentences (~140 characters). At 9600, 140 characters take ~140ms to be transmitted. If the update rate is 1Hz, you have 1000ms - 140ms = 860ms to do Other Things.

You could set a timestamp when the GPS quiet time starts (immediately after gps.available):

 if (gps.available()) {
    fix = gps.read();
    fixCount++;
    lastGPStime = millis();

Then you can test it elsewhere, to see if GPS data is going to start arriving again:

 if (millis() - lastGPSTime < 860) {
    // *probably* no GPS chars yet, it's ok to do something

Or, if you know how long a task will take, don’t start unless there’s plenty of time left:

 if (millis() - lastGPSTime < 860-EST_TASK_TIME) {
    // *probably* no GPS chars yet, it's ok to do something

For example, printing 100 characters will take about 100ms. But the first 64 happen immediately, and the last 36 block for 36ms, while the first 36 characters are removed from the TX buffer. Your estimated time for that “task” would be:

const uint32_t EST_TASK_TIME = 100 - 64; // 36ms

I’m not sure about your question, though. Because you are using HardwareSerial, it saves up to 64 characters, even if you never call read() (or gps.available()). So during that 140ms time window when receiving GPS characters, you only need to check once.

Another way to look at it: If you start a task close to the next update, it must complete within 64ms of that start time. So you could wait as late as:

 if (millis() - lastGPSTime < 860-EST_TASK_TIME+64) {
    // *probably* no GPS chars yet, or they're getting buffered, it's ok to do something

If it starts getting messy, just make sure that no task ever takes longer than 64ms. Break the task up into smaller steps if necessary. And make sure that all task timing is running off of the gps.available “clock”.

“Controlling a servo” isn’t really a dedicated process is it? Don’t you just set the “value” (PWM) and then continue? When a new value comes in over Xbee, you set it again.

Perhaps sending a reply could take a while? If you send the reply immediately after gps.available, it will surely complete long before a new GPS update begins.

Note: You can very quickly write up to 64 characters in all Serial devices, one after the other. All of this data will fit in the separate TX buffers, so it’s very fast. It’s the 65th character on any device that will make the sketch block for 1ms. Yawn.

There are some additional tips at the bottom of the Troubleshooting page.

That's a great help, a couple of different options, those will help with the synchronization. I don't have to update the GPS constantly, once every ten seconds or so, it doesn't go that fast so it doesn't travel to far.

Calling it "Controlling the Servo" may be a bit misleading. You're right the servo library does most of the work. My controller has 2 thumb joysticks, speed and steering, I read them in and map the analog in value to the servo angle. I then send the servo angle over to the rover and it does the rest.

I'm going to work on this, I hope you don't mind if I have any further question I get in touch with you. You've been a tremendous help. Not sure where you are, I'm in the Boston area, if your ever around I owe you a drink.

Thanks again
John

I hope you don't mind if I have any further question I get in touch with you.

Just post another question here, if it's related to the overall problem of coordinating GPS with "other things".

If it's a specific question about Servo or Xbee, you should start a new thread.

Don't use Personal Messages for technical questions, if that's what you meant. Others can benefit from your question (and the answers), so keep it public in a thread.

Not sure where you are

Me either. :wink:

No I meant to post a response to this thread, or start a new one as you say

Thanks again