├── 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 position | Tracking |
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