Pages: [1]   Go Down
Author Topic: Read String from Serial Input, parse it, and write it to the EEPROM.  (Read 1434 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,

I'm trying to do something that should be very simple but it seems much more complicated. I need to receive a String like this:

12:22:-1:30:123:112

This string should then be splitted by the colons and each byte stored in a specific position of the EEPROM:

Please take a look at the following code:

Code:
  byte tmpBuff[6];
  int pos=0;
  int previousStartConfig=0;
  String configuration = command.substring(9);
  for (int i=0; i < configuration.length(); i++)
  {
    if ( configuration.charAt(i) == ':' )
    {
        tmpBuff[pos] = tmpBuff, configuration.substring(previousStartConfig, i);
        previousStartConfig = i+1;
        pos++;
    }
  }
 
  if ( pos < 6 )
  {
    Serial.println("Bad configuration!");
    return;
  } 

Now, I know that this piece of code doesn't work and I know that the problem is due to the conversion from the string to byte. But how can I fi this?

PS: Feel free to improve this piece of code because I know it sucks (full of variables ).  smiley-lol
Logged

California
Offline Offline
Faraday Member
**
Karma: 92
Posts: 3447
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,

I'm trying to do something that should be very simple but it seems much more complicated. I need to receive a String like this:

12:22:-1:30:123:112

This string should then be splitted by the colons and each byte stored in a specific position of the EEPROM:

Please take a look at the following code:

Code:
 byte tmpBuff[6];
  int pos=0;
  int previousStartConfig=0;
  String configuration = command.substring(9);
  for (int i=0; i < configuration.length(); i++)
  {
    if ( configuration.charAt(i) == ':' )
    {
        tmpBuff[pos] = tmpBuff, configuration.substring(previousStartConfig, i);
        previousStartConfig = i+1;
        pos++;
    }
  }
  
  if ( pos < 6 )
  {
    Serial.println("Bad configuration!");
    return;
  }  

Now, I know that this piece of code doesn't work and I know that the problem is due to the conversion from the string to byte. But how can I fi this?

PS: Feel free to improve this piece of code because I know it sucks (full of variables ).  smiley-lol

Does the string have any end of packet marker? New line? carriage return?

Get rid of the String object and use null-terminated char arrays. A finite state machine should be used to keep track of where in the string you are.

Psuedo code:
If serial information available
{
  read character
  if character is a colon or end-of-packet marker
  {
    null terminate the buffer
    convert string to number (atoi())
    store number in EEPROM
    reset the buffer
  }
  else
  {
    store character in buffer
  }
}
« Last Edit: November 26, 2012, 05:34:46 pm by Arrch » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,

I'm trying to do something that should be very simple but it seems much more complicated. I need to receive a String like this:

12:22:-1:30:123:112

This string should then be splitted by the colons and each byte stored in a specific position of the EEPROM:

Please take a look at the following code:

Code:
  byte tmpBuff[6];
  int pos=0;
  int previousStartConfig=0;
  String configuration = command.substring(9);
  for (int i=0; i < configuration.length(); i++)
  {
    if ( configuration.charAt(i) == ':' )
    {
        tmpBuff[pos] = tmpBuff, configuration.substring(previousStartConfig, i);
        previousStartConfig = i+1;
        pos++;
    }
  }
 
  if ( pos < 6 )
  {
    Serial.println("Bad configuration!");
    return;
  } 

Now, I know that this piece of code doesn't work and I know that the problem is due to the conversion from the string to byte. But how can I fi this?

PS: Feel free to improve this piece of code because I know it sucks (full of variables ).  smiley-lol

Does the string have any end of packet marker? New line? carriage return?

Get rid of the String object and use null-terminated char arrays. A finite state machine should be used to keep track of where in the string you are.

Hi Arrch,

Thank you for your reply.

The String ends with a carriage return but this is not passed to my function. Let me show you how I'm parsing the commands:
This is the code that I have in the loop() subroutine:
Code:
if ( Serial.available() > 0 )
 {
      c = Serial.read();
      if (c==13)
        processCommand();
      else
        command.concat(c);
   
   Serial.print(c); //echo purposes
 }

I keep appending chars to a global variable called command and when I receive a CR I process the command. I didn't used char arrays because I find them harder to handle but I'm willing to give it a try. Can you show me some example? Can I use string functions like substring with chars?

Thanks!
Logged

California
Offline Offline
Faraday Member
**
Karma: 92
Posts: 3447
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I keep appending chars to a global variable called command and when I receive a CR I process the command.
Instead, you should be appending characters until you receive a carriage return OR colon.
Quote
I didn't used char arrays because I find them harder to handle but I'm willing to give it a try.
They are slightly harder to handle when you don't know much experience with C, but microcontrollers don't have near the amount of memory available that the microprocessors do, so handling data intensive items, such as String objects, is not recommended. There is also a known bug with the String object in the Arduino environment that can cause memory issues. Best practice is not to use it.

Quote
Can you show me some example?

Simple example that will read numbers from the serial line until it gets a new line and convert to an int:

Code:
static char buffer[6];
static int i=0;
if (Serial.available() > 0)
{
  char inByte = Serial.read();
  if (inByte == '\n')
  {
    // convert the string to a number, and print
    Serial.print("Number received: ");
    Serial.println(atoi(buffer));
    // reset the buffer
    i=0;
    inByte[0] = '\0';
  }
  else
  {
    inByte[i++];  // concatanate the character
    inByte[i] = '\0'; // null terminate the array
  }
}

Quote
Can I use string functions like substring with chars?

List of available string functions:
http://www.cplusplus.com/reference/cstring/
Logged

0
Offline Offline
Tesla Member
***
Karma: 148
Posts: 9750
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Simple example that will read numbers from the serial line until it gets a new line and convert to an int:

interesting snippet, but I seem to have a compile error at    inByte[0] = '\0';
Logged

Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   smiley-cool

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 653
Posts: 50901
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
    inByte[0] = '\0';
  }
  else
  {
    inByte[i++];  // concatanate the character
    inByte[i] = '\0'; // null terminate the array
  }
