Arduino TinyGPS Satellite Count Mod
TinyGPS
A new version is available.
The TinyGPS Library, by Mikal Hart, is a compact GPS library for Arduino. The only thing I saw missing from the library was a way to find the number of satellites in view, and the number of satellites being used (if a fix has been obtained).
Based on Mikal’s version 9 of the TinyGPS Library, I made some modifications to check the GPGSA and GPGSV sentences from the GPS.
Changes
A small change had to be made to the parsing logic.
Mikal makes the library only parse other GPS sentences when a fix has been obtained ((Technically, Mikal checks the GPRMC sentence to see if the Data Status field is reporting a valid position.)). To be able to report the data from the GPGSV sentences (satellites in view, among other things), data had to be available even when a fix was not available.
The change means that you can now check the number of satellites that are in view (i.e. satsinview()
) anytime.
As a result of the changes, another function that you can use to check the current fix type (i.e. no fix, 2D or 3D) is called fixtype()
. It returns one of:
TinyGPS::GPS_FIX_NO_FIX
(== 1) - No GPS fix.TinyGPS::GPS_FIX_2D
(== 2) - 2D GPS fix.TinyGPS::GPS_FIX_3D
(== 3) - 3D GPS fix. This means altitude data is valid.
(Check this handy site for details on the GPGSA sentence).
Example
Here is an example of how you check for valid data and/or check for the satellite count:
TinyGPS_SatsMod_Example.pde
#include <NewSoftSerial.h>
#include <TinyGPS.h>
TinyGPS gps;
NewSoftSerial nss(2, 3);
void setup()
{
Serial.begin(115200);
nss.begin(4800);
Serial.println("TinyGPS Satellite Count Mod Test");
Serial.println();
}
void loop()
{
if (nss.available())
{
if (gps.encode(nss.read()))
{
Serial.print("Satellites in view: ");
Serial.println(gps.satsinview(), DEC);
if (gps.fixtype() == TinyGPS::GPS_FIX_NO_FIX)
{
Serial.print("No fix.");
}
else
{
// we have a fix (could be GPS_FIX_2D or GPS_FIX_3D)
Serial.print("Fix type: ");
if (gps.fixtype() == TinyGPS::GPS_FIX_2D)
Serial.println("2D");
else
Serial.println("3D");
Serial.print("Satellites used: ");
Serial.println(gps.satsused(), DEC);
// You can do the rest of your coding here - e.g. gpsdump()
}
}
}
}
Downloads
You can download a zip of the entire modified library from the Rogue Code Google Code Repository.
Here are the individual source files (I don’t intend on creating a code repository for these, since these are modifications to Mikal’s Library - so if he wants to include these changes, it’s up to him):
TinyGPS.h
TinyGPS.h
/*
TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
Copyright (C) 2008-9 Mikal Hart
All rights reserved.
Satellite Count Mod - by Brett Hagman
http://www.roguerobotics.com/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef TinyGPS_h
#define TinyGPS_h
#include "WProgram.h"
#define _GPS_VERSION 9 // software version of this library
#define _GPS_MPH_PER_KNOT 1.15077945
#define _GPS_MPS_PER_KNOT 0.51444444
#define _GPS_KMPH_PER_KNOT 1.852
#define _GPS_MILES_PER_METER 0.00062137112
#define _GPS_KM_PER_METER 0.001
//#define _GPS_NO_STATS
class TinyGPS
{
public:
TinyGPS();
bool encode(char c); // process one character received from GPS
TinyGPS &operator « (char c) {encode(c); return *this;}
// lat/long in hundred thousandths of a degree and age of fix in milliseconds
inline void get_position(long *latitude, long *longitude, unsigned long *fix_age = 0)
{
if (latitude) *latitude = _latitude;
if (longitude) *longitude = _longitude;
if (fix_age) *fix_age = _last_position_fix == GPS_INVALID_FIX_TIME ?
GPS_INVALID_AGE : millis() - _last_position_fix;
}
// date as ddmmyy, time as hhmmsscc, and age in milliseconds
inline void get_datetime(unsigned long *date, unsigned long *time, unsigned long *fix_age = 0)
{
if (date) *date = _date;
if (time) *time = _time;
if (fix_age) *fix_age = _last_time_fix == GPS_INVALID_FIX_TIME ?
GPS_INVALID_AGE : millis() - _last_time_fix;
}
// signed altitude in centimeters (from GPGGA sentence)
inline long altitude() { return _altitude; }
// course in last full GPRMC sentence in 100th of a degree
inline unsigned long course() { return _course; }
// speed in last full GPRMC sentence in 100ths of a knot
unsigned long speed() { return _speed; }
// number of satellites in view (GPGSV sentence)
unsigned char satsinview() { return _satsinview; }
// number of satellites used for fix (GPGSA sentence)
unsigned char satsused() { return _satsused; }
// get the fix type
unsigned char fixtype() { return _fixtype; }
#ifndef _GPS_NO_STATS
void stats(unsigned long *chars, unsigned short *good_sentences, unsigned short *failed_cs);
#endif
inline void f_get_position(float *latitude, float *longitude, unsigned long *fix_age = 0)
{
long lat, lon;
get_position(&lat, &lon, fix_age);
*latitude = lat / 100000.0;
*longitude = lon / 100000.0;
}
inline void crack_datetime(int *year, byte *month, byte *day,
byte *hour, byte *minute, byte *second, byte *hundredths = 0, unsigned long *fix_age = 0)
{
unsigned long date, time;
get_datetime(&date, &time, fix_age);
if (year)
{
*year = date % 100;
*year += *year > 80 ? 1900 : 2000;
}
if (month) *month = (date / 100) % 100;
if (day) *day = date / 10000;
if (hour) *hour = time / 1000000;
if (minute) *minute = (time / 10000) % 100;
if (second) *second = (time / 100) % 100;
if (hundredths) *hundredths = time % 100;
}
inline float f_altitude() { return altitude() / 100.0; }
inline float f_course() { return course() / 100.0; }
inline float f_speed_knots() { return speed() / 100.0; }
inline float f_speed_mph() { return _GPS_MPH_PER_KNOT * f_speed_knots(); }
inline float f_speed_mps() { return _GPS_MPS_PER_KNOT * f_speed_knots(); }
inline float f_speed_kmph() { return _GPS_KMPH_PER_KNOT * f_speed_knots(); }
static int library_version() { return _GPS_VERSION; }
enum {GPS_INVALID_AGE = 0xFFFFFFFF, GPS_INVALID_ANGLE = 999999999, GPS_INVALID_ALTITUDE = 999999999, GPS_INVALID_DATE = 0,
GPS_INVALID_TIME = 0xFFFFFFFF, GPS_INVALID_SPEED = 999999999, GPS_INVALID_FIX_TIME = 0xFFFFFFFF};
enum {GPS_FIX_NO_FIX = 1, GPS_FIX_2D = 2, GPS_FIX_3D = 3};
private:
enum {_GPS_SENTENCE_GPGGA, _GPS_SENTENCE_GPRMC, _GPS_SENTENCE_GPGSV, _GPS_SENTENCE_GPGSA, _GPS_SENTENCE_OTHER};
// properties
unsigned long _time, _new_time;
unsigned long _date, _new_date;
long _latitude, _new_latitude;
long _longitude, _new_longitude;
long _altitude, _new_altitude;
unsigned long _speed, _new_speed;
unsigned long _course, _new_course;
unsigned char _satsinview, _new_satsinview;
unsigned char _satsused, _new_satsused;
unsigned char _fixtype, _new_fixtype;
unsigned long _last_time_fix, _new_time_fix;
unsigned long _last_position_fix, _new_position_fix;
// parsing state variables
byte _parity;
bool _is_checksum_term;
char _term[15];
byte _sentence_type;
byte _term_number;
byte _term_offset;
#ifndef _GPS_NO_STATS
// statistics
unsigned long _encoded_characters;
unsigned short _good_sentences;
unsigned short _failed_checksum;
unsigned short _passed_checksum;
#endif
// internal utilities
int from_hex(char a);
unsigned long parse_decimal();
unsigned long parse_degrees();
bool term_complete();
bool gpsisdigit(char c) { return c >= '0' && c <= '9'; }
long gpsatol(const char *str);
int gpsstrcmp(const char *str1, const char *str2);
};
// Arduino 0012 workaround
#undef int
#undef char
#undef long
#undef byte
#undef float
#undef abs
#undef round
#endif
TinyGPS.cpp
TinyGPS.cpp
/*
TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
Copyright (C) 2008-9 Mikal Hart
All rights reserved.
Satellite Count Mod - by Brett Hagman
http://www.roguerobotics.com/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WProgram.h"
#include "TinyGPS.h"
#define _GPRMC_TERM "GPRMC"
#define _GPGGA_TERM "GPGGA"
#define _GPGSV_TERM "GPGSV"
#define _GPGSA_TERM "GPGSA"
TinyGPS::TinyGPS()
: _time(GPS_INVALID_TIME)
, _date(GPS_INVALID_DATE)
, _latitude(GPS_INVALID_ANGLE)
, _longitude(GPS_INVALID_ANGLE)
, _altitude(GPS_INVALID_ALTITUDE)
, _speed(GPS_INVALID_SPEED)
, _course(GPS_INVALID_ANGLE)
, _satsinview(0)
, _satsused(0)
, _fixtype(GPS_FIX_NO_FIX)
, _last_time_fix(GPS_INVALID_FIX_TIME)
, _last_position_fix(GPS_INVALID_FIX_TIME)
, _parity(0)
, _is_checksum_term(false)
, _sentence_type(_GPS_SENTENCE_OTHER)
, _term_number(0)
, _term_offset(0)
#ifndef _GPS_NO_STATS
, _encoded_characters(0)
, _good_sentences(0)
, _failed_checksum(0)
#endif
{
_term[0] = '\0';
}
//
// public methods
//
bool TinyGPS::encode(char c)
{
bool valid_sentence = false;
++_encoded_characters;
switch(c)
{
case ',': // term terminators
_parity ^= c;
case '\r':
case '\n':
case '*':
if (_term_offset < sizeof(_term))
{
_term[_term_offset] = 0;
valid_sentence = term_complete();
}
++_term_number;
_term_offset = 0;
_is_checksum_term = c == '*';
return valid_sentence;
case '$': // sentence begin
_term_number = _term_offset = 0;
_parity = 0;
_sentence_type = _GPS_SENTENCE_OTHER;
_is_checksum_term = false;
return valid_sentence;
}
// ordinary characters
if (_term_offset < sizeof(_term) - 1)
_term[_term_offset++] = c;
if (!_is_checksum_term)
_parity ^= c;
return valid_sentence;
}
#ifndef _GPS_NO_STATS
void TinyGPS::stats(unsigned long *chars, unsigned short *sentences, unsigned short *failed_cs)
{
if (chars) *chars = _encoded_characters;
if (sentences) *sentences = _good_sentences;
if (failed_cs) *failed_cs = _failed_checksum;
}
#endif
//
// internal utilities
//
int TinyGPS::from_hex(char a)
{
if (a >= 'A' && a <= 'F')
return a - 'A' + 10;
else if (a >= 'a' && a <= 'f')
return a - 'a' + 10;
else
return a - '0';
}
unsigned long TinyGPS::parse_decimal()
{
char *p = _term;
bool isneg = *p == '-';
if (isneg) ++p;
unsigned long ret = 100UL * gpsatol(p);
while (gpsisdigit(*p)) ++p;
if (*p == '.')
{
if (gpsisdigit(p[1]))
{
ret += 10 * (p[1] - '0');
if (gpsisdigit(p[2]))
ret += p[2] - '0';
}
}
return isneg ? -ret : ret;
}
unsigned long TinyGPS::parse_degrees()
{
char *p;
unsigned long left = gpsatol(_term);
unsigned long tenk_minutes = (left % 100UL) * 10000UL;
for (p=_term; gpsisdigit(*p); ++p);
if (*p == '.')
{
unsigned long mult = 1000;
while (gpsisdigit(*++p))
{
tenk_minutes += mult * (*p - '0');
mult /= 10;
}
}
return (left / 100) * 100000 + tenk_minutes / 6;
}
// Processes a just-completed term
// Returns true if new sentence has just passed checksum test and is validated
bool TinyGPS::term_complete()
{
if (_is_checksum_term)
{
byte checksum = 16 * from_hex(_term[0]) + from_hex(_term[1]);
if (checksum == _parity)
{
#ifndef _GPS_NO_STATS
++_good_sentences;
#endif
_last_time_fix = _new_time_fix;
_last_position_fix = _new_position_fix;
switch(_sentence_type)
{
case _GPS_SENTENCE_GPRMC:
_time = _new_time;
_date = _new_date;
_latitude = _new_latitude;
_longitude = _new_longitude;
_speed = _new_speed;
_course = _new_course;
break;
case _GPS_SENTENCE_GPGGA:
_altitude = _new_altitude;
_time = _new_time;
_latitude = _new_latitude;
_longitude = _new_longitude;
break;
case _GPS_SENTENCE_GPGSV:
_satsinview = _new_satsinview;
break;
case _GPS_SENTENCE_GPGSA:
_satsused = _new_satsused;
_fixtype = _new_fixtype;
break;
}
return true;
}
#ifndef _GPS_NO_STATS
else
++_failed_checksum;
#endif
return false;
}
// the first term determines the sentence type
if (_term_number == 0)
{
if (!gpsstrcmp(_term, _GPRMC_TERM))
_sentence_type = _GPS_SENTENCE_GPRMC;
else if (!gpsstrcmp(_term, _GPGGA_TERM))
_sentence_type = _GPS_SENTENCE_GPGGA;
else if (!gpsstrcmp(_term, _GPGSV_TERM))
{
_sentence_type = _GPS_SENTENCE_GPGSV;
}
else if (!gpsstrcmp(_term, _GPGSA_TERM))
{
_sentence_type = _GPS_SENTENCE_GPGSA;
_new_satsused = 0;
}
else
_sentence_type = _GPS_SENTENCE_OTHER;
return false;
}
if (_sentence_type == _GPS_SENTENCE_GPGSV)
{
if (_term_number == 3 && _term[0])
{
// we've got our number of sats
// NOTE: we will more than likely hit this a few times in a row, because
// there are usually multiple GPGSV sentences to describe all of the sats
_new_satsinview = (unsigned char) gpsatol(_term);
}
}
else if (_sentence_type == _GPS_SENTENCE_GPGSA)
{
if (_term_number == 2 && _term[0]) // Fix type
{
_new_fixtype = (unsigned char) gpsatol(_term);
}
else if (_term_number >= 3 && _term_number <= 14 && _term[0]) // Count our sats used
{
_new_satsused++;
}
* if (_term_number == 15) * PDOP
* if (_term_number == 16) * HDOP
* if (_term_number == 17) * VDOP
}
else if (_sentence_type != _GPS_SENTENCE_OTHER && _term[0])
{
switch((_sentence_type == _GPS_SENTENCE_GPGGA ? 200 : 100) + _term_number)
{
case 101: // Time in both sentences
case 201:
_new_time = parse_decimal();
_new_time_fix = millis();
break;
case 103: // Latitude
case 202:
_new_latitude = parse_degrees();
_new_position_fix = millis();
break;
case 104: // N/S
case 203:
if (_term[0] == 'S')
_new_latitude = -_new_latitude;
break;
case 105: // Longitude
case 204:
_new_longitude = parse_degrees();
break;
case 106: // E/W
case 205:
if (_term[0] == 'W')
_new_longitude = -_new_longitude;
break;
case 107: // Speed (GPRMC)
_new_speed = parse_decimal();
break;
case 108: // Course (GPRMC)
_new_course = parse_decimal();
break;
case 109: // Date (GPRMC)
_new_date = gpsatol(_term);
break;
case 209: // Altitude (GPGGA)
_new_altitude = parse_decimal();
break;
}
}
return false;
}
long TinyGPS::gpsatol(const char *str)
{
long ret = 0;
while (gpsisdigit(*str))
ret = 10 * ret + *str++ - '0';
return ret;
}
int TinyGPS::gpsstrcmp(const char *str1, const char *str2)
{
while (*str1 && *str1 == *str2)
++str1, ++str2;
return *str1;
}
keywords.txt
keywords.txt
#######################################
# Syntax Coloring Map for TinyGPS
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
TinyGPS KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
encode KEYWORD2
get_position KEYWORD2
get_datetime KEYWORD2
altitude KEYWORD2
speed KEYWORD2
course KEYWORD2
stats KEYWORD2
f_get_position KEYWORD2
crack_datetime KEYWORD2
f_altitude KEYWORD2
f_course KEYWORD2
f_speed_knots KEYWORD2
f_speed_mph KEYWORD2
f_speed_mps KEYWORD2
f_speed_kmph KEYWORD2
library_version KEYWORD2
fixtype KEYWORD2
satsinview KEYWORD2
satsused KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
GPS_INVALID_AGE LITERAL1
GPS_INVALID_ANGLE LITERAL1
GPS_INVALID_ALTITUDE LITERAL1
GPS_INVALID_DATE LITERAL1
GPS_INVALID_TIME LITERAL1
GPS_FIX_NO_FIX LITERAL1
GPS_FIX_2D LITERAL1
GPS_FIX_3D LITERAL1