Work with GPS coordinates

Hello,

I got my GPS modules (Neo-7M and 6M) recently to work and am now trying to get my project to the next level. I want to replace a Compass (well not replace, just simulate one, as I do not have one) with a GPS.

My idea was to basically wait for the GPS to get the coordinates, then read those and store them: Then the GPS should move in some direction. After a short while (like 10-20 seconds) the Arduino should read the new coordinates and from there calculate the direction the object is heading.

I am using the NeoGPS library with the standard NMEA sketch.

#include <NMEAGPS.h>


#if defined( UBRR1H ) | defined( ID_USART0 )
  //#include <NeoHWSerial.h>
#else

  //#include <NeoICSerial.h>
  //#include <AltSoftSerial.h>
  #include <NeoSWSerial.h>
 
#endif

#include "GPSport.h"


#include "Streamers.h"


#ifdef NeoHWSerial_h
  #define DEBUG_PORT NeoSerial
#else
  #define DEBUG_PORT Serial
#endif



static NMEAGPS  gps;


static gps_fix  fix_data;


static void doSomeWork()
{


  trace_all( DEBUG_PORT, gps, fix_data );

}



static void GPSloop()
{
  while (gps.available( gps_port )) {
    fix_data = gps.read();
    doSomeWork();
  }

} 


void setup()
{
  DEBUG_PORT.begin(9600);
  while (!DEBUG_PORT)
    ;

  DEBUG_PORT.print( F("NMEA.INO: started\n") );
  DEBUG_PORT.print( F("  fix object size = ") );
  DEBUG_PORT.println( sizeof(gps.fix()) );
  DEBUG_PORT.print( F("  gps object size = ") );
  DEBUG_PORT.println( sizeof(gps) );
  DEBUG_PORT.println( F("Looking for GPS device on " USING_GPS_PORT) );

  #ifndef NMEAGPS_RECOGNIZE_ALL
    #error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
  #endif

  #ifdef NMEAGPS_INTERRUPT_PROCESSING
    #error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
  #endif

  #if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
      !defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
      !defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
      !defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )

    DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );

  #else
    if (gps.merging == NMEAGPS::NO_MERGING) {
      DEBUG_PORT.print  ( F("\nWARNING: displaying data from ") );
      DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
      DEBUG_PORT.print  ( F(" sentences ONLY, and only if ") );
      DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
      DEBUG_PORT.println( F(" is enabled.\n"
                            "  Other sentences may be parsed, but their data will not be displayed.") );
    }
  #endif

  DEBUG_PORT.print  ( F("\nGPS quiet time is assumed to begin after a ") );
  DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
  DEBUG_PORT.println( F(" sentence is received.\n"
                        "  You should confirm this with NMEAorder.ino\n") );

  trace_header( DEBUG_PORT );

  DEBUG_PORT.flush();

  gps_port.begin( 9600 );
}



void loop()
{
  GPSloop();
}

My plan was to somehow only access the GPS values (so latitude and longtitude) and (as I said) store them. However, my coding experience is rather little and I am unsure how to get there.

Right now I am stuck at just accessing the coordinates, storing them and also (not really needed, but for monitoring purposes) send them to the serial monitor).

Another question: How can I print something to the serial monitor with this sketch. I found out that the "trace_all()" command sends the data to the monitor (I hope I am right with that assumption), but not sure how exactly that works and if there is any other way of sending stuff to the serial Monitor.

Thanks for all ideas and answers!

then read those and store them

One pair? Or more than one pair? Store them where?

Then the GPS should move in some direction.

What command do you give a GPS to make it move?

How can I print something to the serial monitor with this sketch.

What sketch?

but not sure how exactly that works and if there is any other way of sending stuff to the serial Monitor.

We can't see your code, or follow your link to the library you are using, so help really seems unlikely.

You can, though, Serial.print() just about anything.

PaulS:
One pair? Or more than one pair? Store them where?

Well, at first it would be enough to store one pair in some kind of variable that I can access. Later on I might want to store more than one, maybe to get an average or the history.

PaulS:
What command do you give a GPS to make it move?

The GPS and Arduino is on a ship (could be a car as well). The motors will make the ship move and therefore make the GPS unit with the Arduino move.

PaulS:
What sketch?
We can't see your code, or follow your link to the library you are using, so help really seems unlikely.

Here is the code:

#include <NMEAGPS.h>