should be
Code:
    buffer[0] = '\0';
  }
  else
  {
    buffer[i++];  // concatanate the character
    buffer[i] = '\0'; // null terminate the array
  }

Is 6 characters enough for the buffer?
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello guys,

I've changed all my code to use char arrays instead of strings but I'm still struggling to parse and store data inside the EEPROM.

This is my current code:

Code:
void writeConfig()
{

  char *myCommand = "saveConfig 2:2:2:2:2:2:2:2";
  char *tmpBuff;
  char configuration[64];
  int comma=0;
 
 
  for(int i=0; i < strlen(myCommand); i++)
  {
    if( myCommand[i] == ':' ) comma++;
  }

  if ( (comma < 7)  || (strlen(myCommand) < 26) )
  {
    Serial.println("Invalid configuration.");
    return;
  }
   
  strncpy(configuration, (myCommand+11), sizeof(myCommand) - 11);
 
  tmpBuff = strtok(configuration, ":");
    int counter=0;
    while (tmpBuff != NULL)
  {
    EEPROM.write(counter, int(tmpBuff));
    tmpBuff = strtok (NULL, ":");
    counter++;
  }
 
 
  Serial.println("Configuration saved!");
}

Please ignore the lines below "strncpy(configuration, (myCommand+11), sizeof(myCommand) - 11);" because they werent tested yet. This line is crashing the arduino and it is reseting everytime it is executed and I don't know why!

All i'm trying to do is:

1st - Receive a string (I've hardcoded it for testing purposes -> char *myCommand = "saveConfig 2:2:2:2:2:2:2:2"smiley-wink
2nd - Cut the head leaving just the configuration argument.
3rd - Split the configuration argument using the commas as delimiter
4th - Save each of those numbers in the EEPROM. Any of them will always be a number ranging from 0 to 255

How can i accomplish this?
Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 653
Posts: 50901
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
  for(int i=0; i < strlen(myCommand); i++)
  {
    if( myCommand == ':' ) comma++;
  }
This is a comma --> ,
This is a colon --> :

See the difference?

Code:
    EEPROM.write(counter, int(tmpBuff));
The int() macro is for people that think (int)tmpBuff looks weird. Personally, I'd have told those people to get over it. The int() macro does NOT convert a char array to an int. The atoi() function does that. All that it does is cast the address of the token to an int, which is hardly the data that you want to be storing in the EEPROM.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
  for(int i=0; i < strlen(myCommand); i++)
  {
    if( myCommand == ':' ) comma++;
  }
This is a comma --> ,
This is a colon --> :

See the difference?

Code:
    EEPROM.write(counter, int(tmpBuff));
The int() macro is for people that think (int)tmpBuff looks weird. Personally, I'd have told those people to get over it. The int() macro does NOT convert a char array to an int. The atoi() function does that. All that it does is cast the address of the token to an int, which is hardly the data that you want to be storing in the EEPROM.

Hellom PaulS, thank you for your reply smiley

About the first part, please bear with me. I was actually thinking in colon ( as you can see, I'm using commas in the hardcoded command and also when I'm searching for it) but switched the names (I'm Portuguese).  smiley-wink

I've also corrected the problem related to the cast but that still doesn't fixe the original issue.

Everytime the strncpy is executed my arduino is resetedand I have no idea why smiley-sad
Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 653
Posts: 50901
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Everytime the strncpy is executed my arduino is resetedand I have no idea why
Nor do I, since the code you posted doesn't call strncpy(). The usual culprit, though, is copying outside the bounds of the destination array. Copy 12 bytes into a 2 element array is not a good thing. Nor is copying to a location pointed to be a non-initialized pointer.

The comma variable should be named delimterCounter, so that it doesn't matter what the delimiter is.
Logged

0
Offline Offline
Tesla Member
***
Karma: 148
Posts: 9750
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I've changed all my code to use char arrays instead of strings but I'm still struggling to parse and store data inside the EEPROM.

I couldn't get any of the posted stuff to work either. Below is simple code to captrure a string. It uses the String class thing, but it actually works (at least for me).

Code:
//zoomkat 3-5-12 simple delimited ',' string parce
//from serial port input (via serial monitor)
//and print result out serial port
// CR/LF could also be a delimiter

String readString;

void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 1.0"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like wer,qwe rty,123 456,hyre kjhg,
  //or like hello world,who are you?,bye!,
 
  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      //do stuff
      Serial.println(readString); //prints string to serial port out
      readString=""; //clears variable for new input     
     } 
    else {     
      readString += c; //makes the string readString
    }
  }
}

Logged

Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   smiley-cool

France
Offline Offline
Edison Member
*
Karma: 38
Posts: 1012
Scientia potentia est.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello,

If you want simplicity (and have enough memory to waste) you can use sscanf, example:

http://arduino.cc/forum/index.php/topic,134496.msg1011549.html#msg1011549
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Everytime the strncpy is executed my arduino is resetedand I have no idea why
Nor do I, since the code you posted doesn't call strncpy(). The usual culprit, though, is copying outside the bounds of the destination array. Copy 12 bytes into a 2 element array is not a good thing. Nor is copying to a location pointed to be a non-initialized pointer.

The comma variable should be named delimterCounter, so that it doesn't matter what the delimiter is.

Thanks for the tip related to the variable name. You are right and it is much more clearer that way smiley-grin

But the strncpy is in fact called. If you look closely you will see that it is the first thing called after I check if the minimum number of colons is present.

Anyway thanks for the help. I was using wrong index values and now everything seems to work perfectly smiley

But now I have another question:

I have my code protected agains buffer overrun, but how can I validate my code against invalid input?

i.e:
 
Lets imagine that the user pass the following argument:

Quote
1:2:3:1:1:a:!:9

How can I parse the content and be sure I will only read numbers (preferably between 0 and 255)?

What I'm currently doing is using the atoi() function to parse the string, and everytime atoi() returns 0 I compare this value to the original string just to make sure that the "zero" wasn't actually the real value being passed (due to the fact that atoi() returns 0 everytime it wasn't able to parse the string into an int and this may be confused the a real zero).

Is this the most effective way of doing it?
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 15
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I've changed all my code to use char arrays instead of strings but I'm still struggling to parse and store data inside the EEPROM.

I couldn't get any of the posted stuff to work either. Below is simple code to captrure a string. It uses the String class thing, but it actually works (at least for me).

Code:
//zoomkat 3-5-12 simple delimited ',' string parce
//from serial port input (via serial monitor)
//and print result out serial port
// CR/LF could also be a delimiter

String readString;

void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 1.0"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like wer,qwe rty,123 456,hyre kjhg,
  //or like hello world,who are you?,bye!,
 
  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      //do stuff
      Serial.println(readString); //prints string to serial port out
      readString=""; //clears variable for new input     
     } 
    else {     
      readString += c; //makes the string readString
    }
  }
}


Thanks for your response, but my problem wasn't related to reading strings from serial input but parsing them after reading smiley
Logged

California
Offline Offline
Faraday Member
**
Karma: 92
Posts: 3447
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Is this the most effective way of doing it?
Loop through each character, checking if it is < '0', >'9' or != ':'
Logged

Pages: [1]   Go Up
Jump to: