Reading Serial Data and storing them in different variables

Hi Guys,

I have some difficulties and I'm new to Arduino programming... I want to read a serial data string similar to the NMEA Protocol:
(for example: $GPRMC,154653,V,4428.2011,N,00440.5161,W,000.5,342.8,050407,,,N*7F) without using the tinygps.h library and store the data between the ',' in variables such as float or int to do some further calculations with them.

However, the code from the arduino-gps tutorial is already close :slight_smile: (Arduino Playground - GPS)

But in case of Serial.print the data, I want to store them in my veriables :slight_smile:
Any ideas?

 void loop() {
   digitalWrite(ledPin, HIGH);
   byteGPS=Serial.read();         // Read a byte of the serial port
   if (byteGPS == -1) {           // See if the port is empty yet
     delay(100); 
   } else {
     linea[conta]=byteGPS;        // If there is serial port data, it is put in the buffer
     conta++;                      
     Serial.print(byteGPS, BYTE); 
     if (byteGPS==13){            // If the received byte is = to 13, end of transmission
       digitalWrite(ledPin, LOW); 
       cont=0;
       bien=0;
       for (int i=1;i<7;i++){     // Verifies if the received command starts with $GPR
         if (linea[i]==comandoGPR[i-1]){
           bien++;
         }
       }
       if(bien==6){               // If yes, continue and process the data
         for (int i=0;i<300;i++){
           if (linea[i]==','){    // check for the position of the  "," separator
             indices[cont]=i;
             cont++;
           }
           if (linea[i]=='*'){    // ... and the "*"
             indices[12]=i;
             cont++;
           }
         }
         Serial.println("");      // ... and write to the serial port
         Serial.println("");
         Serial.println("---------------");
         for (int i=0;i<12;i++){
           switch(i){
             case 0 :Serial.print("Time in UTC (HhMmSs): ");break;
             case 1 :Serial.print("Status (A=OK,V=KO): ");break;
...
             case 12 :Serial.print("Checksum: ");break;
           }
           for (int j=indices[i];j<(indices[i+1]-1);j++) // <- I think what I need is in here... but I don't know how to put the data in variables
{
             Serial.print(linea[j+1]); 
           }
           Serial.println("");
         }
         Serial.println("---------------");
       }
       conta=0;                    // Reset the buffer
       for (int i=0;i<300;i++){    //  
         linea[i]=' ';             
       }                 
     }
   }
 }

I haven't used sscanf() for years but won't it parse this string into variables? As long as the format doesn't change.

If not I would chop the string into multiple strings by replacing all the ',' with '\0' then use atoi/atof etc on the resultant strings.


Rob

This should point you in the right direction:

wrt to the input

is the string fixed length?
has it a specific char at the end?
has it the same number of fields every time?

robtillaart:
wrt to the input

is the string fixed length?
has it a specific char at the end?
has it the same number of fields every time?

NMEA strings do not have a fixed length, but they do have a max length.
They are all terminated by a carriage return (and prefixed with a $).
Each string type does have a specific number of fields (though some of those fields may be empty), but there are several different string types that can be received.

Hi, yes, usually NMEA strings do not have the same lenght..

in my case the string has a fixed lenght, and there will be a specific character at the end and the beginning.. The number of fields will be constant!
Thanks in advance :slight_smile:

What is that fixed length?
What is the specific character?
What is the number of fields?

The String will for example look like this:

$GPSDAT,210102,A,50.352424,N,06.119385,E,000.0,000.0,0123.5,02,*

and contains the following

$GPSDAT,HhMmSs,(A/V),XX.YYZZZZ°,(N/S),XX.YYZZZZ°,(E/W),VVV.V,DDD.D,HHHH.H,NN,*

Which is:
Start ->$GPSDAT,Time,Valid/Invalild,Latitude,Nord/South,Longitude,East/West,Speed,Heading,Altitude,No.Satellites,* <- Stop Character

So I guess the number of fields is: 10.
Furhermore I get a new string every 500ms... I want to store the Longitude/Latitude in Float-Variables and the Rest in Int-Variables... To do some more calculation with them.

Simple fixed field parser to get started... Read this code carefully and try to understand before running...

Give it a try...

// test string
char* s = "$GPSDAT,210102,A,50.352424,N,06.119385,E,000.0,000.0,0123.5,02,*";

