GPS NMEA data logging to SD card. Help needed

I have been working og data logging of thr GPS RMC and GGA.
But I want to try logging all data from the GPS to SD card, is this possible?

I got an Arduino DUE and Arduino Mega2560 and adafruit MicroSD board.
The GPS type Adafruit Ultimate GPS is connected to Serial 1.

  1. Open an output file
  2. Read bytes from the GPS
  3. Write each byte to the file
include <SD.h>
const int chipSelect = 4;
File dataFile;

void setup() {
    SD.begin(chipSelect);
    Serial1.begin(9600);
    dataFile = SD.open("datalog.txt", FILE_WRITE);
}

void loop() {
    while (Serial1.available()) {
        datafile.write(Serial1.read());
    }
}

That was easy code, simple is good hehe.

But If I want to store the GPS data to a buffer or something before I send it to the SD card.
Sow other parts of the sketch can use some information of the GPS NMEA data. Like for screen or bluetooth transmitting to another devices?

NOTE! I think I only whant the GGA,GSA and RMC data.

But If I want to store the GPS data to a buffer or something before I send it to the SD card.

You can do that. The encode() method (of TinyGPS or TinyGPS++) tells you when the sentence is complete.

Never got the TinyGPS++ to work on my GPS unit with hardware Serial1.
Never tested Software serial, but don't want to use that . The Mega 2560 got 3 Hardware serials.

One approach is to copy each character sent from the gps into a char array ( it will need to be about 132 characters long ), and then each time you get a new line from the gps, copy the line of text to the file.

You can then copy those lines of text to your serial monitor on computer or anywhere else you like. You can also check which NMEA string it is, if it isn't one of the ones you want, you can just ignore it.

If you want to extract actual information from the lines of text, then you need to deal with the formatting and parsing of the data lines. This can be done if you have expertise, which you probably don't. Save yourself the trouble and use tinyGPS.

There is an issue with SD card files, whether to open the file and leave it open, or open and close it every time you right to it. Both methods have considerable disadvantages. Opening and closing the file for every single character is ridiculous, opening and closing the file to write a whole line of text is a more reasonable plan.

Got now I am a noob but learning.
Have to start slow but will get the expertise after some years I hope.

