direction (course) from tinygps plus library ??

Hi all,

Im trying to calculate or printing the current course in deg. by using the function gps.course.deg() from tinygps library to give me my current course. but the course is jumping around and Im not sure how the course is being calculated.

is there a way to fix that or any another way to calculate the current course by using the lat and lon ?

a copy of my code is attached below

Thanks

gps.ino (3.32 KB)

a copy of my code is attached below

Why? Your code is short enough to just post.

#include <SoftwareSerial.h>
#include <TinyGPS++.h>

#define RXPin 8
#define TXPin 7
#define GPSBaud 9600
#define ConsoleBaud 115200

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

// The TinyGPS++ object
TinyGPSPlus gps;
unsigned long lastUpdateTime = 0;

#define des_LAT 44.88285
#define des_LNG -68.67253

/* This example shows a basic framework for how you might
   use course and distance to guide a person (or a drone)
   to a destination.  This destination is the Eiffel Tower.
   Change it as required.

   The easiest way to get the lat/long coordinate is to
   right-click the destination in Google Maps (maps.google.com),
   and choose "What's here?".  This puts the exact values in the
   search box.
*/

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

}

void loop()
{
  // If any characters have arrived from the GPS,
  // send them to the TinyGPS++ object
  while (ss.available() > 0)
    gps.encode(ss.read());

  // Every 5 seconds, do an update.
  if (millis() - lastUpdateTime >= 5000)
  {
    lastUpdateTime = millis();
    Serial.println();

    // Establish our current status
    double distanceToDestination = TinyGPSPlus::distanceBetween(gps.location.lat(), gps.location.lng(), des_LAT, des_LNG);
    double courseToDestination = TinyGPSPlus::courseTo(gps.location.lat(), gps.location.lng(), des_LAT, des_LNG);
    const char *directionToDestination = TinyGPSPlus::cardinal(courseToDestination);
    int courseChangeNeeded = (int)(360 + courseToDestination - gps.course.deg()) % 360;

    // debug
    Serial.print("DEBUG: Course2Dest: ");
    Serial.print(courseToDestination);
    Serial.print("  CurCourse: ");
    Serial.print(gps.course.deg());
    Serial.print("  Dir2Dest: ");
    Serial.print(directionToDestination);
    Serial.print("  RelCourse: ");
    Serial.print(courseChangeNeeded);
    Serial.print("  CurSpd: ");
    Serial.println(gps.speed.kmph());

    float l = gps.location.lat();
    Serial.println();
    Serial.print("Lat: "); Serial.print(l,6); Serial.print("  Lon: "); Serial.println(gps.location.lng());
    Serial.print("current Angle: "); Serial.println(atan2(gps.location.lat(), gps.location.lng())*180/M_PI);

    // Within 20 meters of destination?  We're here!
    if (distanceToDestination <= 20.0)
    {
      Serial.println("CONGRATULATIONS: You've arrived!");
      exit(1);
    }

    Serial.print("DISTANCE: ");
    Serial.print(distanceToDestination);
    Serial.println(" meters to go.");
    Serial.print("INSTRUCTION: ");

    // Standing still? Just indicate which direction to go.
    if (gps.speed.kmph() < 2.0)
    {
      Serial.print("Head ");
      Serial.print(directionToDestination);
      Serial.println(".");
      //return;
    }

    if (courseChangeNeeded >= 345 || courseChangeNeeded < 15)
      Serial.println("Keep on straight ahead!");
    else if (courseChangeNeeded >= 315 && courseChangeNeeded < 345)
      Serial.println("Veer slightly to the left.");
    else if (courseChangeNeeded >= 15 && courseChangeNeeded < 45)
      Serial.println("Veer slightly to the right.");
    else if (courseChangeNeeded >= 255 && courseChangeNeeded < 315)
      Serial.println("Turn to the left.");
    else if (courseChangeNeeded >= 45 && courseChangeNeeded < 105)
      Serial.println("Turn to the right.");
    else
      Serial.println("Turn completely around.");
  }
}
    gps.encode(ss.read());

The encode() method returns a VERY important piece of information. You are foolish to discard that.

Speaking of foolish, you are assuming that the TinyGPS++ library has good data to compute a course from. That may NOT be a valid assumption. So, stop making it. Check!

And whilst you probably know what 'jumping around' means, how can the readers of the forum know ?

IF by jumping around, you mean the GPS course (aka heading) is N, then S, then… various random directions, then yes, that’s normal. Below 5kph, the course and speed are not accurate. The lat/lon is also wandering around your actual location, so you can’t use that to compute speed, either.

BTW, all these GPS values could be “empty”, or invalid. The GPS device will send an empty field when it doesn’t know something. Like PaulS said, check the validity first.

My NeoGPS library has flags for all values so it’s easy to check. It’s also faster, smaller and more accurate, especially at smaller distances (<1km). Here is a NeoGPS version of your sketch:

#include <NeoSWSerial.h>
#include <NMEAGPS.h>

#define GPSBaud 9600
#define ConsoleBaud 115200

// The serial connection to the GPS device
//AltSoftSerial gpsPort; // pins 8 & 9 would be better!
#define RXPin 8
#define TXPin 7
NeoSWSerial gpsPort(RXPin, TXPin);

NMEAGPS gps;
uint8_t updates = 0; // used to count elapsed seconds

NeoGPS::Location_t des( 44.88285, -68.67253 ); // Orono, ME

void setup()
{
  Serial.begin(ConsoleBaud);
  gpsPort.begin(GPSBaud);

}

void loop()
{
  // Process characters from the GPS

  while (gps.available( gpsPort )) {
    gps_fix fix = gps.read();

    // Every 5 seconds, do an update.
    if (++updates >= 5) {
      updates = 0;
      Serial.println();

      // If we have a location, give instructions
      if (fix.valid.location) {

        // Establish our current status
        double distanceToDestination = fix.location.DistanceKm( des );
        double courseToDestination   = fix.location.BearingToDegrees( des );
        const __FlashStringHelper *directionToDestination =
                                        compassDir(courseToDestination);
        int courseChangeNeeded = (int)(360 + courseToDestination - fix.heading()) % 360;

        // debug
        Serial.print( F("DEBUG: Course2Dest: ") );
        Serial.print(courseToDestination);
        Serial.print( F("  CurCourse: ") );
        if (fix.valid.heading)
          Serial.print( fix.heading() );
        Serial.print( F("  Dir2Dest: ") );
        Serial.print(directionToDestination);
        Serial.print( F("  RelCourse: ") );
        Serial.print(courseChangeNeeded);
        Serial.print( F("Lat: ") );
        Serial.print( fix.latitude(), 6 );
        Serial.print( F("  Lon: ") );
        Serial.println( fix.longitude(), 6 );

        Serial.print( F("  CurSpd: ") );
        if (fix.valid.speed)
          Serial.print( fix.speed_kph() );
        Serial.println('\n');

        // ???  THIS ISN'T A THING
        //Serial.print( F("current Angle: ") );
        //Serial.println(atan2(gps.location.lat(), gps.location.lng())*180/M_PI);

        // Within 20 meters of destination?  We're here!
        if (distanceToDestination <= 20.0)
        {
          Serial.println( F("CONGRATULATIONS: You've arrived!") );
          exit(1);
        }

        Serial.print( F("DISTANCE: ") );
        Serial.print(distanceToDestination);
        Serial.println( F(" meters to go.") );
        Serial.print( F("INSTRUCTION: ") );

        // Standing still? Just indicate which direction to go.
        if (fix.speed_kph() < 5.0)
        {
          Serial.print( F("Head ") );
          Serial.print(directionToDestination);
          Serial.println( '.' );

        } else // suggest a course change
        if ((courseChangeNeeded >= 345) || (courseChangeNeeded < 15))
          Serial.println( F("Keep on straight ahead!") );
        else if ((courseChangeNeeded >= 315) && (courseChangeNeeded < 345))
          Serial.println( F("Veer slightly to the left.") );
        else if ((courseChangeNeeded >= 15) && (courseChangeNeeded < 45))
          Serial.println( F("Veer slightly to the right.") );
        else if ((courseChangeNeeded >= 255) && (courseChangeNeeded < 315))
          Serial.println( F("Turn to the left.") );
        else if ((courseChangeNeeded >= 45) && (courseChangeNeeded < 105))
          Serial.println( F("Turn to the right.") );
        else
          Serial.println( F("Turn completely around.") );
      } else {
        Serial.println( F("Waiting for GPS fix...") );
      }
    }
  }
}

