I am currently working on a near space balloon launch, using Arduino as my onboard computer. I have the Ultimate GPS Logger Shield, and I've successfully been able to print parsed GPS data over the serial connection, both with the adafruit library and the tinygps library. However, I am having much difficulty printing the parsed data to the SD card. I'm using the SD.h and SPI.h libraries. The example code which Adafruit provides in the library for the sd card does indeed print information to files, but the .txt files on the sd card are filled with unparsed, and sometimes incomplete, data looking like this:
$GPRMC,023910.780,V,,,,,0.73,277.14,190314,,,N*46
and this:
$GPGGA,023959.000,####.####,N,#####.####,W,1,5,4.37,146.1,M,-19.4,M,,*6E (lat, long omitted for personal security)
I understand how NMEA is read and all of that, however I want the program to print parsed data to the sd card.
Thank you for your help
Bonus Question: I also want to take this gps data and turn the individual numbers of latitude and longitude (including the decimal) into an array, so that I can read each significant digit individually. The purpose of my doing this is to take the array and read each number and convert that into a DTMF tone (using a TP5088 chip). The DTMF tones will be transmitted over GMRS radio and interpreted by a DTMF decoder and read as the lat and long coordinates so I can track the balloon's flight. I know that in the tinygps library the coordinates are stored as float data, but I'm unsure of the Adafruit gps library, and I have no idea how to convert either to an array, or manipulate it in any fashion.
Thanks again.
Ultimate GPS Shield SD Logging - SOLVED
Moderators: adafruit_support_bill, adafruit
Please be positive and constructive with your questions and comments.
- dr_unscrupulous
- Posts: 7
- Joined: Tue Mar 18, 2014 10:34 pm
Ultimate GPS Shield SD Logging - SOLVED
Last edited by dr_unscrupulous on Fri Apr 04, 2014 9:32 pm, edited 1 time in total.
- adafruit_support_mike
- Posts: 67485
- Joined: Thu Feb 11, 2010 2:51 pm
Re: Ultimate GPS Shield SD Logging
Have you tried copying the same parsed output that's sent out on the Serial connection to the SD card? That would at least give you some verification that the information is the same.
WRT isolating the latitude and longitude values, that's a basic pattern matching problem. Check the sentence ID strings for the ones that contain lat. and long. data, then step through the string counting commas until you get the fields you need.
Post your code and we'll see if we can find any room for suggestions.
WRT isolating the latitude and longitude values, that's a basic pattern matching problem. Check the sentence ID strings for the ones that contain lat. and long. data, then step through the string counting commas until you get the fields you need.
Post your code and we'll see if we can find any room for suggestions.
- dr_unscrupulous
- Posts: 7
- Joined: Tue Mar 18, 2014 10:34 pm
Re: Ultimate GPS Shield SD Logging
Sorry for the VERY late reply. With you're suggestions, I've managed to fix the issues I have writing to the SD card. Now I'm working on separating all of the location data into an array of the individual numbers called "latArray" and "lonArray". I've been able to store the individual numbers into variables, but it won't let me put the variables into the array. I want to create a "for" loop which will call the individual numbers from the array and then act accordingly. I viewed the outputs of the "for" loop on the Serial window, but all it's printing is 000000 when it should be the numbers in the array. Here is my code, I've only attempted the array for the latitude data. The rounding you see near the bottom of the loop is for attaining the significant digits of the float. I know this question is unrelated to the original thread, and if you want me to create a new thread for this question I can.
Code: Select all
#include <Adafruit_GPS.h>
#include <SPI.h>
#include <SD.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(8, 7);
Adafruit_GPS GPS(&mySerial);
// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences.
#define GPSECHO false
long latmin;
long lonmin;
byte latbase;
byte lonbase;
int a;
int b;
int c;
int d;
int e;
int f;
int latArray [6] = {a, b, c, d, e, f};
byte u;
byte v;
byte w;
byte x;
byte y;
int z;
int chipSelect = 10;
// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy
int tx = 7;
void setup()
{
pinMode (tx, OUTPUT);
pinMode (chipSelect, OUTPUT);
// connect at 115200 so we can read the GPS fast enough and echo without dropping chars
// also spit it out
Serial.begin(9600);
Serial.println("Near Space Adventures Tracker V1.0");
Serial.println("Initializing Card");
if(!SD.begin(chipSelect))
{
Serial.println("Card Failed");
return;
}
Serial.println ("Card Ready");
// 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
GPS.begin(9600);
// uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
// uncomment this line to turn on only the "minimum recommended" data
//GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
// For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
// the parser doesn't care about other sentences at this time
// Set the update rate
GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate
// For the parsing code to work nicely and have time to sort thru the data, and
// print it out we don't suggest using anything higher than 1 Hz
// Request updates on antenna status, comment out to keep quiet
//GPS.sendCommand(PGCMD_ANTENNA);
// the nice thing about this code is you can have a timer0 interrupt go off
// every 1 millisecond, and read data from the GPS for you. that makes the
// loop code a heck of a lot easier!
useInterrupt(true);
delay(1000);
// Ask for firmware version
mySerial.println(PMTK_Q_RELEASE);
}
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
char c = GPS.read();
// if you want to debug, this is a good time to do it!
#ifdef UDR0
if (GPSECHO)
if (c) UDR0 = c;
// writing direct to UDR0 is much much faster than Serial.print
// but only one character can be written at a time.
#endif
}
void useInterrupt(boolean v) {
if (v) {
// Timer0 is already used for millis() - we'll just interrupt somewhere
// in the middle and call the "Compare A" function above
OCR0A = 0xAF;
TIMSK0 |= _BV(OCIE0A);
usingInterrupt = true;
} else {
// do not call the interrupt function COMPA anymore
TIMSK0 &= ~_BV(OCIE0A);
usingInterrupt = false;
}
}
uint32_t timer = millis();
void loop() // run over and over again
{
// in case you are not using the interrupt above, you'll
// need to 'hand query' the GPS, not suggested :(
if (! usingInterrupt) {
// read data from the GPS in the 'main loop'
char c = GPS.read();
// if you want to debug, this is a good time to do it!
if (GPSECHO)
if (c) Serial.print(c);
}
// if a sentence is received, we can check the checksum, parse it...
if (GPS.newNMEAreceived()) {
// a tricky thing here is if we print the NMEA sentence, or data
// we end up not listening and catching other sentences!
// so be very wary if using OUTPUT_ALLDATA and trytng to print out data
//Serial.println(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
return; // we can fail to parse a sentence in which case we should just wait for another
}
// if millis() or timer wraps around, we'll just reset it
if (timer > millis()) timer = millis();
// approximately every 2 seconds or so, print out the current stats
if (millis() - timer > 2000) {
timer = millis(); // reset the timer
digitalWrite (tx, HIGH);
Serial.print("\nTime: ");
Serial.print(GPS.hour, DEC); Serial.print(':');
Serial.print(GPS.minute, DEC); Serial.print(':');
Serial.print(GPS.seconds, DEC); Serial.print('.');
Serial.println(GPS.milliseconds);
Serial.print("Date: ");
Serial.print(GPS.day, DEC); Serial.print('/');
Serial.print(GPS.month, DEC); Serial.print("/20");
Serial.println(GPS.year, DEC);
Serial.print("Fix: "); Serial.print((int)GPS.fix);
Serial.print(" quality: "); Serial.println((int)GPS.fixquality);
if (GPS.fix) {
File dataFile = SD.open("GPS.csv", FILE_WRITE);
dataFile.print("Latitude,Longitude,Speed,Angle,Altitude,Satellites,");
dataFile.println();
dataFile.print(GPS.latitude, 4);dataFile.print(GPS.lat);
dataFile.print(",");
dataFile.print(GPS.longitude, 4);dataFile.println(GPS.lon);
dataFile.print(",");
dataFile.println(GPS.speed);
dataFile.print(",");
dataFile.println(GPS.angle);
dataFile.print(",");
dataFile.println(GPS.altitude);
dataFile.print(",");
dataFile.println((int)GPS.satellites);
dataFile.print(",");
dataFile.println();
dataFile.close();
Serial.print("Location: ");
Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
latbase = round(((GPS.latitude/100)-.5));
latmin = round(((GPS.latitude * 10000)-.5)-(latbase*1000000));
a = round(((latmin)/100000)-.5);
b = round(((latmin - (100000*a))/10000L)-.5);
c = round(((latmin - (100000*a + 10000L*b))/1000L)-.5);
d = round(((latmin - (100000*a + 10000L*b + 1000L*c))/100)-.5);
e = round(((latmin - (100000*a + 10000L*b + 1000L*c + 100*d))/10)-.5);
f = round((latmin - (100000*a + 10000L*b + 1000L*c + 100*d + 10*e))-.5);
for (int thisChar = 0; thisChar < 6; thisChar++)
{
Serial.print(latArray[thisChar]);
}
Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
lonmin = round(((GPS.longitude * 10000)-.5)-122000000);
u = round(((lonmin)/100000)-.5);
v = round(((lonmin - (100000*u))/10000L)-.5);
w = round(((lonmin - (100000*u + 10000L*v))/1000L)-.5);
x = round(((lonmin - (100000*u + 10000L*v + 1000L*w))/100)-.5);
y = round(((lonmin - (100000*u + 10000L*v + 1000L*w + 100*x))/10)-.5);
z = round((lonmin - (100000*u + 10000L*v + 1000L*w + 100*x + 10*y))-.5);
Serial.print(lonmin);
Serial.print(", ");
Serial.print(u);
Serial.print(v);
Serial.print(".");
Serial.print(w);
Serial.print(x);
Serial.print(y);
Serial.print(z);
//Serial.print("Speed (knots): "); Serial.println(GPS.speed);
//Serial.print("Angle: "); Serial.println(GPS.angle);
//Serial.print("Altitude: "); Serial.println(GPS.altitude);
//Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
}
delay (1000);
digitalWrite (tx, LOW);
}
}
- engineercarl
- Posts: 56
- Joined: Fri Nov 22, 2013 6:47 pm
Re: Ultimate GPS Shield SD Logging
I was able to do this, but only after solving my SRAM crash issues. When you start filling your code with string literals you soon run out of memory. You may wish to check out the last code in this thread http://forums.adafruit.com/viewtopic.php?f=31&t=50509.
- engineercarl
- Posts: 56
- Joined: Fri Nov 22, 2013 6:47 pm
Re: Ultimate GPS Shield SD Logging
After rereading your first question, I had another idea. It seems that you are making the problem more complex by converting the coordinates to "floats". The GPS is sending ASCII characters. The library converts this to a float using the code at the bottom. This is just copied from the github library. About 14 lines down you see a " latitude = atof(p);" which does the conversion to float. If you were to make a new library with code like
you then would have a string representation that you could turn into a digit array very simply. Of course, you will have to change several other things like the xxx.h class defintion to account for the latitude now being a string rather than a float. As a note, I haven't tried this code, so there maybe something wrong with the implementation, but I think you can see the idea.
Code: Select all
p = strchr(p, ',')+1;
i=0;
while (p[i] != ',')
{
latitude[i] = p[i];
i++;
}
latitude[i+1] = 0; // null terminate
Code: Select all
if (strstr(nmea, "$GPGGA")) {
// found GGA
char *p = nmea;
// get time
p = strchr(p, ',')+1;
float timef = atof(p);
uint32_t time = timef;
hour = time / 10000;
minute = (time % 10000) / 100;
seconds = (time % 100);
milliseconds = fmod(timef, 1.0) * 1000;
// parse out latitude
p = strchr(p, ',')+1;
latitude = atof(p);
p = strchr(p, ',')+1;
if (p[0] == 'N') lat = 'N';
else if (p[0] == 'S') lat = 'S';
else if (p[0] == ',') lat = 0;
else return false;
// parse out longitude
p = strchr(p, ',')+1;
longitude = atof(p);
p = strchr(p, ',')+1;
if (p[0] == 'W') lon = 'W';
else if (p[0] == 'E') lon = 'E';
else if (p[0] == ',') lon = 0;
else return false;
p = strchr(p, ',')+1;
fixquality = atoi(p);
p = strchr(p, ',')+1;
satellites = atoi(p);
p = strchr(p, ',')+1;
HDOP = atof(p);
p = strchr(p, ',')+1;
altitude = atof(p);
p = strchr(p, ',')+1;
p = strchr(p, ',')+1;
geoidheight = atof(p);
return true;
}
- dr_unscrupulous
- Posts: 7
- Joined: Tue Mar 18, 2014 10:34 pm
Re: Ultimate GPS Shield SD Logging
So I looked into the code of the Adafruit_GPS library, and I didn't see any code that resembled the code you posted. This is the code for the library
I did find that all of the data in the original code was stored in a "char" (I think) because the code says "char c = GPS.read()", and if I serial print that char, I get all of the NMEA data, like this: $GPGGA,223750.000,45**.****,N,122**.****,W,1,9,1.09,38.4,M,-19.4,M,,*51
. Is there a way to turn that char into an array, and then I could select only the "nth" character, for example just the parts of the array which include the lat and lon data?
Code: Select all
/***********************************
This is the Adafruit GPS library - the ultimate GPS library
for the ultimate GPS module!
Tested and works great with the Adafruit Ultimate GPS module
using MTK33x9 chipset
------> http://www.adafruit.com/products/746
Pick one up today at the Adafruit electronics shop
and help support open source hardware & software! -ada
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above must be included in any redistribution
****************************************/
#ifndef _ADAFRUIT_GPS_H
#define _ADAFRUIT_GPS_H
#if ARDUINO >= 100
#include <SoftwareSerial.h>
#else
#include <NewSoftSerial.h>
#endif
// different commands to set the update rate from once a second (1 Hz) to 10 times a second (10Hz)
#define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F"
#define PMTK_SET_NMEA_UPDATE_5HZ "$PMTK220,200*2C"
#define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F"
#define PMTK_SET_BAUD_57600 "$PMTK251,57600*2C"
#define PMTK_SET_BAUD_9600 "$PMTK251,9600*17"
// turn on only the second sentence (GPRMC)
#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"
// turn on GPRMC and GGA
#define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// turn on ALL THE DATA
#define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// turn off output
#define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// to generate your own sentences, check out the MTK command datasheet and use a checksum calculator
// such as the awesome http://www.hhhh.org/wiml/proj/nmeaxor.html
#define PMTK_LOCUS_STARTLOG "$PMTK185,0*22"
#define PMTK_LOCUS_LOGSTARTED "$PMTK001,185,3*3C"
#define PMTK_LOCUS_QUERY_STATUS "$PMTK183*38"
#define PMTK_LOCUS_ERASE_FLASH "$PMTK184,1*22"
#define LOCUS_OVERLAP 0
#define LOCUS_FULLSTOP 1
// standby command & boot successful message
#define PMTK_STANDBY "$PMTK161,0*28"
#define PMTK_STANDBY_SUCCESS "$PMTK001,161,3*36" // Not needed currently
#define PMTK_AWAKE "$PMTK010,002*2D"
// ask for the release and version
#define PMTK_Q_RELEASE "$PMTK605*31"
// request for updates on antenna status
#define PGCMD_ANTENNA "$PGCMD,33,1*6C"
#define PGCMD_NOANTENNA "$PGCMD,33,0*6D"
// how long to wait when we're looking for a response
#define MAXWAITSENTENCE 5
#if ARDUINO >= 100
#include "Arduino.h"
#if !defined(__AVR_ATmega32U4__)
#include "SoftwareSerial.h"
#endif
#else
#include "WProgram.h"
#include "NewSoftSerial.h"
#endif
class Adafruit_GPS {
public:
void begin(uint16_t baud);
#if ARDUINO >= 100
Adafruit_GPS(SoftwareSerial *ser); // Constructor when using SoftwareSerial
#else
Adafruit_GPS(NewSoftSerial *ser); // Constructor when using NewSoftSerial
#endif
Adafruit_GPS(HardwareSerial *ser); // Constructor when using HardwareSerial
char *lastNMEA(void);
boolean newNMEAreceived();
void common_init(void);
void sendCommand(char *);
void pause(boolean b);
boolean parseNMEA(char *response);
uint8_t parseHex(char c);
char read(void);
boolean parse(char *);
void interruptReads(boolean r);
boolean wakeup(void);
boolean standby(void);
uint8_t hour, minute, seconds, year, month, day;
uint16_t milliseconds;
float latitude, longitude, geoidheight, altitude;
float speed, angle, magvariation, HDOP;
char lat, lon, mag;
boolean fix;
uint8_t fixquality, satellites;
boolean waitForSentence(char *wait, uint8_t max = MAXWAITSENTENCE);
boolean LOCUS_StartLogger(void);
boolean LOCUS_ReadStatus(void);
uint16_t LOCUS_serial, LOCUS_records;
uint8_t LOCUS_type, LOCUS_mode, LOCUS_config, LOCUS_interval, LOCUS_distance, LOCUS_speed, LOCUS_status, LOCUS_percent;
private:
boolean paused;
uint8_t parseResponse(char *response);
#if ARDUINO >= 100
SoftwareSerial *gpsSwSerial;
#else
NewSoftSerial *gpsSwSerial;
#endif
HardwareSerial *gpsHwSerial;
};
#endif
. Is there a way to turn that char into an array, and then I could select only the "nth" character, for example just the parts of the array which include the lat and lon data?
- engineercarl
- Posts: 56
- Joined: Fri Nov 22, 2013 6:47 pm
Re: Ultimate GPS Shield SD Logging
That is the Adafruit_GPS.h file you need modify the Adafruit_GPS.cpp, which is the code library. Stepping back... in C you need a bunch of definitions, function prototypes, and class definitions, which are often included in the *.h files. " .h" stands for headers. They are added to the code by lines like #include <Adafruit_GPS.h> Now in the Arduino world they have decided that files *.cpp are libraries, which are code that you can call in several different projects. So, there are two files you have to modify to make this work, both the *.h file, the definitions, and the *.cpp, the library code.
The statement "char c = GPS.read()" in the ISR (interrupt service routine) is just storing characters. The reason you were able to print it is that it is just a pointer to a character string. You need to wait until it is complete. The GPS.read method sets a flag when a sentence is ready. Then you could parse it to get the data you need.
The problem with calling the nth character is that NMEA fields are not fixed length. They are comma delimited. That is why I gave you the code I did. It looks for commas and then copies characters until the next comma. You need to keep track of where you are in the sentence to be sure you get the correct number.
The statement "char c = GPS.read()" in the ISR (interrupt service routine) is just storing characters. The reason you were able to print it is that it is just a pointer to a character string. You need to wait until it is complete. The GPS.read method sets a flag when a sentence is ready. Then you could parse it to get the data you need.
The problem with calling the nth character is that NMEA fields are not fixed length. They are comma delimited. That is why I gave you the code I did. It looks for commas and then copies characters until the next comma. You need to keep track of where you are in the sentence to be sure you get the correct number.
- dr_unscrupulous
- Posts: 7
- Joined: Tue Mar 18, 2014 10:34 pm
Re: Ultimate GPS Shield SD Logging
I have solved the issue by defining the numbers in the array every time it goes through the loop. Thanks for your help. Although, after fixing this problem, I've stumbled upon another one (go figure).I'll just post this new issue in a new thread.
Please be positive and constructive with your questions and comments.