How do I split an incoming string?
Contrarily to other answers, I'd rather stay away from String
for the following reasons:
- dynamic memory usage (that may quickly lead to heap fragmentation and memory 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 use standard 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 characteratoi()
: 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 string
input[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 declare input
as a local variable inside a function that would read the commands and execute them; once the function is returned the size occupied by input
(in the stack) is recovered.
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 = xvalue.toInt(xval);
int yvalue = yvalue.toInt(yval);
This Chunk of code takes a string and separates it based on a given character and returns The 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]) : "";
}
You could do something like the following, but please take into account several things:
If you use readStringUntil()
, 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 to int
, and then match the loop index i) to ensure that nothing went wrong with your message.
You have to check that the returning string from readStringUntil
is not empty. If the function timeouts, you didn't receive enough data, and thus any attempt to extract your int
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);
}
}
}