//======================================================================
//  Program: NMEA.ino
//
//  Description:  This program uses the fix-oriented methods available() and
//    read() to handle complete fix structures.
//
//    When the last character of the LAST_SENTENCE_IN_INTERVAL (see NMEAGPS_cfg.h)
//    is decoded, a completed fix structure becomes available and is returned
//    from read().  The new fix is saved the 'fix_data' structure, and can be used
//    anywhere, at any time.
//
//    If no messages are enabled in NMEAGPS_cfg.h, or
//    no 'gps_fix' members are enabled in GPSfix_cfg.h, no information will be
//    parsed, copied or printed.
//
//  Prerequisites:
//     1) Your GPS device has been correctly powered.
//          Be careful when connecting 3.3V devices.
//     2) Your GPS device is correctly connected to an Arduino serial port.
//          See GPSport.h for the default connections.
//     3) You know the default baud rate of your GPS device.
//          If 9600 does not work, use NMEAdiagnostic.ino to
//          scan for the correct baud rate.
//     4) LAST_SENTENCE_IN_INTERVAL is defined to be the sentence that is
//          sent *last* in each update interval (usually once per second).
//          The default is NMEAGPS::NMEA_RMC (see NMEAGPS_cfg.h).  Other
//          programs may need to use the sentence identified by NMEAorder.ino.
//     5) NMEAGPS_RECOGNIZE_ALL is defined in NMEAGPS_cfg.h
//
//  'Serial' is for debug output to the Serial Monitor window.
//
//======================================================================

//-------------------------------------------------------------------------
//  The GPSport.h include file tries to choose a default serial port
//  for the GPS device.  If you know which serial port you want to use,
//  delete this section and declare it here:
//
//    HardwareSerial & gps_port = Serial2; // an alias
//          or
//    AltSoftSerial gps_port; // depends on Arduino - pins 8 & 9 on UNO
//          or
//    NeoSWSerial gps_port( rxpin, txpin ); // to GPS TX, RX
//          or
//    Search and replace all occurrences of "gps_port" with your port's name.
//
//  See Installation instructions for additional information.

#if defined( UBRR1H ) | defined( ID_USART0 )
  // Default is to use Serial1 when available.  You could also
  // use NeoHWSerial, especially if you want to handle GPS characters
  // in an Interrupt Service Routine.
  //#include <NeoHWSerial.h>
#else
  // Only one serial port is available, uncomment one of the following:
  //#include <NeoICSerial.h>
  //#include <AltSoftSerial.h>
  #include <NeoSWSerial.h>
  //#include <SoftwareSerial.h> /* NOT RECOMMENDED */
#endif

#include "GPSport.h"

//------------------------------------------------------------
// For the NeoGPS example programs, "Streamers" is common set
//   of printing and formatting routines for GPS data, in a
//   Comma-Separated Values text format (aka CSV).  The CSV
//   data will be printed to the "debug output device".
// If you don't need these formatters, simply delete this section.

#include "Streamers.h"

//------------------------------------------------------------
// When NeoHWSerial is used, none of the built-in HardwareSerial
//   variables can be used: Serial, Serial1, Serial2 and Serial3
//   *cannot* be used.  Instead, you must use the corresponding
//   NeoSerial, NeoSerial1, NeoSerial2 or NeoSerial3.  This define
//   is used to substitute the appropriate Serial variable in
//   all debug prints below.

#ifdef NeoHWSerial_h
  #define DEBUG_PORT NeoSerial
#else
  #define DEBUG_PORT Serial
#endif

//------------------------------------------------------------
// This object parses received characters
//   into the gps.fix() data structure

static NMEAGPS  gps;

//------------------------------------------------------------
//  Define a set of GPS fix information.  It will
//  hold on to the various pieces as they are received from
//  an RMC sentence.  It can be used anywhere in your sketch.

static gps_fix  fix_data;

//----------------------------------------------------------------
//  This function gets called about once per second, during the GPS
//  quiet time.  It's the best place to do anything that might take
//  a while: print a bunch of things, write to SD, send an SMS, etc.
//
//  By doing the "hard" work during the quiet time, the CPU can get back to
//  reading the GPS chars as they come in, so that no chars are lost.

static void doSomeWork()
{
  // Print all the things!

  trace_all( DEBUG_PORT, gps, fix_data );

} // doSomeWork

//------------------------------------
//  This is the main GPS parsing loop.

static void GPSloop()
{
  while (gps.available( gps_port )) {
    fix_data = gps.read();
    doSomeWork();
  }

} // GPSloop

//--------------------------

void setup()
{
  // Start the normal trace output
  DEBUG_PORT.begin(9600);
  while (!DEBUG_PORT)
    ;

  DEBUG_PORT.print( F("NMEA.INO: started\n") );
  DEBUG_PORT.print( F("  fix object size = ") );
  DEBUG_PORT.println( sizeof(gps.fix()) );
  DEBUG_PORT.print( F("  gps object size = ") );
  DEBUG_PORT.println( sizeof(gps) );
  DEBUG_PORT.println( F("Looking for GPS device on " USING_GPS_PORT) );

  #ifndef NMEAGPS_RECOGNIZE_ALL
    #error You must define NMEAGPS_RECOGNIZE_ALL in NMEAGPS_cfg.h!
  #endif

  #ifdef NMEAGPS_INTERRUPT_PROCESSING
    #error You must *NOT* define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
  #endif

  #if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \
      !defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \
      !defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \
      !defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST )

    DEBUG_PORT.println( F("\nWARNING: No NMEA sentences are enabled: no fix data will be displayed.") );

  #else
    if (gps.merging == NMEAGPS::NO_MERGING) {
      DEBUG_PORT.print  ( F("\nWARNING: displaying data from ") );
      DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
      DEBUG_PORT.print  ( F(" sentences ONLY, and only if ") );
      DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
      DEBUG_PORT.println( F(" is enabled.\n"
                            "  Other sentences may be parsed, but their data will not be displayed.") );
    }
  #endif

  DEBUG_PORT.print  ( F("\nGPS quiet time is assumed to begin after a ") );
  DEBUG_PORT.print  ( gps.string_for( LAST_SENTENCE_IN_INTERVAL ) );
  DEBUG_PORT.println( F(" sentence is received.\n"
                        "  You should confirm this with NMEAorder.ino\n") );

  trace_header( DEBUG_PORT );

  DEBUG_PORT.flush();

  // Start the UART for the GPS device
  gps_port.begin( 9600 );
}

//--------------------------

void loop()
{
  GPSloop();
}

Do you want me to delete the comments (I thought it might come handy for understanding the code)?

So now, given that code, I do not know how to store (in a variable) and access the coordinates (or does that even make much sense? - could I just ask for the values in the particular spot where I need them) and how to print something to my serial monitor.

Thank you for your answers. I do appreciate them!

The library you are using defines a data structure called gps_fix. You have only one gps_fix in that program, called fix_data. You can create many more variables of that same type. You need to store at least one previous fix, so call it "fix_previous":

gps_fix fix_previous;

(The keyword "static" in the original example is meaningless in this context.)

Look at the other examples in the library - there's lots of demonstrations on how to calculate distances or whatever...

Then the code would look something like:

void loop()
{
  GPSloop();
  if(fix_data.valid.location && fix_previous.location.distance(fix_data.location) > MIN_DISTANCE_FOR_COMPASS) {
    updateCompass(fix_data, fix_previous);
    fix_previous = fix_data;  //overwrite the previous point with the current one
  }
}

We still need to see a link to the library. Clearly, the fix_data object contains some data that trace_all() knows how to print.

PaulS:
We still need to see a link to the library. Clearly, the fix_data object contains some data that trace_all() knows how to print.

I am not quite sure what you mean/need.
This would be the link to download the NeoGPS library.
Or you just add it in your library manager in Arduino IDE. (It is called NeoGPS).

MorganS:
The library you are using defines a data structure called gps_fix. You have only one gps_fix in that program, called fix_data. You can create many more variables of that same type. You need to store at least one previous fix, so call it "fix_previous":

gps_fix fix_previous;

(The keyword "static" in the original example is meaningless in this context.)

Look at the other examples in the library - there's lots of demonstrations on how to calculate distances or whatever...

Then the code would look something like:

void loop()

{
  GPSloop();
  if(fix_data.valid.location && fix_previous.location.distance(fix_data.location) > MIN_DISTANCE_FOR_COMPASS) {
    updateCompass(fix_data, fix_previous);
    fix_previous = fix_data;  //overwrite the previous point with the current one
  }
}

Thank you, I think this will help me. However, I am not exactly sure what all of the code means.
First: what does this data structure mean for me? What type of variables am I working with?
Second: I do get a sense of what your code and most of the NeoGPS library does, but do not fully comprehend it. If you could please explain what you are doing in you if statement, I would really appreciate that! (I am unfamiliar with what exactly the condition states and what the if loop does)

Thank you very much, I really do appreciate your help.

I am off to trying some things with it, even though I am not completely sure what I am doing.

MorganS' answer is great, except that you should put the test in doSomeWork instead of loop:

static const float MIN_DISTANCE_FOR_COMPASS =  0.0001; // km (increase to your threshold)

static void doSomeWork()
{
  trace_all( DEBUG_PORT, gps, fix_data );

  if (fix_data.valid.location) {

    if (fix_previous.valid.location) {
      float distanceTravelled = fix_previous.location.DistanceKm( fix_data.location );

      Serial.print( F("Meters travelled = ") );
      Serial.println( distanceTravelled * 1000.0, 6 );

      if (distanceTravelled > MIN_DISTANCE_FOR_COMPASS) {
        updateCompass(fix_data, fix_previous);
      }
    }

    fix_previous = fix_data;  //overwrite the previous point with the current one
  }
}

void updateCompass( gps_fix & current, gps_fix & previous )
{
  float directionTravelled = previous.location.BearingToDegrees( current.location );

  Serial.print( F("Calculated Heading = ") );
  Serial.println( directionTravelled );

  Serial.print( F("GPS heading = ") );
  if (current.valid.heading)
    Serial.print( current.heading() );
  Serial.println();
  // ...
}

Otherwise, it would perform the same test thousands of times (in loop) before the next fix is available. On the first time through loop after a fix becomes available, it would update the compass. All successive loop executions (thousands) would be comparing the current location with the current location. :confused:

The above code also makes sure that the previous location is valid before using it in a distance calculation. Until you get the first valid location, the previous location is not valid.

Since you asked about printing some of the fields, take a look at NMEAloc, NMEAsimple or Tabular for more examples.

Cheers,
/dev

Wow!

Thank you! I implemented what you suggested and it basically worked instantly. So again, I am a newbie in coding and I feel like I am understanding the code better and better, but I still have some questions:
What is the calculated heading compare to the current heading? Does the GPS already calculate the heading and provide that and the calculated heading is the one based off the code, creating variables and then calculating the heading?

What is the "F" in front of what I actually want to print and after the command in the brackets?

Am I right with assuming that float directionTravelled = previous.location.BearingToDegrees( current.location ); somehow calculates the distance travelled after it converted some coordinates from BearingTODegrees ? How does that work - I do not see any formula or something like that?!

(current.valid.heading) Does the "valid" check (like a boolean) if it is true? Or what exactly does it do in a if statement. Or asked differently: How can I fulfill the if statement

if (current.valid.heading)
Serial.print( current.heading() );
  Serial.println();

And why does it not need these {} brackets? And why is there an extra ")"?

Many small questions that would really help my understanding...
Thank you for your kind help!

What is the "F" in front of what I actually want to print and after the command in the brackets?

The F() macro generates code to keep the string literal from being copied from flash memory to SRAM at run time. No sense wasting memory for constant strings.

Does the "valid" check (like a boolean) if it is true?

valid is an instance of a struct called valid_t. That struct (a bunch of booleans) is populated based on getting valid data. So, yes, valid acts like a function call.

How can I fulfill the if statement

I'm not sure what you mean by "fulfill the if statement".

And why does it not need these {} brackets?

The brackets are strictly necessary only if the body of the if statement contains more than one statement. Real programmers use them all the time.

And why is there an extra ")"?

Because there is an extra (. The parens are needed because the heading thing in current is a function, while the heading thing in current.valid is a boolean. Functions need to be called using parens.

Larry_2:
What is the calculated heading compare to the current heading? Does the GPS calculate the heading and provide that...

Yes.

... and the calculated heading is the one based off the code, creating variables and then calculating the heading?

Yes, the NeoGPS library can calculate the bearing (i.e., "travel heading") between any two locations. This calculated heading may be different from the heading calculated by the GPS device. The NeoGPS library parses the characters from the GPS device and fills out the heading member of a fix structure.

Am I right with assuming that

    float directionTravelled = previous.location.BearingToDegrees( current.location );

somehow calculates the distance travelled after it converted some coordinates from BearingTODegrees ? How does that work - I do not see any formula or something like that?!

Not quite. You can read more about geodetic calculations here. The NeoGPS code for calculating bearing is here.