71

I am sending a list of servo positions via the serial connection to the arduino in the following format

1:90&2:80&3:180

Which would be parsed as:

servoId : Position & servoId : Position & servoId : Position

How would I split these values up, and convert them to an integer?

Anonymous Penguin's user avatar
Anonymous Penguin
6,36510 gold badges34 silver badges62 bronze badges
askedMar 31, 2014 at 6:35
ValrikRobot's user avatar
1
  • i have slave(arduino uno) send string via serial 30;12.4;1 and 1 master (esp8266) recive string i want in master have seperated data like 30 12.4 1 and save it in micro sd cardCommentedMay 27, 2018 at 10:57

12 Answers12

98

Contrarily to other answers, I'd rather stay away fromString for the following reasons:

  • dynamic memory usage (that may quickly lead toheap fragmentation andmemory exhaustion)
  • quite slow due to construction/destruction/assignment operators

In an embedded environment like Arduino (even for a Mega that has more SRAM), I'd rather usestandard C functions:

  • strchr(): search for a character in a C string (i.e.char *)
  • strtok(): splits a C string into substrings, based on a separator character
  • atoi(): converts a C string to anint

That would lead to the following code sample:

// Calculate based on max input size expected for one command#define INPUT_SIZE 30...// Get next command from Serial (add 1 for final 0)char input[INPUT_SIZE + 1];byte size = Serial.readBytes(input, INPUT_SIZE);// Add the final 0 to end the C stringinput[size] = 0;// Read each command pair char* command = strtok(input, "&");while (command != 0){    // Split the command in two values    char* separator = strchr(command, ':');    if (separator != 0)    {        // Actually split the string in 2: replace ':' with 0        *separator = 0;        int servoId = atoi(command);        ++separator;        int position = atoi(separator);        // Do something with servoId and position    }    // Find the next command in input string    command = strtok(0, "&");}

The advantage here is that no dynamic memory allocation takes place; you can even declareinput as a local variable inside a function that would read the commands and execute them; once the function is returned the size occupied byinput (in the stack) is recovered.

answeredMar 31, 2014 at 18:12
jfpoilpret's user avatar
2
  • Hadn't thought of the memory issue. this is great.CommentedMar 31, 2014 at 18:39
  • 4
    Excellent. My answer was very "arduino" based and using typical arduino SDK functions which a novel user could be more used to, but this answer is what should be done for "production" systems. In general, try to escape from dynamic memory allocation in embedded systems.CommentedApr 1, 2014 at 9:22
45

This function can be used to separate a string into pieces based on what the separating character is.

String xval = getValue(myString, ':', 0);String yval = getValue(myString, ':', 1);Serial.println("Y:" + yval);Serial.print("X:" + xval);

Convert String to int

int xvalue = xval.toInt();int yvalue = yval.toInt();

This Chunk of code takes a string and separates it based on a given character and returnsThe item between the separating character

String getValue(String data, char separator, int index){    int found = 0;    int strIndex[] = { 0, -1 };    int maxIndex = data.length() - 1;    for (int i = 0; i <= maxIndex && found <= index; i++) {        if (data.charAt(i) == separator || i == maxIndex) {            found++;            strIndex[0] = strIndex[1] + 1;            strIndex[1] = (i == maxIndex) ? i+1 : i;        }    }    return found > index ? data.substring(strIndex[0], strIndex[1]) : "";}
answeredApr 19, 2014 at 19:08
Odis Harkins's user avatar
0
13

You could do something like the following, but please take into account several things:

If you usereadStringUntil(), it will wait until it receives the character or timeouts. Thus, with your current string, the last position will last a little longer, as it has to wait. You can add a trailing& to avoid this timout. You can easily check this behavior in your monitor, try to send the string with and without the extra& and you will see such timeout delay.

You actually do not need the servo index, you can just send your string of positions, and get the servo index by the value position in the string, something like:90&80&180&.

If you use the servo index, maybe you want to check it (convert toint, and then match the loop index i) to ensure that nothing went wrong with your message.

You have to check that the returning string fromreadStringUntil is not empty. If the function timeouts, you didn't receive enough data, and thus any attempt to extract yourint values will produce strange results.

