forked from SlashDevin/NeoGPS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NMEASDlog.ino
433 lines (350 loc) · 13.4 KB
/
NMEASDlog.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
#include <Arduino.h>
#include <NMEAGPS.h>
//======================================================================
// Program: NMEASDlog.ino
//
// Description: This program is an interrupt-driven GPS logger.
// It uses the alternative serial port libraries NeoHWSerial,
// NeoSWSerial, or NeoICSerial.
//
// Prerequisites:
// 1) You have completed the requirements for NMEA_isr.ino
// 2) You have connected an SPI SD card and verified it is working
// with other SD utilities.
// 3) For logging faster than the default 1Hz rate, you have
// identified the required commands for your GPS device.
//
// 'Serial' is for debug output to the Serial Monitor window.
//
// License:
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
//
//======================================================================
#include <GPSport.h>
//#include <Streamers.h>
//----------------------------------------------------------------
// Check configuration
#if !defined( GPS_FIX_TIME ) || !defined( GPS_FIX_LOCATION )
#error You must define TIME and LOCATION in GPSfix_cfg.h
#endif
#if !defined( NMEAGPS_PARSE_RMC )
#error You must define NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h!
#endif
#ifndef NMEAGPS_INTERRUPT_PROCESSING
#error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h!
#endif
static const int LED = 13;
static NMEAGPS gps;
//----------------------------------------------------------------
// SD card includes and declarations
#include <SPI.h>
#include <SdFat.h>
SdFat SD;
const int chipSelect = 8;
//----------------------------------------------------------------
// For testing, it may be more convenient to simply print the
// GPS fix fields to the Serial Monitor. Simply uncomment
// this define to skip all SD operations. An SD card module
// does not have to be connected.
#define SIMULATE_SD
#ifdef SIMULATE_SD
auto &logfile = DEBUG_PORT;
#else
File logfile;
#endif
//----------------------------------------------------------------
// Utility to print a long integer like it's a float
// with 9 significant digits.
void printL( Print & outs, int32_t degE7 )
{
// Extract and print negative sign
if (degE7 < 0) {
degE7 = -degE7;
outs.print( '-' );
}
// Whole degrees
int32_t deg = degE7 / 10000000L;
outs.print( deg );
outs.print( '.' );
// Get fractional degrees
degE7 -= deg*10000000L;
// Print leading zeroes, if needed
if (degE7 < 10L)
outs.print( F("000000") );
else if (degE7 < 100L)
outs.print( F("00000") );
else if (degE7 < 1000L)
outs.print( F("0000") );
else if (degE7 < 10000L)
outs.print( F("000") );
else if (degE7 < 100000L)
outs.print( F("00") );
else if (degE7 < 1000000L)
outs.print( F("0") );
// Print fractional degrees
outs.print( degE7 );
}
//----------------------------------------------------------------
// Because the SD write can take a long time, GPSisr will store
// parsed data in the NMEAGPS 'buffer' array until GPSloop can get to it.
//
// The number of elements you should have in the array depends on
// two things: the speed of your SD card, and the update rate you
// have chosen.
//
// For an update rate of 10Hz (100ms period), two fixes are probably
// enough. Most cards take ~100ms to complete a write of 512 bytes.
// With FIX_MAX=2, two complete fixes can be stored, which were
// received in 200ms. A third fix can be started, giving a little
// more time before an overrun occurs, a total of about 250ms.
//
// If your card is slower or your update rate is faster, you should
// first build and run this program to determine the speed of your
// card. The time it takes to log one record is printed to the log
// file.
//
// After the program has run for a minute or two, remove the
// card and examine the loggingTimes. You may see that an interval
// was skipped, and you will also see an OVERRUN message on the
// DEBUG_PORT (usually Serial).
//
// You should calculate a new FIX_MAX from the maximum loggingTime
// you see in the log file:
//
// FIX_MAX = (max loggingTime)/(update period) + 1;
//
// For example, if the max loggingTime is 160ms, and the update period is
// 100ms (10Hz), then FIX_MAX = 160/100 + 1 = 2.
//
// Change the FIX_MAX value, build and run the program again. The
// SD log file should now contain all records, and no OVERRUN
// messages should have been printed on the DEBUG_PORT.
//
// If you do see an OVERRUN message, examine the loggingTime to see
// if it exceeded the maximum value from the previous build, and
// increase FIX_MAX.
//
// If you are also printing data to a Serial device, you could be
// printing too much information. In general, you must print less than
// (baudrate/10) characters per second. For example, if your baudrate
// is 9600, you must print less than 960 characters per second. And if
// the update rate is 10Hz, you must print no more than 96 characters
// per update.
//
// There are also restrictions on how much you should print to Serial in one
// section of code. If you print more than 64 characters (the output buffer
// size), then some prints will block until all characters can be stored in the
// output buffer.
//
// For example, if you try to print 80 characters, the first 64 characters
// will be immediately stored in the output buffer. However, the last 16
// characters must wait until the output buffer has room for 16 more
// characters. That takes 16 * (10/baudrate) milliseconds. At 9600 baud
// that will take 17ms. The loggingTimes will show no less than 17ms per
// record, and will occasionally include the longer SD write time of ~100ms.
//----------------------------------------------------------------
static void GPSloop()
{
if (gps.available()) {
gps_fix fix = gps.read();
// Log the fix information if we have a location and time
if (fix.valid.location && fix.valid.time) {
static uint16_t lastLoggingTime = 0;
uint16_t startLoggingTime = millis();
// If you like the CSV format implemented in Streamers.h,
// you could replace all these prints with
// trace_all( logFile, fix ); // uncomment include Streamers.h
printL( logfile, fix.latitudeL() );
logfile.print( ',' );
printL( logfile, fix.longitudeL() );
logfile.print(',');
if (fix.dateTime.hours < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.hours);
logfile.print( ':' );
if (fix.dateTime.minutes < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.minutes);
logfile.print( ':' );
if (fix.dateTime.seconds < 10)
logfile.print( '0' );
logfile.print(fix.dateTime.seconds);
logfile.print( '.' );
if (fix.dateTime_cs < 10)
logfile.print( '0' ); // leading zero for .05, for example
logfile.print(fix.dateTime_cs);
logfile.print(',');
logfile.print( lastLoggingTime ); // write how long the previous logging took
logfile.println();
// flush() is used to empty the contents of the SD buffer to the SD.
// If you don't call flush, the data will fill up the SdFat buffer
// of 512bytes and flush itself automatically.
//
// To avoid losing data or corrupting the SD card file system, you must
// call flush() at least once (or close()) before powering down or pulling
// the SD card.
//
// It is *strongly* recommended that you use some external event
// to close the file. For example, staying within 50m of the moving
// average location for 1 minute, or using a switch to start and stop
// logging. It would also be good to provide a visual indication
// that it is safe to power down and/or remove the card, perhaps via
// the LED.
//
// To reduce the amount of data that may be lost by an abnormal shut down,
// you can call flush() periodically.
//
// Depending on the amount of data you are printing, you can save
// *a lot* of CPU time by not flushing too frequently. BTW, flushing
// every time at 5Hz is too frequent.
// This shows how to flush once a second.
static uint16_t lastFlushTime = 0;
if (startLoggingTime - lastFlushTime > 1000) {
lastFlushTime = startLoggingTime; // close enough
logfile.flush();
}
#ifdef SIMULATE_SD
// Simulate the delay of writing to an SD card. These times are
// very long. This is intended to show (and test) the overrun detection.
//
// On a 1Hz GPS, delaying more than 2 seconds here, or more than
// 2 seconds in two consecutive updates, will cause OVERRUN.
//
// Feel free to try different delays to simulate the actual behavior
// of your SD card.
uint16_t t = random(0,5); // 0..4
t += 3; // 3..7
t = t*t*t*t; // 81..2401ms
delay( t ); // cause an OVERRUN
#endif
// All logging is finished, figure out how long that took.
// This will be written in the *next* record.
lastLoggingTime = (uint16_t) millis() - startLoggingTime;
}
}
} // GPSloop
//----------------------------------------------------------------
void GPSisr( uint8_t c )
{
gps.handle( c );
} // GPSisr
//----------------------------------------------------------------
// This routine waits for GPSisr to provide
// a fix that has a valid location.
//
// The LED is slowly flashed while it's waiting.
static void waitForFix()
{
DEBUG_PORT.print( F("Waiting for GPS fix...") );
uint16_t lastToggle = millis();
for (;;) {
if (gps.available()) {
if (gps.read().valid.location)
break; // Got it!
}
// Slowly flash the LED until we get a fix
if ((uint16_t) millis() - lastToggle > 500) {
lastToggle += 500;
digitalWrite( LED, !digitalRead(LED) );
DEBUG_PORT.write( '.' );
}
}
DEBUG_PORT.println();
digitalWrite( LED, LOW );
gps.overrun( false ); // we had to wait a while...
} // waitForFix
//----------------------------------------------------------------
void setup()
{
DEBUG_PORT.begin(9600);
while (!DEBUG_PORT)
; // wait for serial port to connect.
DEBUG_PORT.println( F("NMEASDlog.ino started!") );
DEBUG_PORT.print( F("fix size = ") );
DEBUG_PORT.println( sizeof(gps_fix) );
DEBUG_PORT.print( NMEAGPS_FIX_MAX );
DEBUG_PORT.println( F(" GPS updates can be buffered.") );
if (gps.merging != NMEAGPS::EXPLICIT_MERGING)
DEBUG_PORT.println( F("Warning: EXPLICIT_MERGING should be enabled for best results!") );
gpsPort.attachInterrupt( GPSisr );
gpsPort.begin( 9600 );
// Configure the GPS. These are commands for MTK GPS devices. Other
// brands will have different commands.
gps.send_P( &gpsPort, F("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") ); // RMC only for MTK GPS devices
gps.send_P( &gpsPort, F("PMTK220,100") ); // 10Hz update rate for MTK GPS devices
// Enable the LED for blinking feedback
pinMode( LED, OUTPUT );
initSD();
waitForFix();
} // setup
//----------------------------------------------------------------
void loop()
{
GPSloop();
if (gps.overrun()) {
gps.overrun( false );
DEBUG_PORT.println( F("DATA OVERRUN: fix data lost!") );
}
}
//----------------------------------------------------------------
void initSD()
{
#ifdef SIMULATE_SD
DEBUG_PORT.println( F(" Simulating SD.") );
#else
DEBUG_PORT.println( F("Initializing SD card...") );
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
DEBUG_PORT.println( F(" SD card failed, or not present") );
// don't do anything more:
// Flicker the LED
while (true) {
digitalWrite(LED,HIGH);
delay(75);
digitalWrite(LED,LOW);
delay(75);
}
}
DEBUG_PORT.println( F(" SD card initialized.") );
// Pick a numbered filename, 00 to 99.
char filename[15] = "data_##.txt";
for (uint8_t i=0; i<100; i++) {
filename[5] = '0' + i/10;
filename[6] = '0' + i%10;
if (!SD.exists(filename)) {
// Use this one!
break;
}
}
logfile = SD.open(filename, FILE_WRITE);
if (!logfile) {
DEBUG_PORT.print( F("Couldn't create ") );
DEBUG_PORT.println(filename);
// If the file can't be created for some reason this leaves the LED on
// so I know there is a problem
digitalWrite(LED,HIGH);
while (true) {}
}
DEBUG_PORT.print( F("Writing to ") );
DEBUG_PORT.println(filename);
// GPS Visualizer requires a header to identify the CSV fields.
// If you are saving other data or don't need this, simply remove/change it
logfile.println( F("latitude,longitude,time,loggingTime") );
//trace_header( logfile ); // and uncomment #include Streamers.h
#endif
} // initSD