mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-02 10:24:11 +01:00
24a4c6b791
GPS module now updates GPSPosition UAVObject rather than the PositionActual object. The GPSPosition object is intended to be consumed only by the AHRS. The AHRS will use this (and other inputs) to compute a filtered version of the position in the PositionActual object. This commit will cause temporary breakage of the GPS functionality in the GCS until the PositionActual object is properly updated by the AHRS. Most of the GCS should continue to use PositionActual. The only exception to this might be any tool for specifically visualizing the raw GPS state. GPS.c is now only responsible for receiving a complete NMEA sentence from the COM interface. NMEA parsing is now factored out into NMEA.[ch] which is where GPSPosition is now updated based on the complete NMEA sentences obtained from the GPS. Latitude and Longitude are now encoded in a fixed-point notation in units of degrees x 10^-7 to prevent truncation of precision due to encoding into a float. git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@1431 ebee16cc-31ac-478f-84a7-5cbb03baadba
533 lines
14 KiB
C
533 lines
14 KiB
C
#include "openpilot.h"
|
|
#include "pios.h"
|
|
#include "NMEA.h"
|
|
#include "gpsposition.h"
|
|
|
|
// Debugging
|
|
//#define GPSDEBUG
|
|
#ifdef GPSDEBUG
|
|
#define NMEA_DEBUG_PKT ///< define to enable debug of all NMEA messages
|
|
#define NMEA_DEBUG_GGA ///< define to enable debug of GGA messages
|
|
#define NMEA_DEBUG_VTG ///< define to enable debug of VTG messages
|
|
#define NMEA_DEBUG_RMC ///< define to enable debug of RMC messages
|
|
#define NMEA_DEBUG_GSA ///< define to enable debug of GSA messages
|
|
#endif
|
|
|
|
/* Utility functions */
|
|
static float NMEA_real_to_float (char * nmea_real);
|
|
static bool NMEA_latlon_to_fixed_point (int32_t * latlon, char * nmea_latlon);
|
|
|
|
/* NMEA sentence parsers */
|
|
|
|
struct nmea_parser {
|
|
const char * prefix;
|
|
bool (*handler)(GPSPositionData * GpsData, char * sentence);
|
|
};
|
|
|
|
static bool nmeaProcessGPGGA (GPSPositionData * GpsData, char * sentence);
|
|
static bool nmeaProcessGPRMC (GPSPositionData * GpsData, char * sentence);
|
|
static bool nmeaProcessGPVTG (GPSPositionData * GpsData, char * sentence);
|
|
static bool nmeaProcessGPGSA (GPSPositionData * GpsData, char * sentence);
|
|
|
|
static struct nmea_parser nmea_parsers[] = {
|
|
{
|
|
.prefix = "GPGGA",
|
|
.handler = nmeaProcessGPGGA,
|
|
},
|
|
{
|
|
.prefix = "GPVTG",
|
|
.handler = nmeaProcessGPVTG,
|
|
},
|
|
{
|
|
.prefix = "GPGSA",
|
|
.handler = nmeaProcessGPGSA,
|
|
},
|
|
{
|
|
.prefix = "GPRMC",
|
|
.handler = nmeaProcessGPRMC,
|
|
},
|
|
};
|
|
|
|
|
|
|
|
static struct nmea_parser * NMEA_find_parser_by_prefix (char * prefix)
|
|
{
|
|
if (!prefix) {
|
|
return (NULL);
|
|
}
|
|
|
|
for (uint8_t i = 0; i < NELEMENTS(nmea_parsers); i++) {
|
|
struct nmea_parser * parser = &nmea_parsers[i];
|
|
|
|
/* Use strcmp to check for exact equality over the entire prefix */
|
|
if (!strcmp(prefix, parser->prefix)) {
|
|
/* Found an appropriate parser */
|
|
return (parser);
|
|
}
|
|
}
|
|
|
|
/* No matching parser for this prefix */
|
|
return (NULL);
|
|
}
|
|
|
|
/**
|
|
* Computes NMEA sentence checksum
|
|
* \param[in] Buffer for parsed nmea sentence
|
|
* \return false checksum not valid
|
|
* \return true checksum valid
|
|
*/
|
|
bool NMEA_checksum (char * nmea_sentence)
|
|
{
|
|
uint8_t checksum_computed = 0;
|
|
uint8_t checksum_received;
|
|
|
|
while (*nmea_sentence != '\0' &&
|
|
*nmea_sentence != '*') {
|
|
checksum_computed ^= *nmea_sentence;
|
|
nmea_sentence++;
|
|
}
|
|
|
|
/* Make sure we're now pointing at the checksum */
|
|
if (*nmea_sentence == '\0') {
|
|
/* Buffer ran out before we found a checksum marker */
|
|
return false;
|
|
}
|
|
|
|
/* Load the checksum from the buffer */
|
|
checksum_received = strtol(nmea_sentence + 1, NULL, 16);
|
|
|
|
//PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%d=%d\r\n",checksum_received,checksum_computed);
|
|
|
|
return (checksum_computed == checksum_received);
|
|
}
|
|
|
|
/**
|
|
* Parses a complete NMEA sentence and updates the GPSPosition UAVObject
|
|
* \param[in] An NMEA sentence with a valid checksum
|
|
* \return true if the sentence was successfully parsed
|
|
* \return false if any errors were encountered with the parsing
|
|
*/
|
|
bool NMEA_update_position (char * nmea_sentence)
|
|
{
|
|
char * sentence = nmea_sentence;
|
|
struct nmea_parser * parser;
|
|
char * prefix;
|
|
|
|
/* Find out what kind of NMEA packet we're dealing with */
|
|
prefix = strsep(&sentence, ",");
|
|
|
|
/* Check if we have a parser for this packet type */
|
|
parser = NMEA_find_parser_by_prefix (prefix);
|
|
|
|
if (!parser) {
|
|
/* Valid but unhandled packet type */
|
|
return false;
|
|
}
|
|
|
|
/* Found a matching parser for this packet type */
|
|
|
|
/* Reject empty (but valid) packets without parsing */
|
|
if (sentence[0] == ',' &&
|
|
sentence[1] == ',' &&
|
|
sentence[2] == ',') {
|
|
/* Nothing to parse, */
|
|
return false;
|
|
}
|
|
|
|
/* Parse the sentence and update the GpsData object */
|
|
|
|
GPSPositionData GpsData;
|
|
GPSPositionGet(&GpsData);
|
|
if (!parser->handler(&GpsData, sentence)) {
|
|
/* Parse failed for valid checksum. Do not update the UAVObject. */
|
|
return false;
|
|
}
|
|
GPSPositionSet(&GpsData);
|
|
|
|
/* Tell the caller what kind of packet we just parsed */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse an NMEA GPGGA sentence and update the given UAVObject
|
|
* \param[in] A pointer to a GPSPosition UAVObject to be updated.
|
|
* \param[in] An NMEA sentence with a valid checksum
|
|
*/
|
|
static bool nmeaProcessGPGGA(GPSPositionData * GpsData, char * sentence)
|
|
{
|
|
char * next = sentence;
|
|
char * tokens;
|
|
char * delimiter = ",*";
|
|
|
|
#ifdef NMEA_DEBUG_GGA
|
|
PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%s\r\n",sentence);
|
|
#endif
|
|
|
|
// get UTC time [hhmmss.sss]
|
|
tokens = strsep(&next, delimiter);
|
|
//strcpy(GpsInfo.TimeOfFix,tokens);
|
|
|
|
// next field: latitude
|
|
// get latitude [DDMM.mmmmm]
|
|
tokens = strsep(&next, delimiter);
|
|
if (!NMEA_latlon_to_fixed_point(&GpsData->Latitude, tokens)) {
|
|
return false;
|
|
}
|
|
|
|
// next field: N/S indicator
|
|
// correct latitute for N/S
|
|
tokens = strsep(&next, delimiter);
|
|
if(tokens[0] == 'S') GpsData->Latitude = -GpsData->Latitude;
|
|
|
|
// next field: longitude
|
|
// get longitude [dddmm.mmmmm]
|
|
tokens = strsep(&next, delimiter);
|
|
if (!NMEA_latlon_to_fixed_point(&GpsData->Longitude, tokens)) {
|
|
return false;
|
|
}
|
|
|
|
// next field: E/W indicator
|
|
// correct longitude for E/W
|
|
tokens = strsep(&next, delimiter);
|
|
if(tokens[0] == 'W') GpsData->Longitude = -GpsData->Longitude;
|
|
|
|
// next field: position fix status
|
|
// position fix status
|
|
// 0 = Invalid, 1 = Valid SPS, 2 = Valid DGPS, 3 = Valid PPS
|
|
// check for good position fix
|
|
tokens = strsep(&next, delimiter);
|
|
//if((tokens[0] != '0') || (tokens[0] != 0))
|
|
// GpsData.Updates++;
|
|
|
|
// next field: satellites used
|
|
// get number of satellites used in GPS solution
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->Satellites=atoi(tokens);
|
|
|
|
// next field: HDOP (horizontal dilution of precision)
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: altitude
|
|
// get altitude (in meters mm.m)
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->Altitude = NMEA_real_to_float (tokens);
|
|
|
|
// next field: altitude units, always 'M'
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: geoid separation
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->GeoidSeparation = NMEA_real_to_float (tokens);
|
|
|
|
// next field: separation units
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: DGPS age
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: DGPS station ID
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: checksum
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse an NMEA GPRMC sentence and update the given UAVObject
|
|
* \param[in] A pointer to a GPSPosition UAVObject to be updated.
|
|
* \param[in] An NMEA sentence with a valid checksum
|
|
*/
|
|
static bool nmeaProcessGPRMC (GPSPositionData * GpsData, char * sentence)
|
|
{
|
|
char * next = sentence;
|
|
char * tokens;
|
|
char * delimiter = ",*";
|
|
|
|
#ifdef NMEA_DEBUG_RMC
|
|
PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%s\r\n",sentence);
|
|
#endif
|
|
|
|
// get UTC time [hhmmss.sss]
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: Navigation receiver warning A = OK, V = warning
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: latitude
|
|
// get latitude [ddmm.mmmmm]
|
|
tokens = strsep(&next, delimiter);
|
|
if (!NMEA_latlon_to_fixed_point(&GpsData->Latitude, tokens)) {
|
|
return false;
|
|
}
|
|
|
|
// next field: N/S indicator
|
|
// correct latitute for N/S
|
|
tokens = strsep(&next, delimiter);
|
|
if(tokens[0] == 'S') GpsData->Latitude = -GpsData->Latitude;
|
|
|
|
// next field: longitude
|
|
// get longitude [dddmm.mmmmm]
|
|
tokens = strsep(&next, delimiter);
|
|
if (!NMEA_latlon_to_fixed_point(&GpsData->Longitude, tokens)) {
|
|
return false;
|
|
}
|
|
|
|
// next field: E/W indicator
|
|
// correct latitute for E/W
|
|
tokens = strsep(&next, delimiter);
|
|
if(tokens[0] == 'W') GpsData->Longitude = -GpsData->Longitude;
|
|
|
|
// next field: speed (knots)
|
|
// get speed in knots
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->Groundspeed = NMEA_real_to_float (tokens);
|
|
GpsData->Groundspeed *= 0.51444;
|
|
|
|
// next field: True course
|
|
// get True course
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->Heading = NMEA_real_to_float (tokens);
|
|
|
|
// next field: Date of fix
|
|
// get Date of fix
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: Magnetic variation
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: E or W
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: Mode: A=autonomous, D=differential, E=Estimated, N=not valid, S=Simulator
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: checksum
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse an NMEA GPVTG sentence and update the given UAVObject
|
|
* \param[in] A pointer to a GPSPosition UAVObject to be updated.
|
|
* \param[in] An NMEA sentence with a valid checksum
|
|
*/
|
|
static bool nmeaProcessGPVTG (GPSPositionData * GpsData, char * sentence)
|
|
{
|
|
char * next = sentence;
|
|
char * tokens;
|
|
char * delimiter = ",*";
|
|
|
|
#ifdef NMEA_DEBUG_VTG
|
|
PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%s\r\n",sentence);
|
|
#endif
|
|
|
|
// get course (true north ref) in degrees [ddd.dd]
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: 'T'
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: course (magnetic north)
|
|
// get course (magnetic north ref) in degrees [ddd.dd]
|
|
tokens = strsep(&next, delimiter);
|
|
// next field: 'M'
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: speed (knots)
|
|
// get speed in knots
|
|
tokens = strsep(&next, delimiter);
|
|
// next field: 'N'
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: speed (km/h)
|
|
// get speed in km/h
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: 'K'
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: checksum
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse an NMEA GPGSA sentence and update the given UAVObject
|
|
* \param[in] A pointer to a GPSPosition UAVObject to be updated.
|
|
* \param[in] An NMEA sentence with a valid checksum
|
|
*/
|
|
static bool nmeaProcessGPGSA (GPSPositionData * GpsData, char * sentence)
|
|
{
|
|
char * next = sentence;
|
|
char * tokens;
|
|
char * delimiter = ",*";
|
|
|
|
#ifdef NMEA_DEBUG_GSA
|
|
PIOS_COM_SendFormattedStringNonBlocking(COM_DEBUG_USART,"$%s\r\n",sentence);
|
|
#endif
|
|
|
|
// next field: Mode
|
|
// Mode: M=Manual, forced to operate in 2D or 3D, A=Automatic, 3D/2D
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: Mode
|
|
// Mode: 1=Fix not available, 2=2D, 3=3D
|
|
tokens = strsep(&next, delimiter);
|
|
switch (atoi(tokens)) {
|
|
case 1:
|
|
GpsData->Status = GPSPOSITION_STATUS_NOFIX;
|
|
break;
|
|
case 2:
|
|
GpsData->Status = GPSPOSITION_STATUS_FIX2D;
|
|
break;
|
|
case 3:
|
|
GpsData->Status = GPSPOSITION_STATUS_FIX3D;
|
|
break;
|
|
default:
|
|
/* Unhandled */
|
|
PIOS_DEBUG_Assert(0);
|
|
break;
|
|
}
|
|
|
|
// next field: 3-14 IDs of SVs used in position fix (null for unused fields)
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
// next field: PDOP
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->PDOP = NMEA_real_to_float (tokens);
|
|
|
|
// next field: HDOP
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->HDOP = NMEA_real_to_float (tokens);
|
|
|
|
// next field: VDOP
|
|
tokens = strsep(&next, delimiter);
|
|
GpsData->VDOP = NMEA_real_to_float (tokens);
|
|
|
|
// next field: checksum
|
|
tokens = strsep(&next, delimiter);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Parse a number encoded in a string of the format:
|
|
* [-]NN.nnnnn
|
|
* into a signed whole part and an unsigned fractional part.
|
|
* The fract_units field indicates the units of the fractional part as
|
|
* 1 whole = 10^fract_units fract
|
|
*/
|
|
static inline bool NMEA_parse_real (int32_t * whole, uint32_t * fract, uint8_t * fract_units, char * field)
|
|
{
|
|
char * s = field;
|
|
char * field_w;
|
|
char * field_f;
|
|
|
|
PIOS_DEBUG_Assert (whole);
|
|
PIOS_DEBUG_Assert (fract);
|
|
PIOS_DEBUG_Assert (fract_units);
|
|
|
|
field_w = strsep(&s, ".");
|
|
field_f = s;
|
|
|
|
*whole = strtol (field_w, NULL, 10);
|
|
|
|
if (field_w) {
|
|
/* decimal was found so we may have a fractional part */
|
|
*fract = strtoul (field_f, NULL, 10);
|
|
*fract_units = strlen(field_f);
|
|
} else {
|
|
/* no decimal was found, fractional part is zero */
|
|
*fract = 0;
|
|
*fract_units = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This function only exists to deal with a linking
|
|
* failure in the stdlib function strtof(). This
|
|
* implementation does not rely on the _sbrk() syscall
|
|
* like strtof() does.
|
|
*/
|
|
|
|
float NMEA_real_to_float (char * nmea_real)
|
|
{
|
|
int32_t whole;
|
|
uint32_t fract;
|
|
uint8_t fract_units;
|
|
|
|
/* Sanity checks */
|
|
PIOS_DEBUG_Assert (nmea_real);
|
|
|
|
if (! NMEA_parse_real (&whole, &fract, &fract_units, nmea_real)) {
|
|
return false;
|
|
}
|
|
|
|
/* Convert to float */
|
|
return (((float)whole) + fract * pow(10, -fract_units));
|
|
}
|
|
|
|
/*
|
|
* Parse a field in the format:
|
|
* DD[D]MM.mmmm[mm]
|
|
* into a fixed-point representation in units of (degrees * 1e-7)
|
|
*/
|
|
bool NMEA_latlon_to_fixed_point (int32_t * latlon, char * nmea_latlon)
|
|
{
|
|
int32_t num_DDDMM;
|
|
uint32_t num_m;
|
|
uint8_t units;
|
|
|
|
/* Sanity checks */
|
|
PIOS_DEBUG_Assert (nmea_latlon);
|
|
PIOS_DEBUG_Assert (latlon);
|
|
|
|
if (! NMEA_parse_real (&num_DDDMM, &num_m, &units, nmea_latlon)) {
|
|
return false;
|
|
}
|
|
|
|
/* scale up the mmmm[mm] field apropriately depending on # of digits */
|
|
switch (units) {
|
|
case 0:
|
|
/* no digits, value is zero so no scaling */
|
|
break;
|
|
case 4: /* mmmm */
|
|
num_m *= 1e3; /* mmmm000 */
|
|
break;
|
|
case 5: /* mmmmm */
|
|
num_m *= 1e2; /* mmmmm00 */
|
|
break;
|
|
case 6: /* mmmmmm */
|
|
num_m *= 1e1; /* mmmmmm0 */
|
|
break;
|
|
default:
|
|
/* unhandled format */
|
|
PIOS_DEBUG_Assert(0);
|
|
break;
|
|
}
|
|
|
|
*latlon = (num_DDDMM / 100) * 1e7; /* scale the whole degrees */
|
|
*latlon += (num_DDDMM % 100) * 1e7 / 60; /* add in the scaled decimal whole minutes */
|
|
*latlon += num_m / 60; /* add in the scaled decimal fractional minutes */
|
|
|
|
return true;
|
|
}
|