void setup() {  Serial.begin(9600);}void loop() {  for (int i=1; i<=3; i++) {    String servo = Serial.readStringUntil(':');    if (servo != "") {      // here you could check the servo number      String pos = Serial.readStringUntil('&');      int int_pos=pos.toInt();      Serial.println("Pos");      Serial.println(int_pos);    }  }}
ocrdu's user avatar
ocrdu
1,7953 gold badges12 silver badges24 bronze badges
answeredMar 31, 2014 at 7:51
drodri's user avatar
5
  • This seems like a very good solution thank you. The example clears it up perfectlyCommentedMar 31, 2014 at 8:01
  • What if we had an undefined number of servo inputs? in my example there was 3. But what if sometimes it was more, or less. Can you offer any suggestion for handling such a scenarioCommentedMar 31, 2014 at 8:30
  • 1
    Sure: There are two possibilities. 1. Send first the number of servos: 3:val1&val2&val3&, read such number prior to starting the loop. 2. Use a different terminator to indicate you have no more servos, loop until you find it: val1&val2&val3&#, for example.CommentedMar 31, 2014 at 8:34
  • Glad this solution helped you, @ValrikRobot, could you please validate the answer if it was useful?CommentedMar 31, 2014 at 10:14
  • 3
    or you can just remove the for, and so the code will just work any time you send a command.CommentedMar 31, 2014 at 13:04
9

You can useStream.readStringUntil(terminator) passing a different terminator for each part.

On each part you then callString.toInt

answeredMar 31, 2014 at 7:36
Federico Fissore's user avatar
7

Simplest solution is to usesscanf().

  int id1, id2, id3;  int pos1, pos2, pos3;  char* buf = "1:90&2:80&3:180";  int n = sscanf(buf, "%d:%d&%d:%d&%d:%d", &id1, &pos1, &id2, &pos2, &id3, &pos3);  Serial.print(F("n="));  Serial.println(n);  Serial.print(F("id1="));  Serial.print(id1);  Serial.print(F(", pos1="));  Serial.println(pos1);  Serial.print(F("id2="));  Serial.print(id2);  Serial.print(F(", pos2="));  Serial.println(pos2);  Serial.print(F("id3="));  Serial.print(id3);  Serial.print(F(", pos3="));  Serial.println(pos3);

This give the following output:

n=6id1=1, pos1=90id2=2, pos2=80id3=3, pos3=180

Cheers!

answeredFeb 18, 2016 at 14:18
Mikael Patel's user avatar
1
  • It is not working for serial.read()... any idea why? I get the following error:invalid conversion from 'int' to 'char*' [-fpermissive]CommentedApr 23, 2016 at 12:47
4
String getValue(String data, char separator, int index){    int maxIndex = data.length() - 1;    int j = 0;    String chunkVal = "";    for (int i = 0; i <= maxIndex && j <= index; i++)    {        chunkVal.concat(data[i]);        if (data[i] == separator)        {            j++;            if (j > index)            {                chunkVal.trim();                return chunkVal;            }            chunkVal = "";        }        else if ((i == maxIndex) && (j < index)) {            chunkVal = "";            return chunkVal;        }    }   }
answeredMar 9, 2015 at 17:07
Sam San's user avatar
1
  • That's was good. That's was perfect!!!CommentedJan 4, 2022 at 22:28
4

See example at:https://github.com/BenTommyE/Arduino_getStringPartByNr

// splitting a string and return the part nr index split by separatorString getStringPartByNr(String data, char separator, int index) {    int stringData = 0;        //variable to count data part nr     String dataPart = "";      //variable to hole the return text    for(int i = 0; i<data.length()-1; i++) {    //Walk through the text one letter at a time        if(data[i]==separator) {            //Count the number of times separator character appears in the text            stringData++;        } else if(stringData==index) {            //get the text when separator is the rignt one            dataPart.concat(data[i]);        } else if(stringData>index) {            //return text and stop if the next separator appears - to save CPU-time            return dataPart;            break;        }    }    //return text if this is the last part    return dataPart;}
dda's user avatar
dda
1,5951 gold badge12 silver badges18 bronze badges
answeredMar 14, 2015 at 18:58
Ben-Tommy Eriksen's user avatar
3

jfpoilpret provided greatanswer for parsing serial command on Arduino. However Attiny85 doesn't have bidirectional serial - SoftwareSerial has to be used. This is how you port same code for Attiny85

#include <SoftwareSerial.h>// Calculate based on max input size expected for one command#define INPUT_SIZE 30// Initialize SoftwareSerialSoftwareSerial mySerial(3, 4); // RX=PB3, TX=PB4// Parameter for receiving Serial command (add 1 for final 0)char input[INPUT_SIZE + 1];void setup() {  mySerial.begin(9600);}void loop() {  // We need this counter to simulate Serial.readBytes which SoftwareSerial lacks  int key = 0;  // Start receiving command from Serial  while (mySerial.available()) {    delay(3);  // Delay to allow buffer to fill, code gets unstable on Attiny85 without this for some reason    // Don't read more characters than defined    if (key < INPUT_SIZE && mySerial.available()) {      input[key] = mySerial.read();      key += 1;    }  }  if (key > 0) {    // Add the final 0 to end the C string    input[key] = 0;    // Read each command pair    char* command = strtok(input, "&");    while (command != 0)    {      // Split the command in two values      char* separator = strchr(command, ':');      if (separator != 0)      {        // Actually split the string in 2: replace ':' with 0        *separator = 0;        int servoId = atoi(command);        ++separator;        int position = atoi(separator);      }      // Find the next command in input string      command = strtok(0, "&");    }  }}

Attiny85 schematics for pin numbersenter image description here

Sketch compiles into:

Sketch uses 2244 bytes (27%) of program storage space. Maximum is 8192 bytes.Global variables use 161 bytes (31%) of dynamic memory, leaving 351 bytes for local variables. Maximum is 512 bytes.

So there is plenty of space and memory for the rest of code

answeredMar 14, 2018 at 21:16
goodevil's user avatar
3
  • How to read from serial on an ATtiny85 isn't really part of the question.CommentedMar 14, 2018 at 22:43
  • Sorry for diverging from question, but community and resources available for Attiny is way smaller than for Arduino. People like me looking for answers useArduino keyword and sometimes get into very tricky situations as implementing Arduino code onto Attiny is not always trivial. Had to convert original code to work on Attiny, tested it working and decided to share itCommentedMar 16, 2018 at 19:33
  • This site is in Q&A format. Answers should answer the question. Yours just adds something that's unrelated to it.CommentedMar 16, 2018 at 19:37
1

Here isArduino method to split a String as answer to the question"How to split a string in substring?" declared as a duplicate of the present question.

The objective of the solution is to parse a series ofGPS positions logged into aSD card file. Instead of having a String received fromSerial, the String is read from file.

The function isStringSplit() parse a StringsLine = "1.12345,4.56789,hello" to 3 StringssParams[0]="1.12345",sParams[1]="4.56789" &sParams[2]="hello".

  1. String sInput: the input lines to be parsed,
  2. char cDelim: the delimiter character between parameters,
  3. String sParams[]: the output array of parameters,
  4. int iMaxParams: the maximum number of parameters,
  5. Outputint: the number of parsed parameters,

The function is based onString::indexOf() andString::substring() :

int StringSplit(String sInput, char cDelim, String sParams[], int iMaxParams){    int iParamCount = 0;    int iPosDelim, iPosStart = 0;    do {        // Searching the delimiter using indexOf()        iPosDelim = sInput.indexOf(cDelim,iPosStart);        if (iPosDelim > (iPosStart+1)) {            // Adding a new parameter using substring()             sParams[iParamCount] = sInput.substring(iPosStart,iPosDelim-1);            iParamCount++;            // Checking the number of parameters            if (iParamCount >= iMaxParams) {                return (iParamCount);            }            iPosStart = iPosDelim + 1;        }    } while (iPosDelim >= 0);    if (iParamCount < iMaxParams) {        // Adding the last parameter as the end of the line        sParams[iParamCount] = sInput.substring(iPosStart);        iParamCount++;    }    return (iParamCount);}

And the usage is really simple:

String sParams[3];int iCount, i;String sLine;// reading the line from filesLine = readLine();// parse only if existsif (sLine.length() > 0) {    // parse the line    iCount = StringSplit(sLine,',',sParams,3);    // print the extracted paramters    for(i=0;i<iCount;i++) {        Serial.print(sParams[i]);    }    Serial.println("");}
answeredNov 28, 2016 at 20:15
J. Piquard's user avatar
1
char str[] = "1:90&2:80&3:180";     // test sample serial input from servoint servoId;int position;char* p = str;while (sscanf(p, "%d:%d", &servoId, &position) == 2){    // process servoId, position here    //    while (*p && *p++ != '&');   // to next id/pos pair}
answeredJan 22, 2018 at 23:34
pakled's user avatar
0
void setup() {Serial.begin(9600);char str[] ="1:90&2:80";char * pch;pch = strtok(str,"&");printf ("%s\n",pch);pch = strtok(NULL,"&"); //pch=next valueprintf ("%s\n",pch);}void loop(){}
answeredJun 20, 2019 at 11:56
Vitalicus's user avatar
0

It's not an answer to your question but it may be useful for someone. If your string has a specific prefix, then you can use simplystartsWith andsubstring. E.g.

void loop ()  if(Serial.available()){    command = Serial.readStringUntil('\n');    Serial.println("Received command: " + command);    if(command.startsWith("height")) {      Serial.println(command.substring(7));    }  }}

And then sendheight 10 what will extract10.

answeredNov 14, 2019 at 17:57
Pawel Zentala's user avatar
Protected question. To answer this question, you need to have at least 10 reputation on this site (not counting theassociation bonus). The reputation requirement helps protect this question from spam and non-answer activity.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.