1 /*!
2  * \file      gps.c
3  *
4  * \brief     GPS driver implementation
5  *
6  * \copyright Revised BSD License, see section \ref LICENSE.
7  *
8  * \code
9  *                ______                              _
10  *               / _____)             _              | |
11  *              ( (____  _____ ____ _| |_ _____  ____| |__
12  *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
13  *               _____) ) ____| | | || |_| ____( (___| | | |
14  *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
15  *              (C)2013-2017 Semtech
16  *
17  * \endcode
18  *
19  * \author    Miguel Luis ( Semtech )
20  *
21  * \author    Gregory Cristian ( Semtech )
22  */
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include "utilities.h"
29 #include "board.h"
30 #include "rtc-board.h"
31 #include "gps-board.h"
32 #include "gps.h"
33 
34 #define TRIGGER_GPS_CNT                             10
35 
36 /* Various type of NMEA data we can receive with the Gps */
37 const char NmeaDataTypeGPGGA[] = "GPGGA";
38 const char NmeaDataTypeGPGSA[] = "GPGSA";
39 const char NmeaDataTypeGPGSV[] = "GPGSV";
40 const char NmeaDataTypeGPRMC[] = "GPRMC";
41 
42 /* Value used for the conversion of the position from DMS to decimal */
43 const int32_t MaxNorthPosition = 8388607;       // 2^23 - 1
44 const int32_t MaxSouthPosition = 8388608;       // -2^23
45 const int32_t MaxEastPosition = 8388607;        // 2^23 - 1
46 const int32_t MaxWestPosition = 8388608;        // -2^23
47 
48 NmeaGpsData_t NmeaGpsData;
49 
50 static bool HasFix = false;
51 static double Latitude = 0;
52 static double Longitude = 0;
53 
54 static int32_t LatitudeBinary = 0;
55 static int32_t LongitudeBinary = 0;
56 
57 static int16_t Altitude = ( int16_t )0xFFFF;
58 
59 static uint32_t PpsCnt = 0;
60 
61 bool PpsDetected = false;
62 
GpsPpsHandler(bool * parseData)63 void GpsPpsHandler( bool *parseData )
64 {
65     PpsDetected = true;
66     PpsCnt++;
67     *parseData = false;
68 
69     if( PpsCnt >= TRIGGER_GPS_CNT )
70     {
71         PpsCnt = 0;
72         *parseData = true;
73     }
74 }
75 
GpsInit(void)76 void GpsInit( void )
77 {
78     PpsDetected = false;
79     GpsMcuInit( );
80 }
81 
GpsStart(void)82 void GpsStart( void )
83 {
84     GpsMcuStart( );
85 }
86 
GpsStop(void)87 void GpsStop( void )
88 {
89     GpsMcuStop( );
90 }
91 
GpsProcess(void)92 void GpsProcess( void )
93 {
94     GpsMcuProcess( );
95 }
96 
GpsGetPpsDetectedState(void)97 bool GpsGetPpsDetectedState( void )
98 {
99     bool state = false;
100 
101     CRITICAL_SECTION_BEGIN( );
102     state = PpsDetected;
103     PpsDetected = false;
104     CRITICAL_SECTION_END( );
105     return state;
106 }
107 
GpsHasFix(void)108 bool GpsHasFix( void )
109 {
110     return HasFix;
111 }
112 
GpsConvertPositionIntoBinary(void)113 void GpsConvertPositionIntoBinary( void )
114 {
115     long double temp;
116 
117     if( Latitude >= 0 ) // North
118     {
119         temp = Latitude * MaxNorthPosition;
120         LatitudeBinary = temp / 90;
121     }
122     else                // South
123     {
124         temp = Latitude * MaxSouthPosition;
125         LatitudeBinary = temp / 90;
126     }
127 
128     if( Longitude >= 0 ) // East
129     {
130         temp = Longitude * MaxEastPosition;
131         LongitudeBinary = temp / 180;
132     }
133     else                // West
134     {
135         temp = Longitude * MaxWestPosition;
136         LongitudeBinary = temp / 180;
137     }
138 }
139 
GpsConvertPositionFromStringToNumerical(void)140 void GpsConvertPositionFromStringToNumerical( void )
141 {
142     int i;
143 
144     double valueTmp1;
145     double valueTmp2;
146     double valueTmp3;
147     double valueTmp4;
148 
149     // Convert the latitude from ASCII to uint8_t values
150     for( i = 0 ; i < 10 ; i++ )
151     {
152         NmeaGpsData.NmeaLatitude[i] = NmeaGpsData.NmeaLatitude[i] & 0xF;
153     }
154     // Convert latitude from degree/minute/second (DMS) format into decimal
155     valueTmp1 = ( double )NmeaGpsData.NmeaLatitude[0] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[1];
156     valueTmp2 = ( double )NmeaGpsData.NmeaLatitude[2] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[3];
157     valueTmp3 = ( double )NmeaGpsData.NmeaLatitude[5] * 1000.0 + ( double )NmeaGpsData.NmeaLatitude[6] * 100.0 +
158                 ( double )NmeaGpsData.NmeaLatitude[7] * 10.0 + ( double )NmeaGpsData.NmeaLatitude[8];
159 
160     Latitude = valueTmp1 + ( ( valueTmp2 + ( valueTmp3 * 0.0001 ) ) / 60.0 );
161 
162     if( NmeaGpsData.NmeaLatitudePole[0] == 'S' )
163     {
164         Latitude *= -1;
165     }
166 
167     // Convert the longitude from ASCII to uint8_t values
168     for( i = 0 ; i < 10 ; i++ )
169     {
170         NmeaGpsData.NmeaLongitude[i] = NmeaGpsData.NmeaLongitude[i] & 0xF;
171     }
172     // Convert longitude from degree/minute/second (DMS) format into decimal
173     valueTmp1 = ( double )NmeaGpsData.NmeaLongitude[0] * 100.0 + ( double )NmeaGpsData.NmeaLongitude[1] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[2];
174     valueTmp2 = ( double )NmeaGpsData.NmeaLongitude[3] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[4];
175     valueTmp3 = ( double )NmeaGpsData.NmeaLongitude[6] * 1000.0 + ( double )NmeaGpsData.NmeaLongitude[7] * 100;
176     valueTmp4 = ( double )NmeaGpsData.NmeaLongitude[8] * 10.0 + ( double )NmeaGpsData.NmeaLongitude[9];
177 
178     Longitude = valueTmp1 + ( valueTmp2 / 60.0 ) + ( ( ( valueTmp3 + valueTmp4 ) * 0.0001 ) / 60.0 );
179 
180     if( NmeaGpsData.NmeaLongitudePole[0] == 'W' )
181     {
182         Longitude *= -1;
183     }
184 }
185 
186 
GpsGetLatestGpsPositionDouble(double * lati,double * longi)187 LmnStatus_t GpsGetLatestGpsPositionDouble( double *lati, double *longi )
188 {
189     LmnStatus_t status = LMN_STATUS_ERROR;
190     if( HasFix == true )
191     {
192         status = LMN_STATUS_OK;
193     }
194     else
195     {
196         GpsResetPosition( );
197     }
198     *lati = Latitude;
199     *longi = Longitude;
200     return status;
201 }
202 
GpsGetLatestGpsPositionBinary(int32_t * latiBin,int32_t * longiBin)203 LmnStatus_t GpsGetLatestGpsPositionBinary( int32_t *latiBin, int32_t *longiBin )
204 {
205     LmnStatus_t status = LMN_STATUS_ERROR;
206 
207     CRITICAL_SECTION_BEGIN( );
208     if( HasFix == true )
209     {
210         status = LMN_STATUS_OK;
211     }
212     else
213     {
214         GpsResetPosition( );
215     }
216     *latiBin = LatitudeBinary;
217     *longiBin = LongitudeBinary;
218     CRITICAL_SECTION_END( );
219     return status;
220 }
221 
GpsGetLatestGpsAltitude(void)222 int16_t GpsGetLatestGpsAltitude( void )
223 {
224     CRITICAL_SECTION_BEGIN( );
225     if( HasFix == true )
226     {
227         Altitude = atoi( NmeaGpsData.NmeaAltitude );
228     }
229     else
230     {
231         Altitude = ( int16_t )0xFFFF;
232     }
233     CRITICAL_SECTION_END( );
234 
235     return Altitude;
236 }
237 
238 /*!
239  * Calculates the checksum for a NMEA sentence
240  *
241  * Skip the first '$' if necessary and calculate checksum until '*' character is
242  * reached (or buffSize exceeded).
243  *
244  * \retval chkPosIdx Position of the checksum in the sentence
245  */
GpsNmeaChecksum(int8_t * nmeaStr,int32_t nmeaStrSize,int8_t * checksum)246 int32_t GpsNmeaChecksum( int8_t *nmeaStr, int32_t nmeaStrSize, int8_t * checksum )
247 {
248     int i = 0;
249     uint8_t checkNum = 0;
250 
251     // Check input parameters
252     if( ( nmeaStr == NULL ) || ( checksum == NULL ) || ( nmeaStrSize <= 1 ) )
253     {
254         return -1;
255     }
256 
257     // Skip the first '$' if necessary
258     if( nmeaStr[i] == '$' )
259     {
260         i += 1;
261     }
262 
263     // XOR until '*' or max length is reached
264     while( nmeaStr[i] != '*' )
265     {
266         checkNum ^= nmeaStr[i];
267         i += 1;
268         if( i >= nmeaStrSize )
269         {
270             return -1;
271         }
272     }
273 
274     // Convert checksum value to 2 hexadecimal characters
275     checksum[0] = Nibble2HexChar( checkNum / 16 ); // upper nibble
276     checksum[1] = Nibble2HexChar( checkNum % 16 ); // lower nibble
277 
278     return i + 1;
279 }
280 
281 /*!
282  * Calculate the checksum of a NMEA frame and compare it to the checksum that is
283  * present at the end of it.
284  * Return true if it matches
285  */
GpsNmeaValidateChecksum(int8_t * serialBuff,int32_t buffSize)286 static bool GpsNmeaValidateChecksum( int8_t *serialBuff, int32_t buffSize )
287 {
288     int32_t checksumIndex;
289     int8_t checksum[2]; // 2 characters to calculate NMEA checksum
290 
291     checksumIndex = GpsNmeaChecksum( serialBuff, buffSize, checksum );
292 
293     // could we calculate a verification checksum ?
294     if( checksumIndex < 0 )
295     {
296         return false;
297     }
298 
299     // check if there are enough char in the serial buffer to read checksum
300     if( checksumIndex >= ( buffSize - 2 ) )
301     {
302         return false;
303     }
304 
305     // check the checksum
306     if( ( serialBuff[checksumIndex] == checksum[0] ) && ( serialBuff[checksumIndex + 1] == checksum[1] ) )
307     {
308         return true;
309     }
310     else
311     {
312         return false;
313     }
314 }
315 
GpsParseGpsData(int8_t * rxBuffer,int32_t rxBufferSize)316 LmnStatus_t GpsParseGpsData( int8_t *rxBuffer, int32_t rxBufferSize )
317 {
318     uint8_t i = 1;
319     uint8_t j = 0;
320     uint8_t fieldSize = 0;
321 
322     if( rxBuffer[0] != '$' )
323     {
324         GpsMcuInvertPpsTrigger( );
325         return LMN_STATUS_ERROR;
326     }
327 
328     if( GpsNmeaValidateChecksum( rxBuffer, rxBufferSize ) == false )
329     {
330         return LMN_STATUS_ERROR;
331     }
332 
333     fieldSize = 0;
334     while( rxBuffer[i + fieldSize++] != ',' )
335     {
336         if( fieldSize > 6 )
337         {
338             return LMN_STATUS_ERROR;
339         }
340     }
341     for( j = 0; j < fieldSize; j++, i++ )
342     {
343         NmeaGpsData.NmeaDataType[j] = rxBuffer[i];
344     }
345     // Parse the GPGGA data
346     if( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPGGA, 5 ) == 0 )
347     {
348         // NmeaUtcTime
349         fieldSize = 0;
350         while( rxBuffer[i + fieldSize++] != ',' )
351         {
352             if( fieldSize > 11 )
353             {
354                 return LMN_STATUS_ERROR;
355             }
356         }
357         for( j = 0; j < fieldSize; j++, i++ )
358         {
359             NmeaGpsData.NmeaUtcTime[j] = rxBuffer[i];
360         }
361         // NmeaLatitude
362         fieldSize = 0;
363         while( rxBuffer[i + fieldSize++] != ',' )
364         {
365             if( fieldSize > 10 )
366             {
367                 return LMN_STATUS_ERROR;
368             }
369         }
370         for( j = 0; j < fieldSize; j++, i++ )
371         {
372             NmeaGpsData.NmeaLatitude[j] = rxBuffer[i];
373         }
374         // NmeaLatitudePole
375         fieldSize = 0;
376         while( rxBuffer[i + fieldSize++] != ',' )
377         {
378             if( fieldSize > 2 )
379             {
380                 return LMN_STATUS_ERROR;
381             }
382         }
383         for( j = 0; j < fieldSize; j++, i++ )
384         {
385             NmeaGpsData.NmeaLatitudePole[j] = rxBuffer[i];
386         }
387         // NmeaLongitude
388         fieldSize = 0;
389         while( rxBuffer[i + fieldSize++] != ',' )
390         {
391             if( fieldSize > 11 )
392             {
393                 return LMN_STATUS_ERROR;
394             }
395         }
396         for( j = 0; j < fieldSize; j++, i++ )
397         {
398             NmeaGpsData.NmeaLongitude[j] = rxBuffer[i];
399         }
400         // NmeaLongitudePole
401         fieldSize = 0;
402         while( rxBuffer[i + fieldSize++] != ',' )
403         {
404             if( fieldSize > 2 )
405             {
406                 return LMN_STATUS_ERROR;
407             }
408         }
409         for( j = 0; j < fieldSize; j++, i++ )
410         {
411             NmeaGpsData.NmeaLongitudePole[j] = rxBuffer[i];
412         }
413         // NmeaFixQuality
414         fieldSize = 0;
415         while( rxBuffer[i + fieldSize++] != ',' )
416         {
417             if( fieldSize > 2 )
418             {
419                 return LMN_STATUS_ERROR;
420             }
421         }
422         for( j = 0; j < fieldSize; j++, i++ )
423         {
424             NmeaGpsData.NmeaFixQuality[j] = rxBuffer[i];
425         }
426         // NmeaSatelliteTracked
427         fieldSize = 0;
428         while( rxBuffer[i + fieldSize++] != ',' )
429         {
430             if( fieldSize > 3 )
431             {
432                 return LMN_STATUS_ERROR;
433             }
434         }
435         for( j = 0; j < fieldSize; j++, i++ )
436         {
437             NmeaGpsData.NmeaSatelliteTracked[j] = rxBuffer[i];
438         }
439         // NmeaHorizontalDilution
440         fieldSize = 0;
441         while( rxBuffer[i + fieldSize++] != ',' )
442         {
443             if( fieldSize > 6 )
444             {
445                 return LMN_STATUS_ERROR;
446             }
447         }
448         for( j = 0; j < fieldSize; j++, i++ )
449         {
450             NmeaGpsData.NmeaHorizontalDilution[j] = rxBuffer[i];
451         }
452         // NmeaAltitude
453         fieldSize = 0;
454         while( rxBuffer[i + fieldSize++] != ',' )
455         {
456             if( fieldSize > 8 )
457             {
458                 return LMN_STATUS_ERROR;
459             }
460         }
461         for( j = 0; j < fieldSize; j++, i++ )
462         {
463             NmeaGpsData.NmeaAltitude[j] = rxBuffer[i];
464         }
465         // NmeaAltitudeUnit
466         fieldSize = 0;
467         while( rxBuffer[i + fieldSize++] != ',' )
468         {
469             if( fieldSize > 2 )
470             {
471                 return LMN_STATUS_ERROR;
472             }
473         }
474         for( j = 0; j < fieldSize; j++, i++ )
475         {
476             NmeaGpsData.NmeaAltitudeUnit[j] = rxBuffer[i];
477         }
478         // NmeaHeightGeoid
479         fieldSize = 0;
480         while( rxBuffer[i + fieldSize++] != ',' )
481         {
482             if( fieldSize > 8 )
483             {
484                 return LMN_STATUS_ERROR;
485             }
486         }
487         for( j = 0; j < fieldSize; j++, i++ )
488         {
489             NmeaGpsData.NmeaHeightGeoid[j] = rxBuffer[i];
490         }
491         // NmeaHeightGeoidUnit
492         fieldSize = 0;
493         while( rxBuffer[i + fieldSize++] != ',' )
494         {
495             if( fieldSize > 2 )
496             {
497                 return LMN_STATUS_ERROR;
498             }
499         }
500         for( j = 0; j < fieldSize; j++, i++ )
501         {
502             NmeaGpsData.NmeaHeightGeoidUnit[j] = rxBuffer[i];
503         }
504 
505         GpsFormatGpsData( );
506         return LMN_STATUS_OK;
507     }
508     else if ( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPRMC, 5 ) == 0 )
509     {
510         // NmeaUtcTime
511         fieldSize = 0;
512         while( rxBuffer[i + fieldSize++] != ',' )
513         {
514             if( fieldSize > 11 )
515             {
516                 return LMN_STATUS_ERROR;
517             }
518         }
519         for( j = 0; j < fieldSize; j++, i++ )
520         {
521             NmeaGpsData.NmeaUtcTime[j] = rxBuffer[i];
522         }
523         // NmeaDataStatus
524         fieldSize = 0;
525         while( rxBuffer[i + fieldSize++] != ',' )
526         {
527             if( fieldSize > 2 )
528             {
529                 return LMN_STATUS_ERROR;
530             }
531         }
532         for( j = 0; j < fieldSize; j++, i++ )
533         {
534             NmeaGpsData.NmeaDataStatus[j] = rxBuffer[i];
535         }
536         // NmeaLatitude
537         fieldSize = 0;
538         while( rxBuffer[i + fieldSize++] != ',' )
539         {
540             if( fieldSize > 10 )
541             {
542                 return LMN_STATUS_ERROR;
543             }
544         }
545         for( j = 0; j < fieldSize; j++, i++ )
546         {
547             NmeaGpsData.NmeaLatitude[j] = rxBuffer[i];
548         }
549         // NmeaLatitudePole
550         fieldSize = 0;
551         while( rxBuffer[i + fieldSize++] != ',' )
552         {
553             if( fieldSize > 2 )
554             {
555                 return LMN_STATUS_ERROR;
556             }
557         }
558         for( j = 0; j < fieldSize; j++, i++ )
559         {
560             NmeaGpsData.NmeaLatitudePole[j] = rxBuffer[i];
561         }
562         // NmeaLongitude
563         fieldSize = 0;
564         while( rxBuffer[i + fieldSize++] != ',' )
565         {
566             if( fieldSize > 11 )
567             {
568                 return LMN_STATUS_ERROR;
569             }
570         }
571         for( j = 0; j < fieldSize; j++, i++ )
572         {
573             NmeaGpsData.NmeaLongitude[j] = rxBuffer[i];
574         }
575         // NmeaLongitudePole
576         fieldSize = 0;
577         while( rxBuffer[i + fieldSize++] != ',' )
578         {
579             if( fieldSize > 2 )
580             {
581                 return LMN_STATUS_ERROR;
582             }
583         }
584         for( j = 0; j < fieldSize; j++, i++ )
585         {
586             NmeaGpsData.NmeaLongitudePole[j] = rxBuffer[i];
587         }
588         // NmeaSpeed
589         fieldSize = 0;
590         while( rxBuffer[i + fieldSize++] != ',' )
591         {
592             if( fieldSize > 8 )
593             {
594                 return LMN_STATUS_ERROR;
595             }
596         }
597         for( j = 0; j < fieldSize; j++, i++ )
598         {
599             NmeaGpsData.NmeaSpeed[j] = rxBuffer[i];
600         }
601         // NmeaDetectionAngle
602         fieldSize = 0;
603         while( rxBuffer[i + fieldSize++] != ',' )
604         {
605             if( fieldSize > 8 )
606             {
607                 return LMN_STATUS_ERROR;
608             }
609         }
610         for( j = 0; j < fieldSize; j++, i++ )
611         {
612             NmeaGpsData.NmeaDetectionAngle[j] = rxBuffer[i];
613         }
614         // NmeaDate
615         fieldSize = 0;
616         while( rxBuffer[i + fieldSize++] != ',' )
617         {
618             if( fieldSize > 8 )
619             {
620                 return LMN_STATUS_ERROR;
621             }
622         }
623         for( j = 0; j < fieldSize; j++, i++ )
624         {
625             NmeaGpsData.NmeaDate[j] = rxBuffer[i];
626         }
627 
628         GpsFormatGpsData( );
629         return LMN_STATUS_OK;
630     }
631     else
632     {
633         return LMN_STATUS_ERROR;
634     }
635 }
636 
GpsFormatGpsData(void)637 void GpsFormatGpsData( void )
638 {
639     if( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPGGA, 5 ) == 0 )
640     {
641         HasFix = ( NmeaGpsData.NmeaFixQuality[0] > 0x30 ) ? true : false;
642     }
643     else if ( strncmp( ( const char* )NmeaGpsData.NmeaDataType, ( const char* )NmeaDataTypeGPRMC, 5 ) == 0 )
644     {
645         HasFix = ( NmeaGpsData.NmeaDataStatus[0] == 0x41 ) ? true : false;
646     }
647     GpsConvertPositionFromStringToNumerical( );
648     GpsConvertPositionIntoBinary( );
649 }
650 
GpsResetPosition(void)651 void GpsResetPosition( void )
652 {
653     Altitude = ( int16_t )0xFFFF;
654     Latitude = 0;
655     Longitude = 0;
656     LatitudeBinary = 0;
657     LongitudeBinary = 0;
658 }
659