// BUFFER TO HOLD SERIAL DATA
char buf[80];
int idx = 0;
boolean stringComplete;

// FIELDS
char time[8]; 
char valid;
float lat;
char NS;
float lon;
char EW;
float speed;
float heading;
float altitude;
int sat;

//////////////////////////////////
void setup()
{
  Serial.begin(115200);  // ADJUST 
  Serial.println("start");

  stringComplete = false;
}

void loop()
{
  // test-code 2lines; parses string s if uncommented
//  stringComplete = true;
//  strcpy(buf, s);

  // RECEIVE STRING
  while (stringComplete == false) 
  {
    if (Serial.available() >0)
    {
      char c = Serial.read();
      buf[idx++] = c;
      buf[idx] = 0;  // terminator...
      stringComplete = (c == '*');
    }
  }

  // PARSE THE STRING AND PRINT PARTS
  if (stringComplete)
  {
    // divide buffer in small strings.
    int l = strlen(buf);
    for (int i=0; i< l; i++)
    {
      if (buf[i] == ',') buf[i] = '\0';
    }

    // convert fields (FIX LENGTH !!
    strcpy(time, &buf[8]);
    valid    = buf[15];
    lat      = atof(&buf[17]);
    NS       = buf[19];
    lon      = atof(&buf[29]);
    EW       = buf[39];
    speed    = atof(&buf[41]);
    heading  = atof(&buf[47]);
    altitude = atof(&buf[53]);
    sat      = atoi(&buf[60]);

    // debug print
    Serial.println();
    Serial.println(millis());
    Serial.print("   TIME: "); 
    Serial.println(time);
    Serial.print("  VALID: "); 
    Serial.println(valid);
    Serial.print("    LAT: "); 
    Serial.println(lat,6);
    Serial.print("    N/S: "); 
    Serial.println(NS);
    Serial.print("   LONG: "); 
    Serial.println(lon,6);
    Serial.print("    E/W: "); 
    Serial.println(EW);
    Serial.print("  SPEED: "); 
    Serial.println(speed,3);
    Serial.print("HEADING: "); 
    Serial.println(heading,3);
    Serial.print("   ALT: "); 
    Serial.println(altitude,1);
    Serial.print("   #SAT: "); 
    Serial.println(sat);
    Serial.println();
  }
}

Since you seem comfortable with strings, here's how you'd use strtok() to split your code, since it is all separated by commas:

char *linea="$GPRMC,154653,V,4428.2011,N,00440.5161,W,000.5,342.8,050407,,,N*7F";
char *ptr=NULL;
ptr=strtok(linea,",");
if (NULL!=ptr)
{
  // ptr points to $GPRMC
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
  // ptr points to 154653
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
  // ptr points to V
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
  // ptr points to 4428.2011
}

You can compact this into a loop, or call only as long as you wish (for example, if you only need the first x parameters). If anything returns NULL, it will continue to return NULL until you're done - useful if you want default values anywhere.

Thanks guys! String parsing works really good now.

I tried to understand robtillaarts solution and I still have a question. I want to send the arduino a new string every 250ms. Unfortunately whenever I send a new string, the Serial.print outputs me the values from the very first string I send at the beginning.

I tried to Serial.flush() and StringComplete == true commands on different points of the code but I didn't find a solution. :frowning: I think the serial buffer is empty after the reading... Do I need to clear the "buf[80];" as well? So the loop doesn't read the buffer with the old variables?

Best regards!

I want to send the arduino a new string every 250ms.

Depending on the baud rate and the amount of processing required, that may be too often.

I tried to Serial.flush()

Throwing away random amounts of unread data is rarely a good idea.

and StringComplete == true

The == operator is an equality test, not an assignment operator.

but I didn't find a solution.

Or post any code... :frowning:

I think the serial buffer is empty after the reading...

Not necessarily. Reading should end when an end-of-packet marker is encountered OR the buffer is empty. It is possible for both to be true, but not necessary.

Do I need to clear the "buf[80];" as well?

Yes, and reset the index into the array. Note that only the first byte needs to be set to NULL, as long as the array is kept NULL terminated (as it should be).

Okay great, this is what i did, resettet the buffer and set string complete to false, think I was just a little confused about the "==", silly mistake :wink: ... seems to work now! Thanks!

