/*! * \file gps.c * * \brief GPS driver implementation * * \copyright Revised BSD License, see section \ref LICENSE. * * \code * ______ _ * / _____) _ | | * ( (____ _____ ____ _| |_ _____ ____| |__ * \____ \| ___ | (_ _) ___ |/ ___) _ \ * _____) ) ____| | | || |_| ____( (___| | | | * (______/|_____)_|_|_| \__)_____)\____)_| |_| * (C)2013-2017 Semtech * * \endcode * * \author Miguel Luis ( Semtech ) * * \author Gregory Cristian ( Semtech ) */ #include #include #include #include #include #include "utilities.h" #include "board.h" #include "rtc-board.h" #include "gps-board.h" #include "gps.h" #define TRIGGER_GPS_CNT 10 /* Various type of NMEA data we can receive with the Gps */ const char NmeaDataTypeGPGGA[] = "GPGGA"; const char NmeaDataTypeGPGSA[] = "GPGSA"; const char NmeaDataTypeGPGSV[] = "GPGSV"; const char NmeaDataTypeGPRMC[] = "GPRMC"; /* Value used for the conversion of the position from DMS to decimal */ const int32_t MaxNorthPosition = 8388607; // 2^23 - 1 const int32_t MaxSouthPosition = 8388608; // -2^23 const int32_t MaxEastPosition = 8388607; // 2^23 - 1 const int32_t MaxWestPosition = 8388608; // -2^23 NmeaGpsData_t NmeaGpsData; static bool HasFix = false; static double Latitude = 0; static double Longitude = 0; static int32_t LatitudeBinary = 0; static int32_t LongitudeBinary = 0; static int16_t Altitude = ( int16_t )0xFFFF; static uint32_t PpsCnt = 0; bool PpsDetected = false; void GpsPpsHandler( bool *parseData ) { PpsDetected = true; PpsCnt++; *parseData = false; if( PpsCnt >= TRIGGER_GPS_CNT ) { PpsCnt = 0; *parseData = true; } } void GpsInit( void ) { PpsDetected = false; GpsMcuInit( ); } void GpsStart( void ) { GpsMcuStart( ); } void GpsStop( void ) { GpsMcuStop( ); } void GpsProcess( void ) { GpsMcuProcess( ); } bool GpsGetPpsDetectedState( void ) { bool state = false; CRITICAL_SECTION_BEGIN( ); state = PpsDetected; PpsDetected = false; CRITICAL_SECTION_END( ); return state; } bool GpsHasFix( void ) { return HasFix; } void GpsConvertPositionIntoBinary( void ) { long double temp; if( Latitude >= 0 ) // North { temp = Latitude * MaxNorthPosition; LatitudeBinary = temp / 90; } else // South { temp = Latitude * MaxSouthPosition; LatitudeBinary = temp / 90; } if( Longitude >= 0 ) // East { temp = Longitude * MaxEastPosition; LongitudeBinary = temp / 180; } else // West { temp = Longitude * MaxWestPosition; LongitudeBinary = temp / 180; } } void GpsConvertPositionFromStringToNumerical( void ) { int i; double valueTmp1; double valueTmp2; double valueTmp3; double valueTmp4; // Convert the latitude from ASCII to uint8_t values for( i = 0 ; i < 10 ; i++ ) { NmeaGpsData.NmeaLatitude[i] = NmeaGpsData.NmeaLatitude[i] & 0xF; } // Convert latitude from degree/minute/second (DMS) format into decimal valueTmp1 = ( double )NmeaGpsData.NmeaLatitude[0] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[1]; valueTmp2 = ( double )NmeaGpsData.NmeaLatitude[2] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[3]; valueTmp3 = ( double )NmeaGpsData.NmeaLatitude[5] * 1000.0 + ( double )NmeaGpsData.NmeaLatitude[6] * 100.0 + ( double )NmeaGpsData.NmeaLatitude[7] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[8]; Latitude = valueTmp1 + ( ( valueTmp2 + ( valueTmp3 * 0.0001 ) ) / 60.0 ); if( NmeaGpsData.NmeaLatitudePole[0] == 'S' ) { Latitude *= -1; } // Convert the longitude from ASCII to uint8_t values for( i = 0 ; i < 10 ; i++ ) { NmeaGpsData.NmeaLongitude[i] = NmeaGpsData.NmeaLongitude[i] & 0xF; } // Convert longitude from degree/minute/second (DMS) format into decimal valueTmp1 = ( double )NmeaGpsData.NmeaLongitude[0] * 100.0 + ( double )NmeaGpsData.NmeaLongitude[1] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[2]; valueTmp2 = ( double )NmeaGpsData.NmeaLongitude[3] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[4]; valueTmp3 = ( double )NmeaGpsData.NmeaLongitude[6] * 1000.0 + ( double )NmeaGpsData.NmeaLongitude[7] * 100; valueTmp4 = ( double )NmeaGpsData.NmeaLongitude[8] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[9]; Longitude = valueTmp1 + ( valueTmp2 / 60.0 ) + ( ( ( valueTmp3 + valueTmp4 ) * 0.0001 ) / 60.0 ); if( NmeaGpsData.NmeaLongitudePole[0] == 'W' ) { Longitude *= -1; } } LmnStatus_t GpsGetLatestGpsPositionDouble( double *lati, double *longi ) { LmnStatus_t status = LMN_STATUS_ERROR; if( HasFix == true ) { status = LMN_STATUS_OK; } else { GpsResetPosition( ); } *lati = Latitude; *longi = Longitude; return status; } LmnStatus_t GpsGetLatestGpsPositionBinary( int32_t *latiBin, int32_t *longiBin ) { LmnStatus_t status = LMN_STATUS_ERROR; CRITICAL_SECTION_BEGIN( ); if( HasFix == true ) { status = LMN_STATUS_OK; } else { GpsResetPosition( ); } *latiBin = LatitudeBinary; *longiBin = LongitudeBinary; CRITICAL_SECTION_END( ); return status; } int16_t GpsGetLatestGpsAltitude( void ) { CRITICAL_SECTION_BEGIN( ); if( HasFix == true ) { Altitude = atoi( NmeaGpsData.NmeaAltitude ); } else { Altitude = ( int16_t )0xFFFF; } CRITICAL_SECTION_END( ); return Altitude; } /*! * Calculates the checksum for a NMEA sentence * * Skip the first '$' if necessary and calculate checksum until '*' character is * reached (or buffSize exceeded). * * \retval chkPosIdx Position of the checksum in the sentence */ int32_t GpsNmeaChecksum( int8_t *nmeaStr, int32_t nmeaStrSize, int8_t * checksum ) { int i = 0; uint8_t checkNum = 0; // Check input parameters if( ( nmeaStr == NULL ) || ( checksum == NULL ) || ( nmeaStrSize <= 1 ) ) { return -1; } // Skip the first '$' if necessary if( nmeaStr[i] == '$' ) { i += 1; } // XOR until '*' or max length is reached while( nmeaStr[i] != '*' ) { checkNum ^= nmeaStr[i]; i += 1; if( i >= nmeaStrSize ) { return -1; } } // Convert checksum value to 2 hexadecimal characters checksum[0] = Nibble2HexChar( checkNum / 16 ); // upper nibble checksum[1] = Nibble2HexChar( checkNum % 16 ); // lower nibble return i + 1; } /*! * Calculate the checksum of a NMEA frame and compare it to the checksum that is * present at the end of it. * Return true if it matches */ static bool GpsNmeaValidateChecksum( int8_t *serialBuff, int32_t buffSize ) { int32_t checksumIndex; int8_t checksum[2]; // 2 characters to calculate NMEA checksum checksumIndex = GpsNmeaChecksum( serialBuff, buffSize, checksum ); // could we calculate a verification checksum ? if( checksumIndex < 0 ) { return false; } // check if there are enough char in the serial buffer to read checksum if( checksumIndex >= ( buffSize - 2 ) ) { return false; } // check the checksum if( ( serialBuff[checksumIndex] == checksum[0] ) && ( serialBuff[checksumIndex + 1] == checksum[1] ) ) { return true; } else { return false; } } LmnStatus_t GpsParseGpsData( int8_t *rxBuffer, int32_t rxBufferSize ) { uint8_t i = 1; uint8_t j = 0; uint8_t fieldSize = 0; if( rxBuffer[0] != '$' ) { GpsMcuInvertPpsTrigger( ); return LMN_STATUS_ERROR; } if( GpsNmeaValidateChecksum( rxBuffer, rxBufferSize ) == false ) { return LMN_STATUS_ERROR; } fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 6 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaDataType[j] = rxBuffer[i]; } // Parse the GPGGA data if( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPGGA, 5 ) == 0 ) { // NmeaUtcTime fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 11 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaUtcTime[j] = rxBuffer[i]; } // NmeaLatitude fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 10 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLatitude[j] = rxBuffer[i]; } // NmeaLatitudePole fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLatitudePole[j] = rxBuffer[i]; } // NmeaLongitude fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 11 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLongitude[j] = rxBuffer[i]; } // NmeaLongitudePole fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLongitudePole[j] = rxBuffer[i]; } // NmeaFixQuality fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaFixQuality[j] = rxBuffer[i]; } // NmeaSatelliteTracked fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 3 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaSatelliteTracked[j] = rxBuffer[i]; } // NmeaHorizontalDilution fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 6 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaHorizontalDilution[j] = rxBuffer[i]; } // NmeaAltitude fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 8 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaAltitude[j] = rxBuffer[i]; } // NmeaAltitudeUnit fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaAltitudeUnit[j] = rxBuffer[i]; } // NmeaHeightGeoid fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 8 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaHeightGeoid[j] = rxBuffer[i]; } // NmeaHeightGeoidUnit fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaHeightGeoidUnit[j] = rxBuffer[i]; } GpsFormatGpsData( ); return LMN_STATUS_OK; } else if ( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPRMC, 5 ) == 0 ) { // NmeaUtcTime fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 11 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaUtcTime[j] = rxBuffer[i]; } // NmeaDataStatus fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaDataStatus[j] = rxBuffer[i]; } // NmeaLatitude fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 10 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLatitude[j] = rxBuffer[i]; } // NmeaLatitudePole fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLatitudePole[j] = rxBuffer[i]; } // NmeaLongitude fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 11 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLongitude[j] = rxBuffer[i]; } // NmeaLongitudePole fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 2 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaLongitudePole[j] = rxBuffer[i]; } // NmeaSpeed fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 8 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaSpeed[j] = rxBuffer[i]; } // NmeaDetectionAngle fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 8 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaDetectionAngle[j] = rxBuffer[i]; } // NmeaDate fieldSize = 0; while( rxBuffer[i + fieldSize++] != ',' ) { if( fieldSize > 8 ) { return LMN_STATUS_ERROR; } } for( j = 0; j < fieldSize; j++, i++ ) { NmeaGpsData.NmeaDate[j] = rxBuffer[i]; } GpsFormatGpsData( ); return LMN_STATUS_OK; } else { return LMN_STATUS_ERROR; } } void GpsFormatGpsData( void ) { if( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPGGA, 5 ) == 0 ) { HasFix = ( NmeaGpsData.NmeaFixQuality[0] > 0x30 ) ? true : false; } else if ( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPRMC, 5 ) == 0 ) { HasFix = ( NmeaGpsData.NmeaDataStatus[0] == 0x41 ) ? true : false; } GpsConvertPositionFromStringToNumerical( ); GpsConvertPositionIntoBinary( ); } void GpsResetPosition( void ) { Altitude = ( int16_t )0xFFFF; Latitude = 0; Longitude = 0; LatitudeBinary = 0; LongitudeBinary = 0; }