For now I use the Adafruit GPS library to save GPS data to SD card, it work fine, but random saving of data sow I adjusted it and it now saves GGA GSA and RMC. But with a delay of about 3 seconds, I don`t mind that for now.

I want to get some specific data from text sines received from Serial1 to Serial3. Sow I will take a look at the formatting and parsing.
I am using the Arduino Mega 2560 but will replace it to the DUE. The DUE can multi task, thats a good thing for me.

I will com back with an update after I have tested some codes I got.

I did something similar, based on a Mega 2560 - some of the information is here:-
http://forum.arduino.cc/index.php?topic=157123.0

The unit is now in regular use, and works just fine.

If you would like a copy of the code [it's too long to include in a message here] send me a PM with your email address.

michinyon:
One approach is to copy each character sent from the gps into a char array ( it will need to be about 132 characters long ), and then each time you get a new line from the gps, copy the line of text to the file.

You can then copy those lines of text to your serial monitor on computer or anywhere else you like. You can also check which NMEA string it is, if it isn't one of the ones you want, you can just ignore it.

If you want to extract actual information from the lines of text, then you need to deal with the formatting and parsing of the data lines. This can be done if you have expertise, which you probably don't. Save yourself the trouble and use tinyGPS.

There is an issue with SD card files, whether to open the file and leave it open, or open and close it every time you right to it. Both methods have considerable disadvantages. Opening and closing the file for every single character is ridiculous, opening and closing the file to write a whole line of text is a more reasonable plan.

SD usage implies a 512Byte buffer. Adafruit's GPS library double-buffers every NMEA string! On an UNO, you can quickly run out of SRAM. Other libraries may be more efficient.

I usually recommend using an external SD-card writer for the serial data stream, as here.

Ray

rgarside:
I did something similar, based on a Mega 2560 - some of the information is here:-
NMEA multiplexer and logger - Project Guidance - Arduino Forum

The unit is now in regular use, and works just fine.

If you would like a copy of the code [it's too long to include in a message here] send me a PM with your email address.

I would like to take you up on your offer. I sent a PM but thought a post might send you a reply notification. I'm using a Mega to multiplex a uBlox GPS and the NMEA output on my fishfinders for making depth maps. I have it multiplexing the two and sending it out the USB but I haven't begun on the code to log this to SD card in NMEA Log format. I also plan to add a barometric pressure, temp and humidity (For Heat Index, I'm in Iowa and that's a relevant thing to know in July and August) sensors to it too but not logging it to the SD card. Since I still have a spare UART left it seems a Bluetooth output wouldn't be a big deal to implement (I already have the hardware) I don't need to do much parsing of NMEA data on the Arduino, that will be done on my Thinkpad Tablet 2 with the ultimate goal (besides the depth mapping part) of entering Lat, Long, depth, water temp, air temp and barometric pressure into my fishing log/database whenever I open a new record.

I'm sure I can figure this all out eventually (I've been working with PIC and PIC assembly for a long time, just not a C programmer) but I hate to re-invent the wheel if I don't have to

Still no reply ...... Must be the aftershave I'm wearing or something

longjohn119:
I hate to re-invent the wheel if I don't have to

I finally wrote a GPS library after using a few of the most popular ones. Like you (and most people), I only needed a few pieces of GPS information, and was disappointed with the size and speed of what was available. They are quite acceptable for most people, but most people also start having trouble as they extend the original example programs. Like mrburnette's understatement,

Other libraries may be more efficient.

:smiley:

So, in an effort to deal with NMEA "peculiarities" (the nicest way I can put it) and the typical embedded constraints, I wrote NeoGPS. It is completely configurable as to which messages and which pieces use RAM, program space and CPU time. It doesn't require float, which makes the program even smaller an faster if all you're doing is logging. There are optional float methods if you are doing calculations (e.g., distance).

If you're interested, the announcement thread is here, and some discussion here on interleaving GPS reading with other tasks, like writing to SD and the other things on your list. Be sure to check out the Performace and RAM descriptions.

Cheers,
/dev

Actually I pared it down and decided to do the temp/humidity/pressure separately because it's just easier to tie the sensor lines to a Nano and to the USB hub a meter away than run a bunch of sensor wires 3 meters to where the NMEA router is located especially since none of that data is logged to the SD card on the multiplexer but goes straight to the main computer. Another project I've been working on is a fishing log and I want to get it to automagically enter GPS position, depth, water temp, air temp and pressure into the log when I open a new record (I'm a hardware hacker at heart so I really put the Hack in hacker when it comes to coding ...LOL)

So basically I need to log two GPS sentences and one depth sentences to the SD card and send everything out the USB to a computer. I still have an extra UART left so I'm going to try having an option to send it to a Bluetooth serial transceiver and log to any device with Bluetooth which is all three of my main devices, a Lenovo Thinkpad Tablet 2 (Hooked to the USB hub, a Nexus 7 original and an Samsung S3 phone.

Basically I've been mapping local waters (Eastern Iowa) for several years and this is an upgrade to an existing system and I have a once in a lifetime chance of mapping a brand new 550 acre lake that's been environmentally engineered to keep out silt, fertilizer and other runoff which plenty of natural and man made structure and spawning beds

This is my blog for the boat and general mapping and this is one I started to document the mapping of the lake. We had a dry winter and early spring so it's not full yet but we have a week's worth of rain coming and in recent years it's been the May rains that are causing the worst flooding, not the usual snowmelt flooding. It could very well be close by the end of the month.

Thanx for the reply, your project sounds like it's just what I was looking for

So basically I need to log two GPS sentences

This will require 2 NMEAGPS variables, which isn't even possible with other libraries. And this will still use less RAM, program space and CPU time.

FYI, each variable is dedicated to a device stream, like this:

NMEAGPS gps1, gps2;

static void log_loc( const gps_fix &fix )
{
  if (fix.valid.location) {
    sd_file << fix.lat << ',' << fix.lon << ','; // integer formats ok?
    Serial  << fix.lat << ',' << fix.lon << ',';
  } else {
    sd_file << F(",,");  // no location fix yet
    Serial  << F(",,");
  }
}

void loop()
{
  while (Serial1.available())
    gps1.decode( Serial1.read() );

  while (Serial2.available())
    gps2.decode( Serial2.read() );

  if (time to log) {
    log_loc( gps1.fix() );
    log_loc( gps2.fix() );
    log other things...
    sd_file << '\n';
    Serial  << '\n';
  }
}

Many details omitted, like setup() and how to detect the quiet time! :slight_smile:

Although it's not as critical when using these HardwareSerial ports, it is convenient that the "quiet times" of the 2 GPS devices will be synchronized. Using SoftwareSerial would definitely be problematic here.

Having a Mega makes this very doable.

Cheers,
/dev

When I say two sentences I mean from a single GPS unit, all my depth mapping software needs either the GGA sentence, the RMC sentence or both so I want to log both sentences and 'throw away the other 3 (

Now I will have a second device sending an SDDPT sentence I need to log in time with the above GPS sentences

I don't really need to parse any info from these sentences but would prefer to log the NMEA sentences themselves as all my depth mapping software prefers the NMEA sentences in NMEA .log format meaning I can just import a NMEA log without any conversion to other formats first. (Basically what it sees in live mapping use) Preferably a log would go

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,47
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W
6A
$SDDPT,3.6,0.0*52

repeating at 1 second intervals

This helps to keep all my data backwards and forward compatible and usable by nearly every mapping and GIS application available.

Basically I need to grab those 3 sentences and log them to the SD card while sending everything else (5 GPS sentences and 3 sounder sentences per second) out the USB (or maybe Bluetooth serial) port to my main computer. The SD card log is mainly a backup but also would allow me to map without a computer onboard because Stuff Happens especially to computers and I hate to waste time, money and fuel for nothing.

When I say two sentences I mean from a single GPS unit

Doh! My misunderstanding. :blush:

So you really are just copying 3 sentences to the USB and onto the SD card. That is a different kettle of fish.

The goal of NeoGPS is to provide the parsed information as quickly as possible, using as little RAM/flash as possible. It's really targeted to 'copters and other real-time navigational apps, or to resource-constrained location-aware apps, like geo-caching. If you were doing Kalman filtering with or without IMU/compass, those calculations would require the parsed fields.

In your case, you only need the bytes between the '$' and the CR/LF. This is really a line-buffering problem, not a parsing problem. You don't need any kind of GPS library.

Here's one way you could do it:

(DO NOT USE THIS CODE! Corrected version by longjohn119 below)

static void check_gps();
static void check_sdd();

void loop()
{
  check_gps();
  check_sdd();
}

//----------------

enum NMEA_state_t { WAITING_FOR_$, RECEIVING };

static NMEA_state_t gps_state, sdd_state;

static char start_of_gps[8];  // temporary place until first 6 chars received
static char start_of_sdd[8];

static char rmc[120] = "$GPRMC";
static char gga[120] = "$GPGGA";
static char dpt[120] = "$SDDPT";

static bool got_rmc;
static bool got_gga;
static bool got_dpt;

static char *gps_line;
static uint8_t gps_count;

static uint32_t last_rx = 0UL; //  to remember when we last received a char

//............

static void check_gps()
{
  bool got_something = false;

  while (Serial1.available()) {
    c = Serial1.Read();
    got_something = true;

    switch (gps_state) {
      case WAITING_FOR_$:
        if (c == '

(DO NOT USE THIS CODE! Corrected version by longjohn119 below)

There might be few things missing... :wink: I'll give it a try later, and edit the post if there's a major problem.

Notice how all sentences are saved until they can be written during the GPS quiet time. This will avoid any possibility of losing GPS chars because the SD and Serial writes take too long.

I imagine that you can see a pattern in the two check routines... there are techniques for reducing the copied sections, but this is perhaps better for your level of coding.

You should probably send a $PUBX configuration command to the GPS during setup() to turn off the other sentence types.

Cheers,
/dev) {
         // Start of sentence!
         gps_count = 0;
         gps_line  = &start_of_gps[0];

*gps_line++ = c;
         gps_count++;

gps_state = RECEIVING;
       }
       break;

case RECEIVING:
       if (c == '\r') {
         *gps_line++ = c;
         gps_count++;

} else if (c == '\n')) {
         *gps_line++ = c;
         gps_count++;
         *gps_line++ = 0; // NUL-terminate

gps_state = WAITING_FOR_$; // get next sentence

} else if (gps_count == 6) {
         // check the sentence type
         *gps_line++ = c;
         gps_count++;

if (strncmp( start_of_gps, rmc, 6 ) == 0) {
           got_rmc  = true;
           gps_line = &rmc[6];
         } else if (strncmp( start_of_rmc, gga, 6 ) == 0)) {
           got_gga  = true;
           gps_line = &gga[6];
         } else
           gps_state = WAITING_FOR_$; // ignore the rest

} else if (gps_count < sizeof(rmc)-3) {
         // save chars if there's room
         *gps_line++ = c;
         gps_count++;
       }
       break;
   }
 }

if (got_something)
   last_rx = millis();

else if (millis() - last_rx > 10UL) {

if (gps_state == WAITING_FOR_$) {

// Quiet time, write the GPS sentences...

if (got_rmc) {
       sd_file->write( rmc );
       Serial->write( rmc );
       got_rmc = false;
     }

if (got_gga) {
       sd_file->write( gga );
       Serial->write( gga );
       got_gga = false;
     }

// ... and the SDDPT sentence, when/if it shows up during the quiet time.

if (got_dpt) {
         sd_file->write( dpt );
         Serial->write( dpt );
         got_dpt = false;
     }
   }
 }
}

//...............

static char   *sdd_line;
static uint8_t sdd_count;

static void check_sdd()
{
 while (Serial2.available()) {
   c = Serial2.Read();

switch (sdd_state) {
     case WAITING_FOR_$:
       if (c == '


(DO NOT USE THIS CODE! Corrected version by **longjohn119** below)

There might be few things missing... ;) I'll give it a try later, and edit the post if there's a major problem.

Notice how all sentences are saved until they can be written during the GPS quiet time. This will avoid any possibility of losing GPS chars because the SD and Serial writes take too long.

I imagine that you can see a pattern in the two check routines... there are techniques for reducing the copied sections, but this is perhaps better for your level of coding.

You should probably send a $PUBX configuration command to the GPS during **setup()** to turn off the other sentence types.

Cheers,
/dev) {
          // Start of sentence!
          sdd_count = 0;
          sdd_line  = &start_of_sdd[0];

          *sdd_line++ = c;
          sdd_count++;

          sdd_state = RECEIVING;
        }
        break;

      case RECEIVING:
        if (c == '\r') {
          *sdd_line++ = c;
          sdd_count++;

        } else if (c == '\n')) {
          // SDDPT complete

          *sdd_line++ = c;
          sdd_count++;
          *sdd_line = 0; // NUL-terminate

          got_dpt = true;

          sdd_state = WAITING_FOR_$;

        } else if (sdd_count == 6) {
          // check the sentence type
          *sdd_line++ = c;
          sdd_count++;

          if (strncmp( start_of_sdd, dpt, 6 ) == 0) {
            sdd_line = &dpt[6];
            got_dpt  = false;
          } else
            sdd_state = WAITING_FOR_$; // ignore the rest

        } else if (sdd_count < sizeof(dpt)-3) {
           // save chars if there's room
          *sdd_line++ = c;
          sdd_count++;
        }
        break;
    }
  }
}

(DO NOT USE THIS CODE! Corrected version by longjohn119 below)

There might be few things missing... :wink: I'll give it a try later, and edit the post if there's a major problem.

Notice how all sentences are saved until they can be written during the GPS quiet time. This will avoid any possibility of losing GPS chars because the SD and Serial writes take too long.

I imagine that you can see a pattern in the two check routines... there are techniques for reducing the copied sections, but this is perhaps better for your level of coding.

You should probably send a $PUBX configuration command to the GPS during setup() to turn off the other sentence types.

Cheers,
/dev

That's what I'm looking for and looks close to code I've been trying to work out and you are correct about losing characters, and that's what has been hanging me up. It looks good for a while and then I get cut off sentences and I figured it was dropping data while writing to the SD card

I was also advised to use the SDFat libraries rather than the SD library included in the IDE which seemed to help but not completely fix my problem. I'll play around with this code this weekend

Actually I have a uBlox module with the EEPROM so I can set it up and it'll hold that setup between sessions, part of the reason I went with it rather than one of the AdaFruit modules which seems to need to be set up every time you power up. Still the 'stock' configuration of the standard 5 sentences does have data that's useful, for instance the satellite information (GSV) is handy for troubleshooting, VTG is nice since I have no other means of easily finding my boat's speed and I figured GLL would be easier to parse for the lat, long data I want to automatically insert into my fishing log/database although I could get by without those (again why I chose the uBlox module with the EEPROM in the first place)

Since you are already experiencing the blocking problem, I moved the SDDPT write into the quiet time as well.

One other thing is worth mentioning: If you were doing sd_file->write( one_char ), that would also slow things down. Saving all the chars in buffers and doing one write per sentence will be much more efficient.

the 'stock' configuration of the standard 5 sentences does have data that's useful

It's probably obvious how to save additional sentences, but it might be less code if you just buffered all the sentences received until the quiet time, then wrote them all out. For debugging, configure the ublox to emit all 5, and that's what gets saved. For a real run, configure the ublox for just the GGA and RMC. You could even add a simple console interface to your sketch that lets the tablet enable/disable those sentences:

while (Serial.available()) {
  char c = Serial.read();

  if (c == '*') {
    // enable extra sentences -- debugging
    Serial1.println( F("$PUBX,40,GLL,0,1,0,0,0,0,0*??\r\n") );
    Serial1.println( F("$PUBX,40,VTG,0,1,0,0,0,0,0*??\r\n") );
    Serial1.println( F("$PUBX,40,GSV,0,1,0,0,0,0,0*??\r\n") );

  } else if (c == '0') {
    // disable extra sentences -- no debugging
    Serial1.println( F("$PUBX,40,GLL,0,0,0,0,0,0,0*??\r\n") );
    Serial1.println( F("$PUBX,40,VTG,0,0,0,0,0,0,0*??\r\n") );
    Serial1.println( F("$PUBX,40,GSV,0,0,0,0,0,0,0*??\r\n") );

  } // else other commands?
}

Cheers,
/dev

"Since you are already experiencing the blocking problem, I moved the SDDPT write into the quiet time as well."

Actually I was going to attempt that myself, I figure that would keep the position and depth in sync better.

Since I have a module with the EEPROM I can save at least 2 (I think it's 4 but I'd have to look it up) different setups and chose between them with a single UBX command/sentence.

If I can get all 5 GPS sentences plus the depth sentence written to the SD card and be ready for the next 'sample' (once per second) that's really not a bad thing, I have some scripts that use that extra information for "sanity checking" data and parsing out bad data, drop outs or just a weak fix (High HDOP) It's a lot easier than culling out the bad data in Dr Depth or Reefmaster essentially step by step .... If I have a file with 2 hours worth of mapping that's 7200 data points to step through. On my Windows machines I take the GPS com port to a virtual com port and split it to two mapping programs and a 3rd program that just logs everything to a file and I use that file for pre-processing. It's a lot easier (and much more flexible) to do data parsing on a 32/64 bit Windows machine than a 16 Mhz 8 bit microcontroller which is why I've avoided it

Your library will definitely come in handy later this year when I get ready to play around with auto-pilot on my trolling motor or an autonomous platform to automatically map along a predetermined path

I finally got a chance to work with this and work through some bugs, some minor some not so much. A couple of instances of closing parenthesis that need to be removed and a couple of instances of Serial1.Read that had to be changed to Serial1.read

The others were a little tougher for instance WAITING_FOR_$ was throwing a error: stray '$' in program which looks like a common error where UTF-8 needs to be converted to ANSI ... but that wasn't it ... Although no mention of it in the Arduino Reference apparantly $ is a reserved charactor and so I change all instances of WAITING_FOR_$ to WAITING_FOR_START which seemed to work

Then I had an error with the -> symbol and again nothing about it in the Arduino Refererence but what I ended up doing is changing things like sd_file->write( rmc ); and Serial->write( rmc ); to sd_file.write( rmc ); and Serial.write( rmc ); ... Again that seemed to work, at least it made the compiler happy but I still have a problem, I'm dropping the comma delimiter after the NMEA word For instance instead of

$GPRMC,051020.00,A,4132.14109,N,09033.36797,W,0.169,,120515,,,A*64
$GPGGA,051020.00,4132.14109,N,09033.36797,W,1,09,1.02,203.3,M,-32.8,M,,68
$SDDPT,25.07,0.00
67

I'm getting

$GPRMC051020.00,A,4132.14109,N,09033.36797,W,0.169,,120515,,,A*64
$GPGGA051020.00,4132.14109,N,09033.36797,W,1,09,1.02,203.3,M,-32.8,M,,68
$SDDPT25.07,0.00
67

And therefore none of my mapping programs can read it, it just is "Waiting for fix"