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