gps sensor won't print altitude to lcd, acting very weird

Hi,

I made a rather complicated sketch that printed gps info to an LCD, but the altitude information kept getting corrupted randomly, so I made the simplest sketch I could to isolate the problem, and it seems to have got worse. It seems very weird to me, but hopefully someone here is clever enough to spot it, so here goes:

the code is:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>
/*
   This sample sketch demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const int RXPin = 2, TXPin = 3;
static const uint32_t GPSBaud = 9600;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
  Serial.begin(9600);
  ss.begin(GPSBaud);

  Serial.println(F("DeviceExample.ino"));
  Serial.println(F("A simple demonstration of TinyGPS++ with an attached GPS module"));
  Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();
}

void loop()
{
  // This sketch displays information every time a new sentence is correctly encoded.
  while (ss.available() > 0)
    if (gps.encode(ss.read()))
      displayInfo();

  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println(F("No GPS detected: check wiring."));
    while(true);
  }
}

void displayInfo()
{
 // lcd.print (gps.altitude.meters());
  Serial.print(F("altitude: ")); 
  if (gps.location.isValid())
  {
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }
//if (gps.altitude.meters() < 1000);
 lcd.print (gps.altitude.meters());                         //this caused the problems

  Serial.print(F(" "));
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    if (gps.time.centisecond() < 10) Serial.print(F("0"));
    Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

Now about 3/4 down I have commented a line with “this caused the problems”. when the line is disabled, the serial monitor prints the altitude correctly. But when it is enabled, the serial monitor simple prints the altitude as “0.00”.

I can’t work out why the lcd print function affects the altitude value stored and printed via serial.

thanks

Why are you printing the altitude twice?
The first time, you check for a valid fix. The second time, you don't, so expect nonsense.

jremington:
Why are you printing the altitude twice?
The first time, you check for a valid fix. The second time, you don't, so expect nonsense.

I am not printing to the same location twice, I am printing to the serial monitor correctly and when I also print to the LCD, the altitude value appears to get corrupted and reads 0.00.

As stated before this sketch was custom created to isolate this problem in a purposeful sketch that doesn't make sense to me. I understand that not checking for a valid fix might mean that the lcd value it nonsense, but why would that affect the serially printed value that is checked for validity?

You have provided no evidence for your assertions, but it is certainly likely that things will go wrong if you disobey the basic assumptions made by the code, i.e. that you don't ask for a value before it exists.

Going just on what you stated, here is another question:

How could printing nonsense on an LCD affect a value that has already been printed on the serial monitor?

jremington:
You have provided no evidence for your assertions, but it is certainly likely that things will go wrong if you disobey the basic assumptions made by the code, i.e. that you don't ask for a value before it exists.

Going just on what you stated, here is another question:

How could printing nonsense on an LCD affect a value that has already been printed on the serial monitor?

I could provide a screen shot of the serial monitor showing 0.00 as per the original post if you want?

And the other question you posed: Exactly my point, as per the first post.

Have you fixed the obvious problem, of printing a value that does not exist?

jremington:
Have you fixed the obvious problem, of printing a value that does not exist?

I interpreted this as "you should always query whether the data is correct before reading it and printing it. There's a chance that even just reading an incorrect value can corrupt any further reading of it. Copy the 'is.valid' part before printing the values".

I did that and it still didn't make any difference. The way I did it was to put the lcd.print line on the line above the serial.print line. Once again I would comment out the lcd line and serial monitor would be fine, enable the line and it would only read 0.00.

 if (gps.location.isValid())
  {
   lcd.print (gps.altitude.meters());                 //this is the line that causes problems
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }

So I started playing around with adding various commands to maybe fix the lcd causing crashes (lcd.begin, lcd setcursor etc), but it didn't fix anything. So I played and played around with the code, and eventually discovered the problem:

The "gps.location.isValid" command works for the serial print, but the lcd print requires "gps.altitude.isValid"!

So to get it to work, it requires the following change.

 if (gps.altitude.isValid())
  {
   lcd.print (gps.altitude.meters());                 //this is the line that causes problems
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }

Hopefully I've detailed this enough to helps out anyone who has the same problem, and doesn't want to sift through the usual forum sarcasm to get to a solution.

Good to know that you fixed the problem. In retrospect I realized that where the real problem lies: there are two different sentences that TinyGPS++ parses, $GPRMC and $GMGGA.

$GPRMC sentences provide location information but not altitude information. $GPGGA sentences carry both location and altitude.

So, if you want to print altitude, you must check whether altitude is available. It may be, but is not always sufficient to check if a valid location fix is available. for example, if a valid $GPGGA sentence has just been received, a valid location implies a valid altitude.

It certainly should not matter which device is printing the information, but it is possible that there is an error lurking somewhere in TinyGPS++.

Ahh that’s good to know. I thought I had it fixed by only ever calling the function when it is confirmed as valid, but the error has now reappeared. :confused:

I appear to have isolated the problem, and it appears to be how the value is printed.

When I just use:

  // l Serial.print(F("altitude: ")); 
   if (gps.altitude.isValid())
  {
    lcd.setCursor(0,1); 
        lcd.print (gps.altitude.meters());
       // lcd.setCursor(4,1);
      //  lcd.print (" metres     ");                 //this is the line that causes problems
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));

All works fine

But when I enable the two commented out lines, after about 5 minutes or so the altitude value on the serial port and lcd will change to around 25.0. and sometimes 17.2 as per the attached screenshot. It’s weird. My only theory was that the extra delay of printing to the lcd could allow the value to become corrupt before it is read. So I moved the ‘metres’ print bit and set cursor to before the ‘if valid’ bit, and now the serial monitor reads INVALID for a few minutes before changing to 0.00. the LCD shows " metres" (5 blank characters then metres) indefinitely.

void displayInfo()
{
  Serial.print(F("altitude: ")); 
  lcd.setCursor(4,1);
  lcd.print (" metres     ");  
  lcd.setCursor(0,1);
   if (gps.altitude.isValid())
  {
    lcd.print (gps.altitude.meters());
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }

If I comment out the top two grouped lcd commands, all works fine as per the code below.

void displayInfo()
{
  Serial.print(F("altitude: ")); 
  //lcd.setCursor(4,1);
  //lcd.print (" metres     ");  
  lcd.setCursor(0,1);
   if (gps.altitude.isValid())
  {
    lcd.print (gps.altitude.meters());
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }

and finally in case it helps:

Here is the entire program code that doesn’t work:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>
/*
   This sample sketch demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const int RXPin = 2, TXPin = 3;
static const uint32_t GPSBaud = 9600;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
  Serial.begin(9600);
  ss.begin(GPSBaud);

  Serial.println(F("DeviceExample.ino"));
  Serial.println(F("A simple demonstration of TinyGPS++ with an attached GPS module"));
  Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();
  lcd.begin(16,2);
}

void loop()
{
  // This sketch displays information every time a new sentence is correctly encoded.
  while (ss.available() > 0)
    if (gps.encode(ss.read()))
      displayInfo();

  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println(F("No GPS detected: check wiring."));
    while(true);
  }
}

void displayInfo()
{
  Serial.print(F("altitude: ")); 
  lcd.setCursor(4,1);
  lcd.print (" metres     ");  
  lcd.setCursor(0,1);
   if (gps.altitude.isValid())
  {
    lcd.print (gps.altitude.meters());
    Serial.print(gps.altitude.meters());
    Serial.print(F(","));
     }
  else
  {
    Serial.print(F("INVALID"));
  }


  Serial.print(F(" "));
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    if (gps.time.centisecond() < 10) Serial.print(F("0"));
    Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

I just can’t figure it out, why can I print the altitude perfectly (1275.90) on the bottom line of the lcd), but when I add the word ‘metres’, it stops it working?

Any help is greatly appreciated, and I hope I pasted all the code well enough.

It's probably a timing issue.

What you're doing now is displaying everything whenever you get a completed sentence, i.e. when encode() returns true. But that means you could be displaying your data while the next sentence is being sent. If you don't get back to the start of the loop before the RX buffer fills you'll lose characters, the sentence checksum won't match and you'll get some sort of data dropout.

Normally displaying a few things on a screen doesn't take that long, but because you're using SoftwareSerial the processor has only about 5% of its normal bandwidth while the characters are being received. If your processor runs at 16MHz it will be as if it is only running at about 800kHz when data is coming in. So it's possible the 64 byte buffer could fill.

You could test this hypothesis by editing the SoftwareSerial.h file and increasing the buffer size to 128.

A better solution is to synchronize your data output with the quiet periods in between GPS reports. Let's say your GPS outputs RMC and GGA data once per second. You need to figure out a way to determine when both of those have been sent. Once that's happened then display the data, during the lull between reports from the GPS.

On the GPS I've been using the GGA always comes first, then the RMC. So I have code that looks for a completed RMC sentence. If I detect that then I know both have been sent. I don't know if that's universal for all GPS devices though. You could figure it out quickly enough by displaying raw data from your GPS.

As an aside, at 4800 baud SoftwareSerial causes the timer 0 overflow to sometimes be missed. That means your millis() and delay() and micros() functions will all run a little bit slow.

dave_sausages:
Ahh that's good to know. I thought I had it fixed by only ever calling the function when it is confirmed as valid, but the error has now reappeared. :confused:

I appear to have isolated the problem, and it appears to be how the value is printed.

When I just use:

  // l Serial.print(F("altitude: ")); 

if (gps.altitude.isValid())
 {
   lcd.setCursor(0,1);
       lcd.print (gps.altitude.meters());
      // lcd.setCursor(4,1);
     //  lcd.print (" metres     ");                 //this is the line that causes problems
   Serial.print(gps.altitude.meters());
   Serial.print(F(","));
    }
 else
 {
   Serial.print(F("INVALID"));

The problem you are running into is that the LCD is not organized as:
line1: 0..15
line2: 16..31

The HD44780 LCD controller when used with a 16x2 LCD is actually organized like this:
Line1: 0..39
Line2: 40..79

But it only displays 0..15,40..55. So if you expect a LCD.print() to wrap from the last position of the first line to the first position of the second line, you are in error.

Also you are overwriting your 'meters' prompt, LCD.setCursor(4,1); says set then LCD cursor position to line 2, 5th character, LCD.setCursor(0,1); now moves the Cursor back to Line2 0th character. Which then overwrite 'meters'.

Chuck.

I thought about this a little bit more and realized that what I suggested probably wouldn't produce the symptoms you're seeing. I don't think Chuck's observation explains it either since you're seeing odd numbers in the serial monitor output.

Are you sure the GPS isn't actually outputting those altitude values?

Here’s a version of your sketch I modified to use NeoGPS (install instructions). It compiles on my machine, but of course I can’t test it… maybe it’ll work for you. It will have different timing, so it may help with a SoftwareSerial GPS like yours.

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>


/*
   This sample sketch demonstrates the normal use of the NMEAGPS object from NeoGPS.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/
static const int RXPin = 2, TXPin = 3;
static const uint32_t GPSBaud = 9600;

// The NeoGPS object
#include "NMEAGPS.h"
NMEAGPS gps;

//------------------------------------------------------------
//  Define an extra set of GPS fix information.  It will
//  hold on to the various pieces as they are received from
//  different kinds of sentences.

static gps_fix fused;

static uint32_t seconds = 0L; // just a rolling seconds count

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
  Serial.begin(9600);
  ss.begin(GPSBaud);

  Serial.println(F("Alternate NeoGPS library"));
  Serial.println();

  lcd.begin(16,2);
}

void loop()
{
  static uint32_t last_rx = 0L;

  while (ss.available()) {
    last_rx = millis();

    if (gps.decode( ss.read() ) == NMEAGPS::DECODE_COMPLETED) {

      // All enabled sentence types will be merged into one fix
      fused |= gps.fix();

      if (gps.nmeaMessage == NMEAGPS::NMEA_RMC)
        //  Use received GPRMC sentence as a pulse
        seconds++;
    }
  }
  
  static uint32_t last_seconds = 0UL; // Remember the last time we printed

  //  Make sure we print *something* every two seconds, even if we
  //    haven't gotten any valid data.  
  //  This will show whether we have been getting *any* characters.

  static uint32_t last_time_seconds_changed = 0UL;

  if (millis() - last_time_seconds_changed > 2000UL)
    seconds += 2;

  if (last_seconds != seconds) {
    last_seconds = seconds; // force last_seconds to change
    last_time_seconds_changed = millis();
  }

  // Print things out *after* the serial input has died down.
  // This prevents input buffer overflow during printing.

  static uint32_t last_trace = 0UL;

  if ((last_trace != seconds) && (millis() - last_rx > 5)) {
    last_trace = seconds;

    if (gps.statistics.chars < 10)
    {
      Serial.println(F("No GPS detected: check wiring."));
      while(true);
    } else
      displayInfo();
  }

}

void displayInfo()
{
  Serial.print(F("altitude: ")); 
  lcd.setCursor(4,1);
  lcd.print (" metres     ");  
  lcd.setCursor(0,1);
  if (fused.valid.altitude)
  {
    lcd.print (fused.altitude());
    Serial.print(fused.altitude());
    Serial.print(',');
     }
  else
  {
    Serial.print(F("INVALID"));
  }


  Serial.print(' ');
  if (fused.valid.time)
  {
    if (fused.dateTime.hours < 10) Serial.print('0');
    Serial.print(fused.dateTime.hours);
    Serial.print(':');
    if (fused.dateTime.minutes < 10) Serial.print('0');
    Serial.print(fused.dateTime.minutes);
    Serial.print(':');
    if (fused.dateTime.seconds < 10) Serial.print('0');
    Serial.print(fused.dateTime.seconds);
    Serial.print('.');
    if (fused.dateTime_cs < 10) Serial.print('0');
    Serial.print(fused.dateTime_cs);
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

It contains excerpts from the NeoGPS example program NMEAfused.ino. It should work with the default configuration, Nominal, and save about 200 bytes of RAM and maybe some program space, too. You could save even more RAM and program space with a smaller configuration, if you’re interested.

Cheers,
/dev

Wow the replies are flooding in :slight_smile:

I'll try to address all the points. The altitude here is approx 1270 metres. All of this is being done while outside in an open area with miles of Utah sky above me.

@/dev

Is it your opinion that the neogps library will serve me better? I can make the switch in my full program if it will make things easier in the long run. It appears to have better functionality for error checking and the acquisition of usable data.

I'm also thinking that maybe I should move all of the gps instructions to one loop, and then 'copy' the values into separate integer values. Then I can just call the integer values when ever I want without any risk of messing up a GPS string. Sound good to you guys?

I'm also thinking that maybe I should move all of the gps instructions to one loop, and then 'copy' the values into separate integer values.

I intended to suggest that. Get the data while its hot!

Is it your opinion that the neogps library will serve me better?

As a completely unbiased observer, I can unreservedly recommend NeoGPS as the superior alternative.

Oh, wait... I wrote it. :stuck_out_tongue:

Under the hood, it is definitely more complicated to understand. If you are more interested in using libraries, like LiquidCrystal and NeoGPS, you may not care about the inner workings, as long as it's faster, smaller and more robust.

I can tell you, without reservations, that NeoGPS is faster and smaller in all regards: RAM, CPU time and program space. Other libraries are just not at the same Software Engineering level as NeoGPS. I continue to work towards making it accessible to a wider Arduino audience with increased documentation and examples. I leave it up to you to prioritize your needs. Personally, I wrote NeoGPS because of my frustration with then-available libraries, and with NMEA in general. Here's the announcement thread.

It appears to have better functionality for error checking and the acquisition of usable data.

Yes, that was one of the goals. Arduino users are at many different programming levels. At first, I only targeted the advanced user. Over time, I've seen beginners struggle with the same problems in naive libraries. I'm trying to widen the appeal of NeoGPS, because it can help you avoid those problems from the beginning.

I'm also thinking that maybe I should move all of the gps instructions to one loop, and then 'copy' the values into separate integer values. Then I can just call the integer values when ever I want without any risk of messing up a GPS string. Sound good to you guys?

That is the exact purpose of the structure gps_fix. It has location, altitude, speed, date, time, satellite info, etc. It is a collection of GPS fix data, a snapshot of the most recent sentence or time interval. Actually, the NMEAGPS processing object gps fills out its own gps_fix structure as it parses the sentences.

Like any C++ class member, you can access the gps's fix structure like this: gps.fix(). When a sentence is COMPLETED, the example program then copies the parsed gps.fix() into a separate gps_fix structure, called fused:

    fused |= gps.fix();

This saves all those pieces (location, altitude, etc.) with one statement. Then you can access fused.altitude() whenever you want, regardless of whether half a sentence has been received or not. Like jremington said, "Get the data while it's hot!"

BTW, That section of the sketch is very independent from the rest: the while (serial.available()) loop consumes the incoming characters, and updates the fused structure at the right time. The rest of your program just accesses fused. See displayInfo().

As others noted, SoftwareSerial is sensitive to the CPU load. Along with the loop structure in the sketch I provided, NeoGPS may free the CPU up to pay more attention to the SoftwareSerial. This may be why the altitude goes from 1266 to 25. Maybe it's dropping the '1'?

There are many peripheral issues that would make this post even longer. :slight_smile: Coherency, integers vs. floating-point, device-specific settings, etc.

Cheers,
/dev

dave_sausages:
The altitude here is approx 1270 metres. All of this is being done while outside in an open area with miles of Utah sky above me.

That doesn't answer my question. It only means you're confident that the GPS couldn't possibly be reporting odd altitude values, not that it isn't. It's a simple matter to display the raw data.

/dev:
This may be why the altitude goes from 1266 to 25. Maybe it's dropping the '1'?

And still get a correct checksum? That seems unlikely. It's why I think it's worth echoing the actual NMEA strings to the serial monitor, as a sanity check.

Maybe it's dropping the '1'?

Oops, I meant "dropping the '1's" from 1251, which would still pass the CS test. It is unlikely, though.

In the screenshot, there are (at least) 4 sentences per second, and the altitude doesn't change for several seconds before and after. That seemed to imply CS errors, as the altitude should have wandered if the sentence CS was ok. It looks like the GGA, the only sentence with altitude, is the 3rd or 4th sentence in each second. If there are lots of errors, then a double error is certainly possible, and the NMEA CS isn't very good for multiple errors.

The time is correctly updating, so at least the first 4 sentences in every interval don't have errors, which makes sense. He might start getting errors because the "Altitude" prints are 25 chars/sentence. which would cause it to block at Serial.print during the 3rd sentence. It looks like the 3rd or 4th sentence (not a GGA) gets dropped in interval 45, and then the time changes. These prints clear out at the end of the interval, perhaps causing the same set of errors every second.

The NeoGPS example sketch displays the Sentence Count, CS errors, and Chars Received, which would have been unambiguous. The sketch I provided defers printing to the GPS quiet time, which would certainly change its timing.

As you said, example output would be best.

Cheers,
/dev

Oh, I see. I didn’t even realize that dropping two identical characters would leave the checksum unchanged. That’s probably more likely than his GPS having reception problems. As for wandering, my own GPS frequently reports the exact same altitude for 5-10 seconds in a row.

Why is this GPS stuff such a PITA? It should be easier.