// test string
char* s = "$GPSDAT,210102,A,50.352424,N,06.119385,E,000.0,000.0,0123.5,02,*";

// BUFFER TO HOLD SERIAL DATA
char buf[80];
int idx = 0;
boolean stringComplete;

// FIELDS
char time[8]; 
char valid;
float lat;
char NS;
float lon;
char EW;
float speed;
float heading;
float altitude;
int sat;

//////////////////////////////////
void setup()
{
  Serial.begin(115200);  // ADJUST 
  Serial.println("start");

  stringComplete = false;
}

void loop()
{
  delay(5000);           // get new data every 1/4 of a second
  {
   while (stringComplete == false) 
  {
    if (Serial.available() >0)
    {
      char c = Serial.read();
      buf[idx++] = c;
      buf[idx] = 0;  // terminator...
      stringComplete = (c == '*');
    }
  }

  // PARSE THE STRING AND PRINT PARTS
  if (stringComplete)
  {
    // divide buffer in small strings.
    int l = strlen(buf);
    for (int i=0; i< l; i++)
    {
      if (buf[i] == ',') buf[i] = '\0';
 
    }

    // convert fields (FIX LENGTH !!
    strcpy(time, &buf[8]);
    valid    = buf[15];
    lat      = atof(&buf[17]);
    NS       = buf[27];
    lon      = atof(&buf[29]);
    EW       = buf[39];
    speed    = atof(&buf[41]);
    heading  = atof(&buf[47]);
    altitude = atof(&buf[53]);
    sat      = atoi(&buf[60]);
    
    for (int j=0; j< 80; ++j) {buf[j] = 0;}
    idx=0;
    stringComplete = false;
    // debug print
    Serial.print("   TIME: "); 
    Serial.println(time);
    Serial.print("  VALID: "); 
    Serial.println(valid);
    Serial.print("    LAT: "); 
    Serial.println(lat,6);
    Serial.print("    N/S: "); 
    Serial.println(NS);
    Serial.print("   LONG: "); 
    Serial.println(lon,6);
    Serial.print("    E/W: "); 
    Serial.println(EW);
    Serial.print("  SPEED: "); 
    Serial.println(speed,3);
    Serial.print("HEADING: "); 
    Serial.println(heading,3);
    Serial.print("   ALT: "); 
    Serial.println(altitude,1);
    Serial.print("   #SAT: "); 
    Serial.println(sat);
    Serial.println();
  }}
}
  delay(5000);           // get new data every 1/4 of a second

5000 milliseconds / 1000 milliseconds/second != 1/4 seconds.

    for (int j=0; j< 80; ++j) {buf[j] = 0;}

is not necessary.

buf[0] = '\0';

will do.

Since buf is kept NULL terminated, replacing buf[0] with NULL is all that is required.

The strtok() function could be used in place of stuffing NULLs into buf to replace the commas. The strtok() will return a pointer to the (next) token, regardless of the length of the token, or the length of the preceding tokens.

Since you are going to be parsing real-time data that is NOT going to be fixed length, you should learn how to use strtok() BEFORE you tackle parsing real-time data.

Okay, so I changed the code to:

// test string
char* s = "$GPSDAT,210102,A,50.352424,N,06.119385,E,000.0,000.0,0123.5,02,*";

// BUFFER TO HOLD SERIAL DATA
char buf[80];
int idx = 0;
boolean stringComplete;

// FIELDS
char time[8]; 
char valid;
float lat;
char NS;
float lon;
char EW;
float speed;
float heading;
float altitude;
int sat;

//////////////////////////////////
void setup()
{
  Serial.begin(9600);  // ADJUST 
  Serial.println("start");

  stringComplete = false;
}

