├── Readme.md ├── TinyGPS.cpp ├── TinyGPS.h ├── examples ├── simple_test │ └── simple_test.ino ├── static_test │ └── static_test.ino └── test_with_gps_device │ └── test_with_gps_device.ino └── keywords.txt /Readme.md: -------------------------------------------------------------------------------- 1 | # TinyGPS upgrade for NMEA Data Protocol v3.x and GLONASS 2 | 3 | This update adds support for newer NMEA-capable GPS devices that implement the [v3.x GNSS spec](http://geostar-navi.com/files/docs/geos3/geos_nmea_protocol_v3_0_eng.pdf) as well as devices that support [GLONASS](https://en.wikipedia.org/wiki/GLONASS). 4 | 5 |
Acquiring positionTracking
6 | 7 | #### The following new sentences are now supported: 8 | 9 | 1. NMEA GPS sentence: 10 | * GPS Satellites in view [GPGSV](http://aprs.gids.nl/nmea/#gsv) 11 | 2. GNSS sentences: 12 | * GNRMC (same with GPRMC) 13 | * GNGNS 14 | 3. GLONASS sentences: 15 | * GLGSV 16 | 17 | #### Tracking satellites in view for both GPS and GLONASS constellations. 18 | 19 | This allows for building e.g. an advanced GUI that shows the satellites in view and their respective strength both when searching and when tracking. 20 | The data is accessible via a new method: `uint32_t[] trackedSattelites()` that returns an array of uint32 that (to keep the additional memory footprint at a minimum) encodes the useful data as follows (check [GPGSV](http://aprs.gids.nl/nmea/#gsv) for a comprehensive explanation ): 21 | 22 | * bit 0-7: sattelite ID 23 | * bit 8-14: SNR (dB), max 99dB 24 | * bit 15: this sattelite is used in solution (not implemented yet) 25 | 26 | The uint32 array size is 24, that is, it tracks up to 12 satellites for each constellation, GPS and GLONASS. 12 is the maximum number of satellites in view per constellation at any given point on Earth. 27 | ```c 28 | ... 29 | char buf[32]; 30 | uint32_t* satz = tinygps.trackedSatellites(); 31 | uint8_t sat_count = 0; 32 | for(int i=0;i<24;i++) 33 | { 34 | if(satz[i] != 0) //exclude zero SNR sats 35 | { 36 | sat_count++; 37 | sprintf(buf, "PRN %d: %ddB ", satz[i]>>8, (satz[i]&0xFF)>>1); 38 | Serial.println(buf); 39 | } 40 | } 41 | 42 | ``` 43 | 44 | This code produces an output of this form: ```PRN 21: 31dB PRN 11: 25dB PRN 71: 24dB ...``` where satellite ID and the strength are displayed for each satellite in view. 45 | Additional notes from NMEA protocol v3.0: 46 | 47 | > GPS satellites are identified by their PRN numbers, which range from 1 to 32. 48 | 49 | > The numbers 65-96 are reserved for GLONASS satellites. GLONASS satellites are identified by 64+satellite slot number. The slot numbers are 1 through 24 for the full constellation of 24 satellites, this gives a range of 65 through 88. The numbers 89 through 96 are available if slot numbers above 24 are allocated to on-orbit spares. 50 | 51 | The array is grouped by constellations with GPS first. 52 | 53 | #### GNS mode indicator 54 | 55 | Fix data now includes a field that shows which constellations are used when tracking, Accessible via a new ```char[] constellations()``` method that returns a char array on the following spec: 56 | > Mode Indicator: 57 | A variable length valid character field type with the first two characters currently defined. The first character indicates the use of GPS satellites, the second character indicates the use of GLONASS satellites. If another satellite system is added to the standard, the mode indicator will be extended to three characters, new satellite systems shall always be added on the right, so the order of characters in the Mode Indicator is: GPS, GLONASS, other satellite systems in the future. 58 | The characters shall take one of the following values: 59 | 60 | > * N = No fix 61 | > * A = Autonomous mode 62 | > * D = Differential mode 63 | > * P = Precise mode is used to compute position fix 64 | > * R = Real Time Kinematic 65 | > * F = Float RTK 66 | > * E = Estimated (dead reckoning) mode 67 | > * M = Manual input mode 68 | > * S = Simulator mode. 69 | 70 | For example, a return of ```AA``` when calling ```constellations()``` means that both GPS and GLONASS are used when the device is tracking, ```AN``` means only GPS is used, etc. 71 | 72 | #### Time and date available when the GPS is not tracking 73 | 74 | Now the time and date are also updated even when the device is not tracking since a valid date and time is computed when enough satellites are in view. Use with caution as it may yield false date and time. 75 | 76 | Blogged [here](http://blog.metaflow.net/2015/09/03/a-tinygps-upgrade-adding-nmea-v3-0-and-glonass-support). 77 | -------------------------------------------------------------------------------- /TinyGPS.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPS - a small GPS library for Arduino providing basic NMEA parsing 3 | Based on work by and "distance_to" and "course_to" courtesy of Maarten Lamers. 4 | Suggestion to add satellites(), course_to(), and cardinal(), by Matt Monson. 5 | Precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2013 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #include "TinyGPS.h" 25 | 26 | #define _GPRMC_TERM "GPRMC" 27 | #define _GPGGA_TERM "GPGGA" 28 | #define _GPGSA_TERM "GPGSA" 29 | #define _GNRMC_TERM "GNRMC" 30 | #define _GNGNS_TERM "GNGNS" 31 | #define _GNGSA_TERM "GNGSA" 32 | #define _GPGSV_TERM "GPGSV" 33 | #define _GLGSV_TERM "GLGSV" 34 | 35 | TinyGPS::TinyGPS() 36 | : _time(GPS_INVALID_TIME) 37 | , _date(GPS_INVALID_DATE) 38 | , _latitude(GPS_INVALID_ANGLE) 39 | , _longitude(GPS_INVALID_ANGLE) 40 | , _altitude(GPS_INVALID_ALTITUDE) 41 | , _speed(GPS_INVALID_SPEED) 42 | , _course(GPS_INVALID_ANGLE) 43 | , _hdop(GPS_INVALID_HDOP) 44 | , _numsats(GPS_INVALID_SATELLITES) 45 | , _last_time_fix(GPS_INVALID_FIX_TIME) 46 | , _last_position_fix(GPS_INVALID_FIX_TIME) 47 | , _parity(0) 48 | , _is_checksum_term(false) 49 | , _sentence_type(_GPS_SENTENCE_OTHER) 50 | , _term_number(0) 51 | , _term_offset(0) 52 | , _gps_data_good(false) 53 | #ifndef _GPS_NO_STATS 54 | , _encoded_characters(0) 55 | , _good_sentences(0) 56 | , _failed_checksum(0) 57 | #endif 58 | { 59 | _term[0] = '\0'; 60 | } 61 | 62 | // 63 | // public methods 64 | // 65 | 66 | bool TinyGPS::encode(char c) 67 | { 68 | bool valid_sentence = false; 69 | 70 | #ifndef _GPS_NO_STATS 71 | ++_encoded_characters; 72 | #endif 73 | switch(c) 74 | { 75 | case ',': // term terminators 76 | _parity ^= c; 77 | case '\r': 78 | case '\n': 79 | case '*': 80 | if (_term_offset < sizeof(_term)) 81 | { 82 | _term[_term_offset] = 0; 83 | valid_sentence = term_complete(); 84 | } 85 | ++_term_number; 86 | _term_offset = 0; 87 | _is_checksum_term = c == '*'; 88 | return valid_sentence; 89 | 90 | case '$': // sentence begin 91 | _term_number = _term_offset = 0; 92 | _parity = 0; 93 | _sentence_type = _GPS_SENTENCE_OTHER; 94 | _is_checksum_term = false; 95 | _gps_data_good = false; 96 | return valid_sentence; 97 | } 98 | 99 | // ordinary characters 100 | if (_term_offset < sizeof(_term) - 1) 101 | _term[_term_offset++] = c; 102 | if (!_is_checksum_term) 103 | _parity ^= c; 104 | 105 | return valid_sentence; 106 | } 107 | 108 | #ifndef _GPS_NO_STATS 109 | void TinyGPS::stats(unsigned long *chars, unsigned short *sentences, unsigned short *failed_cs) 110 | { 111 | if (chars) *chars = _encoded_characters; 112 | if (sentences) *sentences = _good_sentences; 113 | if (failed_cs) *failed_cs = _failed_checksum; 114 | } 115 | #endif 116 | 117 | // 118 | // internal utilities 119 | // 120 | int TinyGPS::from_hex(char a) 121 | { 122 | if (a >= 'A' && a <= 'F') 123 | return a - 'A' + 10; 124 | else if (a >= 'a' && a <= 'f') 125 | return a - 'a' + 10; 126 | else 127 | return a - '0'; 128 | } 129 | 130 | unsigned long TinyGPS::parse_decimal() 131 | { 132 | char *p = _term; 133 | bool isneg = *p == '-'; 134 | if (isneg) ++p; 135 | unsigned long ret = 100UL * gpsatol(p); 136 | while (gpsisdigit(*p)) ++p; 137 | if (*p == '.') 138 | { 139 | if (gpsisdigit(p[1])) 140 | { 141 | ret += 10 * (p[1] - '0'); 142 | if (gpsisdigit(p[2])) 143 | ret += p[2] - '0'; 144 | } 145 | } 146 | return isneg ? -ret : ret; 147 | } 148 | 149 | // Parse a string in the form ddmm.mmmmmmm... 150 | unsigned long TinyGPS::parse_degrees() 151 | { 152 | char *p; 153 | unsigned long left_of_decimal = gpsatol(_term); 154 | unsigned long hundred1000ths_of_minute = (left_of_decimal % 100UL) * 100000UL; 155 | for (p=_term; gpsisdigit(*p); ++p); 156 | if (*p == '.') 157 | { 158 | unsigned long mult = 10000; 159 | while (gpsisdigit(*++p)) 160 | { 161 | hundred1000ths_of_minute += mult * (*p - '0'); 162 | mult /= 10; 163 | } 164 | } 165 | return (left_of_decimal / 100) * 1000000 + (hundred1000ths_of_minute + 3) / 6; 166 | } 167 | 168 | #define COMBINE(sentence_type, term_number) (((unsigned)(sentence_type) << 5) | term_number) 169 | 170 | // Processes a just-completed term 171 | // Returns true if new sentence has just passed checksum test and is validated 172 | bool TinyGPS::term_complete() 173 | { 174 | if (_is_checksum_term) 175 | { 176 | byte checksum = 16 * from_hex(_term[0]) + from_hex(_term[1]); 177 | if (checksum == _parity) 178 | { 179 | if(_sentence_type == _GPS_SENTENCE_GPRMC) //set the time and date even if not tracking 180 | { 181 | _time = _new_time; 182 | _date = _new_date; 183 | } 184 | if (_gps_data_good) 185 | { 186 | #ifndef _GPS_NO_STATS 187 | ++_good_sentences; 188 | #endif 189 | _last_time_fix = _new_time_fix; 190 | _last_position_fix = _new_position_fix; 191 | 192 | switch(_sentence_type) 193 | { 194 | case _GPS_SENTENCE_GPRMC: 195 | _time = _new_time; 196 | _date = _new_date; 197 | _latitude = _new_latitude; 198 | _longitude = _new_longitude; 199 | _speed = _new_speed; 200 | _course = _new_course; 201 | break; 202 | case _GPS_SENTENCE_GPGGA: 203 | _altitude = _new_altitude; 204 | _time = _new_time; 205 | _latitude = _new_latitude; 206 | _longitude = _new_longitude; 207 | _numsats = _new_numsats; 208 | _hdop = _new_hdop; 209 | break; 210 | } 211 | 212 | return true; 213 | } 214 | } 215 | 216 | #ifndef _GPS_NO_STATS 217 | else 218 | ++_failed_checksum; 219 | #endif 220 | return false; 221 | } 222 | 223 | // the first term determines the sentence type 224 | if (_term_number == 0) 225 | { 226 | if (!gpsstrcmp(_term, _GPRMC_TERM) || !gpsstrcmp(_term, _GNRMC_TERM)) 227 | _sentence_type = _GPS_SENTENCE_GPRMC; 228 | else if (!gpsstrcmp(_term, _GPGGA_TERM)) 229 | _sentence_type = _GPS_SENTENCE_GPGGA; 230 | else if (!gpsstrcmp(_term, _GNGNS_TERM)) 231 | _sentence_type = _GPS_SENTENCE_GNGNS; 232 | else if (!gpsstrcmp(_term, _GNGSA_TERM) || !gpsstrcmp(_term, _GPGSA_TERM)) 233 | _sentence_type = _GPS_SENTENCE_GNGSA; 234 | else if (!gpsstrcmp(_term, _GPGSV_TERM)) 235 | _sentence_type = _GPS_SENTENCE_GPGSV; 236 | else if (!gpsstrcmp(_term, _GLGSV_TERM)) 237 | _sentence_type = _GPS_SENTENCE_GLGSV; 238 | else 239 | _sentence_type = _GPS_SENTENCE_OTHER; 240 | return false; 241 | } 242 | 243 | if (_sentence_type != _GPS_SENTENCE_OTHER && _term[0]) 244 | switch(COMBINE(_sentence_type, _term_number)) 245 | { 246 | case COMBINE(_GPS_SENTENCE_GPRMC, 1): // Time in both sentences 247 | case COMBINE(_GPS_SENTENCE_GPGGA, 1): 248 | case COMBINE(_GPS_SENTENCE_GNGNS, 1): 249 | _new_time = parse_decimal(); 250 | _new_time_fix = millis(); 251 | break; 252 | case COMBINE(_GPS_SENTENCE_GPRMC, 2): // GPRMC validity 253 | _gps_data_good = _term[0] == 'A'; 254 | break; 255 | case COMBINE(_GPS_SENTENCE_GPRMC, 3): // Latitude 256 | case COMBINE(_GPS_SENTENCE_GPGGA, 2): 257 | case COMBINE(_GPS_SENTENCE_GNGNS, 2): 258 | _new_latitude = parse_degrees(); 259 | _new_position_fix = millis(); 260 | break; 261 | case COMBINE(_GPS_SENTENCE_GPRMC, 4): // N/S 262 | case COMBINE(_GPS_SENTENCE_GPGGA, 3): 263 | case COMBINE(_GPS_SENTENCE_GNGNS, 3): 264 | if (_term[0] == 'S') 265 | _new_latitude = -_new_latitude; 266 | break; 267 | case COMBINE(_GPS_SENTENCE_GPRMC, 5): // Longitude 268 | case COMBINE(_GPS_SENTENCE_GPGGA, 4): 269 | case COMBINE(_GPS_SENTENCE_GNGNS, 4): 270 | _new_longitude = parse_degrees(); 271 | break; 272 | case COMBINE(_GPS_SENTENCE_GPRMC, 6): // E/W 273 | case COMBINE(_GPS_SENTENCE_GPGGA, 5): 274 | case COMBINE(_GPS_SENTENCE_GNGNS, 5): 275 | if (_term[0] == 'W') 276 | _new_longitude = -_new_longitude; 277 | break; 278 | case COMBINE(_GPS_SENTENCE_GNGNS, 6): 279 | strncpy(_constellations, _term, 5); 280 | break; 281 | case COMBINE(_GPS_SENTENCE_GPRMC, 7): // Speed (GPRMC) 282 | _new_speed = parse_decimal(); 283 | break; 284 | case COMBINE(_GPS_SENTENCE_GPRMC, 8): // Course (GPRMC) 285 | _new_course = parse_decimal(); 286 | break; 287 | case COMBINE(_GPS_SENTENCE_GPRMC, 9): // Date (GPRMC) 288 | _new_date = gpsatol(_term); 289 | break; 290 | case COMBINE(_GPS_SENTENCE_GPGGA, 6): // Fix data (GPGGA) 291 | _gps_data_good = _term[0] > '0'; 292 | break; 293 | case COMBINE(_GPS_SENTENCE_GPGGA, 7): // Satellites used (GPGGA): GPS only 294 | case COMBINE(_GPS_SENTENCE_GNGNS, 7): // GNGNS counts-in all constellations 295 | _new_numsats = (unsigned char)atoi(_term); 296 | break; 297 | case COMBINE(_GPS_SENTENCE_GPGGA, 8): // HDOP 298 | _new_hdop = parse_decimal(); 299 | break; 300 | case COMBINE(_GPS_SENTENCE_GPGGA, 9): // Altitude (GPGGA) 301 | _new_altitude = parse_decimal(); 302 | break; 303 | case COMBINE(_GPS_SENTENCE_GNGSA, 3): //satellites used in solution: 3 to 15 304 | //_sats_used[ 305 | break; 306 | case COMBINE(_GPS_SENTENCE_GPGSV, 2): //beginning of sequence 307 | case COMBINE(_GPS_SENTENCE_GLGSV, 2): //beginning of sequence 308 | { 309 | uint8_t msgId = atoi(_term)-1; //start from 0 310 | if(msgId == 0) { 311 | //http://geostar-navigation.com/file/geos3/geos_nmea_protocol_v3_0_eng.pdf 312 | if(_sentence_type == _GPS_SENTENCE_GPGSV) { 313 | //reset GPS & WAAS trackedSatellites 314 | for(uint8_t x=0;x<12;x++) 315 | { 316 | tracked_sat_rec[x] = 0; 317 | } 318 | } else { 319 | //reset GLONASS trackedSatellites: range starts with 23 320 | for(uint8_t x=12;x<24;x++) 321 | { 322 | tracked_sat_rec[x] = 0; 323 | } 324 | } 325 | } 326 | _sat_index = msgId*4; //4 sattelites/line 327 | if(_sentence_type == _GPS_SENTENCE_GLGSV) 328 | { 329 | _sat_index = msgId*4 + 12; //Glonass offset by 12 330 | } 331 | break; 332 | } 333 | case COMBINE(_GPS_SENTENCE_GPGSV, 4): //satellite # 334 | case COMBINE(_GPS_SENTENCE_GPGSV, 8): 335 | case COMBINE(_GPS_SENTENCE_GPGSV, 12): 336 | case COMBINE(_GPS_SENTENCE_GPGSV, 16): 337 | case COMBINE(_GPS_SENTENCE_GLGSV, 4): 338 | case COMBINE(_GPS_SENTENCE_GLGSV, 8): 339 | case COMBINE(_GPS_SENTENCE_GLGSV, 12): 340 | case COMBINE(_GPS_SENTENCE_GLGSV, 16): 341 | _tracked_satellites_index = atoi(_term); 342 | break; 343 | case COMBINE(_GPS_SENTENCE_GPGSV, 7): //strength 344 | case COMBINE(_GPS_SENTENCE_GPGSV, 11): 345 | case COMBINE(_GPS_SENTENCE_GPGSV, 15): 346 | case COMBINE(_GPS_SENTENCE_GPGSV, 19): 347 | case COMBINE(_GPS_SENTENCE_GLGSV, 7): //strength 348 | case COMBINE(_GPS_SENTENCE_GLGSV, 11): 349 | case COMBINE(_GPS_SENTENCE_GLGSV, 15): 350 | case COMBINE(_GPS_SENTENCE_GLGSV, 19): 351 | uint8_t stren = (uint8_t)atoi(_term); 352 | if(stren == 0) //remove the record, 0dB strength 353 | { 354 | tracked_sat_rec[_sat_index + (_term_number-7)/4] = 0; 355 | } 356 | else 357 | { 358 | tracked_sat_rec[_sat_index + (_term_number-7)/4] = _tracked_satellites_index<<8 | stren<<1; 359 | } 360 | break; 361 | } 362 | 363 | return false; 364 | } 365 | 366 | long TinyGPS::gpsatol(const char *str) 367 | { 368 | long ret = 0; 369 | while (gpsisdigit(*str)) 370 | ret = 10 * ret + *str++ - '0'; 371 | return ret; 372 | } 373 | 374 | int TinyGPS::gpsstrcmp(const char *str1, const char *str2) 375 | { 376 | while (*str1 && *str1 == *str2) 377 | ++str1, ++str2; 378 | return *str1; 379 | } 380 | 381 | /* static */ 382 | float TinyGPS::distance_between (float lat1, float long1, float lat2, float long2) 383 | { 384 | // returns distance in meters between two positions, both specified 385 | // as signed decimal-degrees latitude and longitude. Uses great-circle 386 | // distance computation for hypothetical sphere of radius 6372795 meters. 387 | // Because Earth is no exact sphere, rounding errors may be up to 0.5%. 388 | // Courtesy of Maarten Lamers 389 | float delta = radians(long1-long2); 390 | float sdlong = sin(delta); 391 | float cdlong = cos(delta); 392 | lat1 = radians(lat1); 393 | lat2 = radians(lat2); 394 | float slat1 = sin(lat1); 395 | float clat1 = cos(lat1); 396 | float slat2 = sin(lat2); 397 | float clat2 = cos(lat2); 398 | delta = (clat1 * slat2) - (slat1 * clat2 * cdlong); 399 | delta = sq(delta); 400 | delta += sq(clat2 * sdlong); 401 | delta = sqrt(delta); 402 | float denom = (slat1 * slat2) + (clat1 * clat2 * cdlong); 403 | delta = atan2(delta, denom); 404 | return delta * 6372795; 405 | } 406 | 407 | float TinyGPS::course_to (float lat1, float long1, float lat2, float long2) 408 | { 409 | // returns course in degrees (North=0, West=270) from position 1 to position 2, 410 | // both specified as signed decimal-degrees latitude and longitude. 411 | // Because Earth is no exact sphere, calculated course may be off by a tiny fraction. 412 | // Courtesy of Maarten Lamers 413 | float dlon = radians(long2-long1); 414 | lat1 = radians(lat1); 415 | lat2 = radians(lat2); 416 | float a1 = sin(dlon) * cos(lat2); 417 | float a2 = sin(lat1) * cos(lat2) * cos(dlon); 418 | a2 = cos(lat1) * sin(lat2) - a2; 419 | a2 = atan2(a1, a2); 420 | if (a2 < 0.0) 421 | { 422 | a2 += TWO_PI; 423 | } 424 | return degrees(a2); 425 | } 426 | 427 | const char *TinyGPS::cardinal (float course) 428 | { 429 | static const char* directions[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; 430 | 431 | int direction = (int)((course + 11.25f) / 22.5f); 432 | return directions[direction % 16]; 433 | } 434 | 435 | // lat/long in MILLIONTHs of a degree and age of fix in milliseconds 436 | // (note: versions 12 and earlier gave this value in 100,000ths of a degree. 437 | void TinyGPS::get_position(long *latitude, long *longitude, unsigned long *fix_age) 438 | { 439 | if (latitude) *latitude = _latitude; 440 | if (longitude) *longitude = _longitude; 441 | if (fix_age) *fix_age = _last_position_fix == GPS_INVALID_FIX_TIME ? 442 | GPS_INVALID_AGE : millis() - _last_position_fix; 443 | } 444 | 445 | // date as ddmmyy, time as hhmmsscc, and age in milliseconds 446 | void TinyGPS::get_datetime(unsigned long *date, unsigned long *time, unsigned long *age) 447 | { 448 | if (date) *date = _date; 449 | if (time) *time = _time; 450 | if (age) *age = _last_time_fix == GPS_INVALID_FIX_TIME ? 451 | GPS_INVALID_AGE : millis() - _last_time_fix; 452 | } 453 | 454 | void TinyGPS::f_get_position(float *latitude, float *longitude, unsigned long *fix_age) 455 | { 456 | long lat, lon; 457 | get_position(&lat, &lon, fix_age); 458 | *latitude = lat == GPS_INVALID_ANGLE ? GPS_INVALID_F_ANGLE : (lat / 1000000.0); 459 | *longitude = lat == GPS_INVALID_ANGLE ? GPS_INVALID_F_ANGLE : (lon / 1000000.0); 460 | } 461 | 462 | void TinyGPS::crack_datetime(int *year, byte *month, byte *day, 463 | byte *hour, byte *minute, byte *second, byte *hundredths, unsigned long *age) 464 | { 465 | unsigned long date, time; 466 | get_datetime(&date, &time, age); 467 | if (year) 468 | { 469 | *year = date % 100; 470 | *year += *year > 80 ? 1900 : 2000; 471 | } 472 | if (month) *month = (date / 100) % 100; 473 | if (day) *day = date / 10000; 474 | if (hour) *hour = time / 1000000; 475 | if (minute) *minute = (time / 10000) % 100; 476 | if (second) *second = (time / 100) % 100; 477 | if (hundredths) *hundredths = time % 100; 478 | } 479 | 480 | float TinyGPS::f_altitude() 481 | { 482 | return _altitude == GPS_INVALID_ALTITUDE ? GPS_INVALID_F_ALTITUDE : _altitude / 100.0; 483 | } 484 | 485 | float TinyGPS::f_course() 486 | { 487 | return _course == GPS_INVALID_ANGLE ? GPS_INVALID_F_ANGLE : _course / 100.0; 488 | } 489 | 490 | float TinyGPS::f_speed_knots() 491 | { 492 | return _speed == GPS_INVALID_SPEED ? GPS_INVALID_F_SPEED : _speed / 100.0; 493 | } 494 | 495 | float TinyGPS::f_speed_mph() 496 | { 497 | float sk = f_speed_knots(); 498 | return sk == GPS_INVALID_F_SPEED ? GPS_INVALID_F_SPEED : _GPS_MPH_PER_KNOT * sk; 499 | } 500 | 501 | float TinyGPS::f_speed_mps() 502 | { 503 | float sk = f_speed_knots(); 504 | return sk == GPS_INVALID_F_SPEED ? GPS_INVALID_F_SPEED : _GPS_MPS_PER_KNOT * sk; 505 | } 506 | 507 | float TinyGPS::f_speed_kmph() 508 | { 509 | float sk = f_speed_knots(); 510 | return sk == GPS_INVALID_F_SPEED ? GPS_INVALID_F_SPEED : _GPS_KMPH_PER_KNOT * sk; 511 | } 512 | 513 | const float TinyGPS::GPS_INVALID_F_ANGLE = 1000.0; 514 | const float TinyGPS::GPS_INVALID_F_ALTITUDE = 1000000.0; 515 | const float TinyGPS::GPS_INVALID_F_SPEED = -1.0; 516 | -------------------------------------------------------------------------------- /TinyGPS.h: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPS - a small GPS library for Arduino providing basic NMEA parsing 3 | Based on work by and "distance_to" and "course_to" courtesy of Maarten Lamers. 4 | Suggestion to add satellites(), course_to(), and cardinal(), by Matt Monson. 5 | Precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2013 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #ifndef TinyGPS_h 25 | #define TinyGPS_h 26 | 27 | #if defined(ARDUINO) && ARDUINO >= 100 28 | #include "Arduino.h" 29 | #else 30 | #include "WProgram.h" 31 | #endif 32 | 33 | #include 34 | 35 | #define _GPS_VERSION 13 // software version of this library 36 | #define _GPS_MPH_PER_KNOT 1.15077945 37 | #define _GPS_MPS_PER_KNOT 0.51444444 38 | #define _GPS_KMPH_PER_KNOT 1.852 39 | #define _GPS_MILES_PER_METER 0.00062137112 40 | #define _GPS_KM_PER_METER 0.001 41 | // #define _GPS_NO_STATS 42 | 43 | class TinyGPS 44 | { 45 | public: 46 | enum { 47 | GPS_INVALID_AGE = 0xFFFFFFFF, GPS_INVALID_ANGLE = 999999999, 48 | GPS_INVALID_ALTITUDE = 999999999, GPS_INVALID_DATE = 0, 49 | GPS_INVALID_TIME = 0xFFFFFFFF, GPS_INVALID_SPEED = 999999999, 50 | GPS_INVALID_FIX_TIME = 0xFFFFFFFF, GPS_INVALID_SATELLITES = 0xFF, 51 | GPS_INVALID_HDOP = 0xFFFFFFFF 52 | }; 53 | 54 | static const float GPS_INVALID_F_ANGLE, GPS_INVALID_F_ALTITUDE, GPS_INVALID_F_SPEED; 55 | 56 | TinyGPS(); 57 | bool encode(char c); // process one character received from GPS 58 | TinyGPS &operator << (char c) {encode(c); return *this;} 59 | 60 | // lat/long in MILLIONTHs of a degree and age of fix in milliseconds 61 | // (note: versions 12 and earlier gave lat/long in 100,000ths of a degree. 62 | void get_position(long *latitude, long *longitude, unsigned long *fix_age = 0); 63 | 64 | // date as ddmmyy, time as hhmmsscc, and age in milliseconds 65 | void get_datetime(unsigned long *date, unsigned long *time, unsigned long *age = 0); 66 | 67 | // signed altitude in centimeters (from GPGGA sentence) 68 | inline long altitude() { return _altitude; } 69 | 70 | // course in last full GPRMC sentence in 100th of a degree 71 | inline unsigned long course() { return _course; } 72 | 73 | // speed in last full GPRMC sentence in 100ths of a knot 74 | inline unsigned long speed() { return _speed; } 75 | 76 | // satellites used in last full GPGGA sentence 77 | inline unsigned short satellites() { return _numsats; } 78 | 79 | // horizontal dilution of precision in 100ths 80 | inline unsigned long hdop() { return _hdop; } 81 | 82 | inline char* constellations() { return _constellations; } 83 | inline uint32_t* trackedSatellites() { return tracked_sat_rec; } 84 | 85 | void f_get_position(float *latitude, float *longitude, unsigned long *fix_age = 0); 86 | void crack_datetime(int *year, byte *month, byte *day, 87 | byte *hour, byte *minute, byte *second, byte *hundredths = 0, unsigned long *fix_age = 0); 88 | float f_altitude(); 89 | float f_course(); 90 | float f_speed_knots(); 91 | float f_speed_mph(); 92 | float f_speed_mps(); 93 | float f_speed_kmph(); 94 | 95 | static int library_version() { return _GPS_VERSION; } 96 | 97 | static float distance_between (float lat1, float long1, float lat2, float long2); 98 | static float course_to (float lat1, float long1, float lat2, float long2); 99 | static const char *cardinal(float course); 100 | 101 | #ifndef _GPS_NO_STATS 102 | void stats(unsigned long *chars, unsigned short *good_sentences, unsigned short *failed_cs); 103 | #endif 104 | 105 | private: 106 | enum {_GPS_SENTENCE_GPGGA, _GPS_SENTENCE_GPRMC, _GPS_SENTENCE_GNGNS, _GPS_SENTENCE_GNGSA, 107 | _GPS_SENTENCE_GPGSV, _GPS_SENTENCE_GLGSV, _GPS_SENTENCE_OTHER}; 108 | 109 | // properties 110 | unsigned long _time, _new_time; 111 | unsigned long _date, _new_date; 112 | long _latitude, _new_latitude; 113 | long _longitude, _new_longitude; 114 | long _altitude, _new_altitude; 115 | unsigned long _speed, _new_speed; 116 | unsigned long _course, _new_course; 117 | unsigned long _hdop, _new_hdop; 118 | unsigned short _numsats, _new_numsats; 119 | 120 | unsigned long _last_time_fix, _new_time_fix; 121 | unsigned long _last_position_fix, _new_position_fix; 122 | 123 | // parsing state variables 124 | byte _parity; 125 | bool _is_checksum_term; 126 | char _term[15]; 127 | byte _sentence_type; 128 | byte _term_number; 129 | byte _term_offset; 130 | bool _gps_data_good; 131 | 132 | struct TrackedSattelites { 133 | uint8_t prn; //"pseudo-random noise" sequences, or Gold codes. GPS sats are listed here http://en.wikipedia.org/wiki/List_of_GPS_satellites 134 | uint8_t strength; //in dB 135 | }; 136 | 137 | char _constellations[5]; 138 | uint8_t _sat_used_count; 139 | 140 | //format: 141 | //bit 0-7: sat ID 142 | //bit 8-14: snr (dB), max 99dB 143 | //bit 15: used in solution (when tracking) 144 | uint32_t tracked_sat_rec[24]; //TODO: externalize array size 145 | int _tracked_satellites_index; 146 | uint8_t _sat_index; 147 | 148 | #ifndef _GPS_NO_STATS 149 | // statistics 150 | unsigned long _encoded_characters; 151 | unsigned short _good_sentences; 152 | unsigned short _failed_checksum; 153 | unsigned short _passed_checksum; 154 | #endif 155 | 156 | // internal utilities 157 | int from_hex(char a); 158 | unsigned long parse_decimal(); 159 | unsigned long parse_degrees(); 160 | bool term_complete(); 161 | bool gpsisdigit(char c) { return c >= '0' && c <= '9'; } 162 | long gpsatol(const char *str); 163 | int gpsstrcmp(const char *str1, const char *str2); 164 | }; 165 | 166 | #if !defined(ARDUINO) 167 | // Arduino 0012 workaround 168 | #undef int 169 | #undef char 170 | #undef long 171 | #undef byte 172 | #undef float 173 | #undef abs 174 | #undef round 175 | #endif 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /examples/simple_test/simple_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /* This sample code demonstrates the normal use of a TinyGPS object. 6 | It requires the use of SoftwareSerial, and assumes that you have a 7 | 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). 8 | */ 9 | 10 | TinyGPS gps; 11 | SoftwareSerial ss(4, 3); 12 | char buf[32]; 13 | 14 | void setup() 15 | { 16 | Serial.begin(115200); 17 | ss.begin(4800); 18 | 19 | Serial.print("Simple TinyGPS library v. "); Serial.println(TinyGPS::library_version()); 20 | Serial.println("by Mikal Hart"); 21 | Serial.println(); 22 | } 23 | 24 | void loop() 25 | { 26 | bool newData = false; 27 | unsigned long chars; 28 | unsigned short sentences, failed; 29 | 30 | // For one second we parse GPS data and report some key values 31 | for (unsigned long start = millis(); millis() - start < 1000;) 32 | { 33 | while (ss.available()) 34 | { 35 | char c = ss.read(); 36 | // Serial.write(c); // uncomment this line if you want to see the GPS data flowing 37 | if (gps.encode(c)) // Did a new valid sentence come in? 38 | newData = true; 39 | } 40 | } 41 | 42 | unsigned long age; 43 | if (newData) 44 | { 45 | float flat, flon; 46 | gps.f_get_position(&flat, &flon, &age); 47 | Serial.print("LAT="); 48 | Serial.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6); 49 | Serial.print(" LON="); 50 | Serial.print(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6); 51 | Serial.print(" SAT="); 52 | Serial.print(gps.satellites() == TinyGPS::GPS_INVALID_SATELLITES ? 0 : gps.satellites()); 53 | Serial.print(" PREC="); 54 | Serial.print(gps.hdop() == TinyGPS::GPS_INVALID_HDOP ? 0 : gps.hdop()); 55 | //GPS mode 56 | Serial.print(" Constellations="); 57 | Serial.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0 : gps.constellations()); 58 | } 59 | 60 | //satellites in view 61 | uint32_t* satz = gps.trackedSatellites(); 62 | uint8_t sat_count = 0; 63 | for(int i=0;i<24;i++) 64 | { 65 | if(satz[i] != 0) //exclude zero SNR sats 66 | { 67 | sat_count++; 68 | byte strength = (satz[i]&0xFF)>>1; 69 | byte prn = satz[i]>>8; 70 | sprintf(buf, "PRN %d: ", prn); 71 | Serial.print(buf); 72 | Serial.print(strength); 73 | Serial.println("dB"); 74 | } 75 | } 76 | 77 | //date time 78 | int year; 79 | uint8_t month, day, hour, minutes, second, hundredths; 80 | gps.crack_datetime(&year, &month, &day, &hour, &minutes, &second, &hundredths, &age); 81 | sprintf(buf,"GPS time: %d/%02d/%02d %02d:%02d:%02d", year, month, day, hour, minutes, second); 82 | Serial.println(buf); 83 | 84 | gps.stats(&chars, &sentences, &failed); 85 | Serial.print(" CHARS="); 86 | Serial.print(chars); 87 | Serial.print(" SENTENCES="); 88 | Serial.print(sentences); 89 | Serial.print(" CSUM ERR="); 90 | Serial.println(failed); 91 | if (chars == 0) 92 | Serial.println("** No characters received from GPS: check wiring **"); 93 | } -------------------------------------------------------------------------------- /examples/static_test/static_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* This sample code demonstrates the basic use of a TinyGPS object. 5 | Typically, you would feed it characters from a serial GPS device, but 6 | this example uses static strings for simplicity. 7 | */ 8 | prog_char str1[] PROGMEM = "$GPRMC,201547.000,A,3014.5527,N,09749.5808,W,0.24,163.05,040109,,*1A"; 9 | prog_char str2[] PROGMEM = "$GPGGA,201548.000,3014.5529,N,09749.5808,W,1,07,1.5,225.6,M,-22.5,M,18.8,0000*78"; 10 | prog_char str3[] PROGMEM = "$GPRMC,201548.000,A,3014.5529,N,09749.5808,W,0.17,53.25,040109,,*2B"; 11 | prog_char str4[] PROGMEM = "$GPGGA,201549.000,3014.5533,N,09749.5812,W,1,07,1.5,223.5,M,-22.5,M,18.8,0000*7C"; 12 | prog_char *teststrs[4] = {str1, str2, str3, str4}; 13 | 14 | static void sendstring(TinyGPS &gps, const PROGMEM char *str); 15 | static void gpsdump(TinyGPS &gps); 16 | static void print_float(float val, float invalid, int len, int prec); 17 | static void print_int(unsigned long val, unsigned long invalid, int len); 18 | static void print_date(TinyGPS &gps); 19 | static void print_str(const char *str, int len); 20 | 21 | void setup() 22 | { 23 | TinyGPS test_gps; 24 | Serial.begin(115200); 25 | 26 | Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version()); 27 | Serial.println("by Mikal Hart"); 28 | Serial.println(); 29 | Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS)); 30 | Serial.println(); 31 | 32 | Serial.println("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Distance Course Card Chars Sentences Checksum"); 33 | Serial.println(" (deg) (deg) Age Age (m) --- from GPS ---- ---- to London ---- RX RX Fail"); 34 | Serial.println("--------------------------------------------------------------------------------------------------------------------------------------"); 35 | gpsdump(test_gps); 36 | for (int i=0; i<4; ++i) 37 | { 38 | sendstring(test_gps, teststrs[i]); 39 | gpsdump(test_gps); 40 | } 41 | } 42 | 43 | void loop() 44 | { 45 | } 46 | 47 | static void sendstring(TinyGPS &gps, const PROGMEM char *str) 48 | { 49 | while (true) 50 | { 51 | char c = pgm_read_byte_near(str++); 52 | if (!c) break; 53 | gps.encode(c); 54 | } 55 | gps.encode('\r'); 56 | gps.encode('\n'); 57 | } 58 | 59 | static void gpsdump(TinyGPS &gps) 60 | { 61 | float flat, flon; 62 | unsigned long age, date, time, chars = 0; 63 | unsigned short sentences = 0, failed = 0; 64 | static const float LONDON_LAT = 51.508131, LONDON_LON = -0.128002; 65 | 66 | print_int(gps.satellites(), TinyGPS::GPS_INVALID_SATELLITES, 5); 67 | print_int(gps.hdop(), TinyGPS::GPS_INVALID_HDOP, 5); 68 | gps.f_get_position(&flat, &flon, &age); 69 | print_float(flat, TinyGPS::GPS_INVALID_F_ANGLE, 9, 5); 70 | print_float(flon, TinyGPS::GPS_INVALID_F_ANGLE, 10, 5); 71 | print_int(age, TinyGPS::GPS_INVALID_AGE, 5); 72 | 73 | print_date(gps); 74 | 75 | print_float(gps.f_altitude(), TinyGPS::GPS_INVALID_F_ALTITUDE, 8, 2); 76 | print_float(gps.f_course(), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2); 77 | print_float(gps.f_speed_kmph(), TinyGPS::GPS_INVALID_F_SPEED, 6, 2); 78 | print_str(gps.f_course() == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(gps.f_course()), 6); 79 | print_int(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0UL : (unsigned long)TinyGPS::distance_between(flat, flon, LONDON_LAT, LONDON_LON) / 1000, 0xFFFFFFFF, 9); 80 | print_float(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : TinyGPS::course_to(flat, flon, 51.508131, -0.128002), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2); 81 | print_str(flat == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(TinyGPS::course_to(flat, flon, LONDON_LAT, LONDON_LON)), 6); 82 | 83 | gps.stats(&chars, &sentences, &failed); 84 | print_int(chars, 0xFFFFFFFF, 6); 85 | print_int(sentences, 0xFFFFFFFF, 10); 86 | print_int(failed, 0xFFFFFFFF, 9); 87 | Serial.println(); 88 | } 89 | 90 | static void print_int(unsigned long val, unsigned long invalid, int len) 91 | { 92 | char sz[32]; 93 | if (val == invalid) 94 | strcpy(sz, "*******"); 95 | else 96 | sprintf(sz, "%ld", val); 97 | sz[len] = 0; 98 | for (int i=strlen(sz); i 0) 101 | sz[len-1] = ' '; 102 | Serial.print(sz); 103 | } 104 | 105 | static void print_float(float val, float invalid, int len, int prec) 106 | { 107 | char sz[32]; 108 | if (val == invalid) 109 | { 110 | strcpy(sz, "*******"); 111 | sz[len] = 0; 112 | if (len > 0) 113 | sz[len-1] = ' '; 114 | for (int i=7; i= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; 124 | for (int i=flen; i 2 | 3 | #include 4 | 5 | /* This sample code demonstrates the normal use of a TinyGPS object. 6 | It requires the use of SoftwareSerial, and assumes that you have a 7 | 4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx). 8 | */ 9 | 10 | TinyGPS gps; 11 | SoftwareSerial ss(4, 3); 12 | 13 | static void smartdelay(unsigned long ms); 14 | static void print_float(float val, float invalid, int len, int prec); 15 | static void print_int(unsigned long val, unsigned long invalid, int len); 16 | static void print_date(TinyGPS &gps); 17 | static void print_str(const char *str, int len); 18 | 19 | void setup() 20 | { 21 | Serial.begin(115200); 22 | 23 | Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version()); 24 | Serial.println("by Mikal Hart"); 25 | Serial.println(); 26 | Serial.println("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Distance Course Card Chars Sentences Checksum"); 27 | Serial.println(" (deg) (deg) Age Age (m) --- from GPS ---- ---- to London ---- RX RX Fail"); 28 | Serial.println("-------------------------------------------------------------------------------------------------------------------------------------"); 29 | 30 | ss.begin(4800); 31 | } 32 | 33 | void loop() 34 | { 35 | float flat, flon; 36 | unsigned long age, date, time, chars = 0; 37 | unsigned short sentences = 0, failed = 0; 38 | static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002; 39 | 40 | print_int(gps.satellites(), TinyGPS::GPS_INVALID_SATELLITES, 5); 41 | print_int(gps.hdop(), TinyGPS::GPS_INVALID_HDOP, 5); 42 | gps.f_get_position(&flat, &flon, &age); 43 | print_float(flat, TinyGPS::GPS_INVALID_F_ANGLE, 10, 6); 44 | print_float(flon, TinyGPS::GPS_INVALID_F_ANGLE, 11, 6); 45 | print_int(age, TinyGPS::GPS_INVALID_AGE, 5); 46 | print_date(gps); 47 | print_float(gps.f_altitude(), TinyGPS::GPS_INVALID_F_ALTITUDE, 7, 2); 48 | print_float(gps.f_course(), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2); 49 | print_float(gps.f_speed_kmph(), TinyGPS::GPS_INVALID_F_SPEED, 6, 2); 50 | print_str(gps.f_course() == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(gps.f_course()), 6); 51 | print_int(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0xFFFFFFFF : (unsigned long)TinyGPS::distance_between(flat, flon, LONDON_LAT, LONDON_LON) / 1000, 0xFFFFFFFF, 9); 52 | print_float(flat == TinyGPS::GPS_INVALID_F_ANGLE ? TinyGPS::GPS_INVALID_F_ANGLE : TinyGPS::course_to(flat, flon, LONDON_LAT, LONDON_LON), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2); 53 | print_str(flat == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(TinyGPS::course_to(flat, flon, LONDON_LAT, LONDON_LON)), 6); 54 | 55 | gps.stats(&chars, &sentences, &failed); 56 | print_int(chars, 0xFFFFFFFF, 6); 57 | print_int(sentences, 0xFFFFFFFF, 10); 58 | print_int(failed, 0xFFFFFFFF, 9); 59 | Serial.println(); 60 | 61 | smartdelay(1000); 62 | } 63 | 64 | static void smartdelay(unsigned long ms) 65 | { 66 | unsigned long start = millis(); 67 | do 68 | { 69 | while (ss.available()) 70 | gps.encode(ss.read()); 71 | } while (millis() - start < ms); 72 | } 73 | 74 | static void print_float(float val, float invalid, int len, int prec) 75 | { 76 | if (val == invalid) 77 | { 78 | while (len-- > 1) 79 | Serial.print('*'); 80 | Serial.print(' '); 81 | } 82 | else 83 | { 84 | Serial.print(val, prec); 85 | int vi = abs((int)val); 86 | int flen = prec + (val < 0.0 ? 2 : 1); // . and - 87 | flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1; 88 | for (int i=flen; i 0) 105 | sz[len-1] = ' '; 106 | Serial.print(sz); 107 | smartdelay(0); 108 | } 109 | 110 | static void print_date(TinyGPS &gps) 111 | { 112 | int year; 113 | byte month, day, hour, minute, second, hundredths; 114 | unsigned long age; 115 | gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age); 116 | if (age == TinyGPS::GPS_INVALID_AGE) 117 | Serial.print("********** ******** "); 118 | else 119 | { 120 | char sz[32]; 121 | sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d ", 122 | month, day, year, hour, minute, second); 123 | Serial.print(sz); 124 | } 125 | print_int(age, TinyGPS::GPS_INVALID_AGE, 5); 126 | smartdelay(0); 127 | } 128 | 129 | static void print_str(const char *str, int len) 130 | { 131 | int slen = strlen(str); 132 | for (int i=0; i