//------------------------------------------------------------
//  This snippet is from NMEAaverage.  It keeps all the
//    compass direction strings in FLASH memory, saving RAM.

const char nCD  [] PROGMEM = "N";
const char nneCD[] PROGMEM = "NNE";
const char neCD [] PROGMEM = "NE";
const char eneCD[] PROGMEM = "ENE";
const char eCD  [] PROGMEM = "E";
const char eseCD[] PROGMEM = "ESE";
const char seCD [] PROGMEM = "SE";
const char sseCD[] PROGMEM = "SSE";
const char sCD  [] PROGMEM = "S";
const char sswCD[] PROGMEM = "SSW";
const char swCD [] PROGMEM = "SW";
const char wswCD[] PROGMEM = "WSW";
const char wCD  [] PROGMEM = "W";
const char wnwCD[] PROGMEM = "WNW";
const char nwCD [] PROGMEM = "NW";
const char nnwCD[] PROGMEM = "NNW";

const char * const dirStrings[] PROGMEM =
  { nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD, 
    sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };

const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
  const int16_t directions    = sizeof(dirStrings)/sizeof(dirStrings[0]);
  const int16_t degreesPerDir = 360 / directions;
        int8_t  dir           = (bearing + degreesPerDir/2) / degreesPerDir;

  while (dir < 0)
    dir += directions;
  while (dir >= directions)
    dir -= directions;

  return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );

} // compassDir

The major change is using the 1-second GPS updates for the 5-second interval timer. After 5 GPS updates (fixes) have been received, you know that 5 seconds have elapsed. Although it’s not very important for a 5-second interval, you should be aware that the Arduino millis() will drift against the GPS clock. Sometimes 5000ms will 4 GPS updates, 6 updates, or even 4.5 updates (some new data, some old data).

Also notice that it uses my NeoSWSerial instead of SoftwareSerial. SoftwareSerial is very inefficient, because it disables interrupts for long periods of time. It will use about 95% of the CPU time, just waiting. This can interfere with other parts of your sketch or with other libraries.

AltSoftSerial is the absolute best choice for a second serial port, but you have to connect the GPS Tx/Rx to specific Arduino pins (e.g., 8/9 on an UNO). Strongly recommended!

Other changes:

  • The F macro around string constants is used to save RAM.
  • The compass direction strings are stored in FLASH, also saving RAM.
  • All GPS fields are checked for validity before being used.
  • If the location is not valid, it just displays “Waiting for GPS fix…”

The original sketch uses 11540 bytes of program space and 892 bytes of RAM.
With NeoGPS/NeoSWSerial it uses 13010 bytes of program space and 408 bytes of RAM. NeoGPS saved about 100 bytes of RAM, and putting the strings in FLASH saved about 400 bytes of RAM.

If you want to try it, NeoGPS is available from the Arduino IDE Library Manager, under the menu Sketch → Include Library → Manage Libraries. Be sure to get v4.1.5, or download from github directly. Download NeoSWSerial from the link above.

Cheers,
/dev

/dev:
IF by jumping around, you mean the GPS course (aka heading) is N, then S, then… various random directions, then yes, that’s normal. Below 5kph, the course and speed are not accurate. The lat/lon is also wandering around your actual location, so you can’t use that to compute speed, either.

BTW, all these GPS values could be “empty”, or invalid. The GPS device will send an empty field when it doesn’t know something. Like PaulS said, check the validity first.

My NeoGPS library has flags for all values so it’s easy to check. It’s also faster, smaller and more accurate, especially at smaller distances (<1km). Here is a NeoGPS version of your sketch:

#include <NeoSWSerial.h>

#include <NMEAGPS.h>

#define GPSBaud 9600
#define ConsoleBaud 115200

// The serial connection to the GPS device
//AltSoftSerial gpsPort; // pins 8 & 9 would be better!
#define RXPin 8
#define TXPin 7
NeoSWSerial gpsPort(RXPin, TXPin);

NMEAGPS gps;
uint8_t updates = 0; // used to count elapsed seconds

NeoGPS::Location_t des( 44.88285, -68.67253 ); // Orono, ME

void setup()
{
  Serial.begin(ConsoleBaud);
  gpsPort.begin(GPSBaud);

}

