Go Down

Topic: serial string to integers (Read 1 time) previous topic - next topic

andywatson

I have a sensor that outputs a serial string of numbers, spaces, and letters.  It's in a format like this:   "525 0 894xcf"

It's basically 3 numbers separated by white spaces.  The numbers can vary from 1 to 4 digits, so I have to use the white spaces as delimiters.  And there are always a few letters tacked onto the last number that I want to throw away.  I need to separate the 3 numbers into integers so that they can be stored or transmitted further down in the script.

I'm able to successfully read in the serial data and store it in a string.  The trouble comes when I try to parse it.

I've searched this forum and got several different examples.  Some people say to use sscanf, others say atoi or strtok.  It seems like there are several ways to do it and everyone has their own opinion about what should or should not be used.   I've tried all 3 of the above by using examples people have posted here, but can't seem to make them work.  So I'm wondering which method I should focus on if somebody has some tips.

jraskell

strtok can be useful to quickly and easily break a string up into the specified tokens (but it doesn't do any datatype conversion, so you still have to use something like atoi to convert the values).  An important side affect to strtok is that it modifies the string it is tokenizing.

sscanf is going to have more compiled overhead than atoi, because it's a much more versatile function.  On small sketches this may not be an issue, but if you're low on flash space, the difference between sscanf and atoi could be the difference between your program fitting on the micro or not.

wildbill

strtok will do it. Here's a quick & dirty example. Note that it assumes that you have the string with all three numbers & doesn't check that that is really the case:
Code: [Select]

char buf[]="525 0 894xcf";
char separator[]=" ";

void setup()
{
  char *ptr;
  int x,y,z;
  Serial.begin(9600);
  ptr=strtok(buf,separator);
  x=atoi(ptr);
  Serial.println(x);
  ptr=strtok(NULL,separator);
  y=atoi(ptr);
  Serial.println(y);
  ptr=strtok(NULL,separator);
  z=atoi(ptr);
  Serial.println(z);
}

void loop()
{
}

jraskell

Quick and dirty would be sscanf.  1 line of code instead of 6.  This:

Code: [Select]

  ptr=strtok(buf,separator);
  x=atoi(ptr);
  ptr=strtok(NULL,separator);
  y=atoi(ptr);
  ptr=strtok(NULL,separator);
  z=atoi(ptr);


becomes this:

Code: [Select]

  sscanf(buf, "%d %d %d", &x, &y, &z);


The important difference to be aware of though, the sscanf version is roughly 1500 bytes larger when compiled.  It also doesn't modify the buf string.

andywatson

Thanks for the help.  I'm posting the code I have so far, but  I'm getting the following error on the line with the sscanf function:

Code: [Select]
_5TMtest_2:25: error: cannot convert 'String' to 'const char*' for argument '1' to 'int sscanf(const char*, const char*, ...)'

My code:

Code: [Select]
#include <NewSoftSerial.h>

NewSoftSerial mySerial(8, 9);
int x,y,z;
String readString;

void setup()
{
  Serial.begin(1200);
   mySerial.begin(1200);
   Serial.println("5TMtest-2"); // so I can keep track of what is loaded
}

void loop() {
 
    while (mySerial.available()) {
    delay(1); 
    if (mySerial.available() >0) {
      char c = mySerial.read();  //gets one byte from serial buffer
      readString += c; //makes the string readString
    }
  }
  if (readString.length() >0) {
   Serial.print("readString = ");
   Serial.println(readString);  //so you can see the captured string
   sscanf (readString, "%d %d %d", &x, &y, &z);
   Serial.print("first= ");
   Serial.println(x);
   Serial.print("middle= ");
   Serial.println(y);
   Serial.print("last= ");
   Serial.println(z);
  }
   readString="";
}

AWOL

The error is saying you can't pass a String object to sscanf, which expects a C string (an array of "char", terminated by a zero).
You need to convert your String to a C string if you want to use sscanf.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

andywatson

Okay, that makes sense.  But how to I convert the string to a C string?  Would it be easier to read the data from the sensor directly into a C string first instead of converting?

AWOL

"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

wildbill


Would it be easier to read the data from the sensor directly into a C string first instead of converting?

No easier, but probably safer - you won't have to worry about string operations fragmenting your heap and failing unexpectedly after you've been running for a while.

andywatson

#9
May 03, 2011, 10:28 pm Last Edit: May 03, 2011, 10:41 pm by andywatson Reason: 1
It's working now, except that the serial data from my sensor apparently has a couple non-numeric characters before the actual numbers I want to read, along with a carriage return after them, and that seems to be throwing things off.  Is there a way to filter out non-numeric things when populating the string during the myserial.read part?

I just captured several lines of the raw serial data from the sensor using a program on my PC called RealTerm to save it to a text file.  I'm attaching a picture of what the raw sensor data looks like when viewed with notepad.  Note the strange character at the beginning of each line and the little box after 642, which I guess is a carriage return. 

wildbill

Test the character you've read to see if it's in the 0-9 range, or is space. Use these, throw everything else away. Something like this fragment:
Code: [Select]

  char c;
  if(Serial.available())
    {
      c=Serial.read();
      if((c>='0' and c<='9') || c==' ')
        {
        //use character
        }
    }

andywatson

Thanks wildbill, that code worked perfectly for filtering out all the other stuff.  Thanks! 

Go Up