void loop()
{
//  delay(5000);           // get new data every 1/4 of a second
  {
   while (stringComplete == false) 
  {
    if (Serial.available() >0)
    {
      char c = Serial.read();
      buf[idx++] = c;
      buf[idx] = 0;  // terminator...
      stringComplete = (c == '*');
    }
  }

  // PARSE THE STRING AND PRINT PARTS
  if (stringComplete)
  {
    // divide buffer in small strings.
    int l = strlen(buf);
    for (int i=0; i< l; i++)
    {
      if (buf[i] == ',') buf[i] = '\0';
 
    }

    // convert fields (FIX LENGTH !!
    strcpy(time, &buf[8]);
    valid    = buf[15];
    lat      = atof(&buf[17]);
    NS       = buf[27];
    lon      = atof(&buf[29]);
    EW       = buf[39];
    speed    = atof(&buf[41]);
    heading  = atof(&buf[47]);
    altitude = atof(&buf[53]);
    sat      = atoi(&buf[60]);
    buf[0] = '\0';
    idx=0;
    stringComplete = false;
    // debug print
    Serial.print("   TIME: "); 
    Serial.println(time);
    Serial.print("  VALID: "); 
    Serial.println(valid);
    Serial.print("    LAT: "); 
    Serial.println(lat,6);
    Serial.print("    N/S: "); 
    Serial.println(NS);
    Serial.print("   LONG: "); 
    Serial.println(lon,6);
    Serial.print("    E/W: "); 
    Serial.println(EW);
    Serial.print("  SPEED: "); 
    Serial.println(speed,3);
    Serial.print("HEADING: "); 
    Serial.println(heading,3);
    Serial.print("   ALT: "); 
    Serial.println(altitude,1);
    Serial.print("   #SAT: "); 
    Serial.println(sat);
    Serial.println();
  }}
}

I will try to understand the strtok() function this evening, I think David gave me a pretty good example... But I have another problem which I just found out. For testing I used the "Arduino" Serial Monitor... I can send strings and get the answer as it is stated in the code. But now that I switched to another terminal program I received empty variables after the first string.. :frowning: Any idea what that is?

error2.jpg

Any idea what that is?

Get a idea where the problem is:

if (stringComplete)
{
Serial.print("buf = [");
Serial.print(buf);
Serial.println("]");
// divide buffer in small strings.

My guess is it is because you aren't latching onto the start of your data string, and your program is injecting additional termination characters, either a carriage return, or a line feed, or both. This will result in all your data shifting to the right 1 or 2 characters, and all your hard coded data locations just fall apart at that point.

With the Arduino Serial Monitor, what do you have selected for line termination? It'll be a list box in the lower right corner right next to the baud rate. Default is Carriage return, but I'd guess you have it set to no line ending, otherwise your code would have the same problem with the Serial Monitor.

Hi guys, this is how i solved my problems :slight_smile: Thx for your help

// BUFFER TO HOLD SERIAL DATA
char buf[65];
int idx = 0;
boolean stringComplete;

// FIELDS
//$GPSDAT,210102,A,50.352424,N,06.119385,E,000.0,000.0,0123.5,02,* <-teststring
char gpsdat;
char endstring;
long time;
char valid;
float lat;
char NS;
float lon;
char EW;
float speed;
float heading;
float altitude;
int sat;

//////////////////////////////////
void setup()
{
  Serial.begin(9600);  // ADJUST
  Serial.println("start");
  stringComplete = false;
}

void loop()
{

   {
   while (stringComplete == false) 
  {
    if (Serial.available() >0)
    {
      char c = Serial.read();
      buf[idx++] = c;
      buf[idx] = 0;  // terminator...
      stringComplete = (c == '*');
    }
  }

char *ptr=NULL;
ptr=strtok(buf,",");
  
if (NULL!=ptr)
{
  gpsdat=*ptr; // ptr points to $
  Serial.print(gpsdat); 
  Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
   time=atof(ptr);
  Serial.print(time); //ptr points to time
  Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 valid=*ptr;
 Serial.print(valid); 
 Serial.println();// ptr points to Valid or Not Valid
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 lat = atof(ptr);
 Serial.print(lat,6); // ptr points to lat
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 NS = *ptr;
 Serial.print(NS); // ptr points to N/S
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 lon = atof(ptr);
 Serial.print(lon,6); // ptr points to lon
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 EW = *ptr;
 Serial.print(EW); // ptr points to EW
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 speed = atof(ptr);
 Serial.print(speed); // ptr points to speed
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 heading = atof(ptr);
 Serial.print(heading); // ptr points to heading
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 altitude = atof(ptr);
 Serial.print(altitude); // ptr points to altitude
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 sat = atof(ptr);
 Serial.print(sat); // ptr points to #sat
 Serial.println();
}
if ( NULL != (ptr=strtok(NULL,",")) )
{
 endstring=*ptr; // ptr points to $
  Serial.print(endstring); 
  Serial.println();
}
*ptr=NULL;
stringComplete = false;
buf[0] = '\0';
}
}