void loop()
{
  // Process characters from the GPS

while (gps.available( gpsPort )) {
    gps_fix fix = gps.read();

// Every 5 seconds, do an update.
    if (++updates >= 5) {
      updates = 0;
      Serial.println();

// If we have a location, give instructions
      if (fix.valid.location) {

// Establish our current status
        double distanceToDestination = fix.location.DistanceKm( des );
        double courseToDestination  = fix.location.BearingToDegrees( des );
        const __FlashStringHelper *directionToDestination =
                                        compassDir(courseToDestination);
        int courseChangeNeeded = (int)(360 + courseToDestination - fix.heading()) % 360;

// debug
        Serial.print( F("DEBUG: Course2Dest: “) );
        Serial.print(courseToDestination);
        Serial.print( F(”  CurCourse: “) );
        if (fix.valid.heading)
          Serial.print( fix.heading() );
        Serial.print( F(”  Dir2Dest: “) );
        Serial.print(directionToDestination);
        Serial.print( F(”  RelCourse: ") );
        Serial.print(courseChangeNeeded);
        Serial.print( F("Lat: “) );
        Serial.print( fix.latitude(), 6 );
        Serial.print( F(”  Lon: ") );
        Serial.println( fix.longitude(), 6 );

Serial.print( F("  CurSpd: ") );
        if (fix.valid.speed)
          Serial.print( fix.speed_kph() );
        Serial.println(’\n’);

// ???  THIS ISN’T A THING
        //Serial.print( F("current Angle: ") );
        //Serial.println(atan2(gps.location.lat(), gps.location.lng())*180/M_PI);

// Within 20 meters of destination?  We’re here!
        if (distanceToDestination <= 20.0)
        {
          Serial.println( F(“CONGRATULATIONS: You’ve arrived!”) );
          exit(1);
        }

Serial.print( F(“DISTANCE: “) );
        Serial.print(distanceToDestination);
        Serial.println( F(” meters to go.”) );
        Serial.print( F("INSTRUCTION: ") );

// Standing still? Just indicate which direction to go.
        if (fix.speed_kph() < 5.0)
        {
          Serial.print( F("Head ") );
          Serial.print(directionToDestination);
          Serial.println( ‘.’ );

} else // suggest a course change
        if ((courseChangeNeeded >= 345) || (courseChangeNeeded < 15))
          Serial.println( F(“Keep on straight ahead!”) );
        else if ((courseChangeNeeded >= 315) && (courseChangeNeeded < 345))
          Serial.println( F(“Veer slightly to the left.”) );
        else if ((courseChangeNeeded >= 15) && (courseChangeNeeded < 45))
          Serial.println( F(“Veer slightly to the right.”) );
        else if ((courseChangeNeeded >= 255) && (courseChangeNeeded < 315))
          Serial.println( F(“Turn to the left.”) );
        else if ((courseChangeNeeded >= 45) && (courseChangeNeeded < 105))
          Serial.println( F(“Turn to the right.”) );
        else
          Serial.println( F(“Turn completely around.”) );
      } else {
        Serial.println( F(“Waiting for GPS fix…”) );
      }
    }
  }
}

//------------------------------------------------------------
//  This snippet is from NMEAaverage.  It keeps all the
//    compass direction strings in FLASH memory, saving RAM.

const char nCD  PROGMEM = “N”;
const char nneCD PROGMEM = “NNE”;
const char neCD PROGMEM = “NE”;
const char eneCD PROGMEM = “ENE”;
const char eCD  PROGMEM = “E”;
const char eseCD PROGMEM = “ESE”;
const char seCD PROGMEM = “SE”;
const char sseCD PROGMEM = “SSE”;
const char sCD  PROGMEM = “S”;
const char sswCD PROGMEM = “SSW”;
const char swCD PROGMEM = “SW”;
const char wswCD PROGMEM = “WSW”;
const char wCD  PROGMEM = “W”;
const char wnwCD PROGMEM = “WNW”;
const char nwCD PROGMEM = “NW”;
const char nnwCD PROGMEM = “NNW”;

const char * const dirStrings PROGMEM =
  { nCD, nneCD, neCD, eneCD, eCD, eseCD, seCD, sseCD,
    sCD, sswCD, swCD, wswCD, wCD, wnwCD, nwCD, nnwCD };

const __FlashStringHelper *compassDir( uint16_t bearing ) // degrees CW from N
{
  const int16_t directions    = sizeof(dirStrings)/sizeof(dirStrings[0]);
  const int16_t degreesPerDir = 360 / directions;
        int8_t  dir          = (bearing + degreesPerDir/2) / degreesPerDir;

while (dir < 0)
    dir += directions;
  while (dir >= directions)
    dir -= directions;

return (const __FlashStringHelper *) pgm_read_ptr( &dirStrings[ dir ] );

} // compassDir



The major change is using the 1-second GPS updates for the 5-second interval timer. After 5 GPS updates (fixes) have been received, you know that 5 seconds have elapsed. Although it's not very important for a 5-second interval, you should be aware that the Arduino `millis()` will drift against the GPS clock. Sometimes 5000ms will 4 GPS updates, 6 updates, or even 4.5 updates (some new data, some old data).

Also notice that it uses my [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial) instead of SoftwareSerial. SoftwareSerial is very inefficient, because it disables interrupts for long periods of time. It will use about 95% of the CPU time, just waiting. This can interfere with other parts of your sketch or with other libraries.

AltSoftSerial is the absolute best choice for a second serial port, but you have to connect the GPS Tx/Rx to specific Arduino pins (e.g., 8/9 on an UNO). Strongly recommended!

Other changes:

* The F macro around string constants is used to save RAM.
* The compass direction strings are stored in FLASH, also saving RAM.
* All GPS fields are checked for validity before being used.
* If the location is not valid, it just displays "Waiting for GPS fix..."

The original sketch uses 11540 bytes of program space and 892 bytes of RAM.
With NeoGPS/NeoSWSerial it uses 13010 bytes of program space and 408 bytes of RAM. NeoGPS saved about 100 bytes of RAM, and putting the strings in FLASH saved about 400 bytes of RAM.

If you want to try it, NeoGPS is available from the Arduino IDE Library Manager, under the menu **Sketch -> Include Library -> Manage Libraries**. Be sure to get v4.1.5, or download from github directly. Download NeoSWSerial from the link above.

Cheers,
/dev

Thanks /dev, I don’t think your could will work with me because Im using adafruit ultimate gps+logging shield. Or, is there a way to use your code with adafruit gps?

is there a way to use your code with adafruit gps?

Yes, NeoGPS supports all GPS devices that output the standard set of NMEA sentences, like RMC and GGA. Many people have used NeoGPS with the Adafruit GPS devices. It will not void your warranty. :wink:

If you are using the logging functions: There is one peciliarity of that shield: it has an SD card reader and a MTK3329 GPS device, and there are actually two ways to log information with that shield:

1) Parse GPS information with a library (Adafruit_GPS or NeoGPS) and write whatever you want to the SD card, using an Arduino library for the SD card. To retrive the logged data, just remove the SD card an open the SD file in your computer.

* The Adafruit example just writes the raw NMEA data to the SD card. There are many NMEA utilities for using the raw data.

* The NeoGPS example will write parsed values (e.g., lat/lon) in a CSV file format, not the raw NMEA characters. You can configure NeoGPS to enable or disable the GPS fields you want, and you can modify the sketch to change the format of the file. It is easy to import a CSV file into a spreadsheet, and there are many utilities that take CSV files of "Geo" information.

2) Send a command to the MTK3329 GPS device that enables its internal non-volatile memory. Adafruit does not recommend this technique, as there is limited space, a different sketch is required to retrieve the information from the GPS device (not the SD card), and you do not have any control over the format of the saved information. A NeoGPS sketch could start and stop the logging function, but you would still have to use a different sketch to retrieve the information from the GPS device.

Adafruit recommends the first approach, as do I. I also recommend saving parsed data, not raw. The log file will be 5 to 10 times smaller. I don't know your application, but this could affect battery life or system responsiveness.

If you are not using the logging functions, I would not suggest using the Adafruit library: it does not validate the checksum on GPS data. When combined with a software serial connection, it could produce invalid locations.

Cheers,
/dev

loyal979:
Hi all,

Im trying to calculate or printing the current course in deg. by using the function gps.course.deg() from tinygps library to give me my current course. but the course is jumping around and Im not sure how the course is being calculated.

The library you mentioned is available with full source code, so if you are interested in how the calculation is done, look up the source code!