¿Cómo divido una cadena entrante?


51

Estoy enviando una lista de posiciones de servo a través de la conexión en serie al arduino en el siguiente formato

1:90&2:80&3:180

Que se analizaría como:

servoId : Position & servoId : Position & servoId : Position

¿Cómo dividiría estos valores y los convertiría en un entero?


tengo esclavo (arduino uno) enviar cadena a través de serie 30; 12.4; 1 y 1 maestro (esp8266) recibir cadena que quiero en maestro tiene datos separados como 30 12.4 1 y guardarlo en la tarjeta micro sd
majid mahmoudi

Respuestas:


72

Contrariamente a otras respuestas, prefiero alejarme Stringpor las siguientes razones:

  • uso dinámico de memoria (que puede conducir rápidamente a la fragmentación del montón y al agotamiento de la memoria )
  • bastante lento debido a los operadores de construcción / destrucción / asignación

En un entorno integrado como Arduino (incluso para un Mega que tiene más SRAM), prefiero usar las funciones estándar de C :

  • strchr(): busca un carácter en una cadena C (es decir char *)
  • strtok(): divide una cadena C en subcadenas, en función de un carácter separador
  • atoi(): convierte una cadena C en un int

Eso llevaría al siguiente ejemplo de código:

// 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, "&");
}

La ventaja aquí es que no tiene lugar una asignación dinámica de memoria; incluso puede declarar inputcomo una variable local dentro de una función que leería los comandos y los ejecutaría; una vez que se devuelve la función, inputse recupera el tamaño ocupado por (en la pila).


No había pensado en el problema de la memoria. esto es genial.
ValrikRobot

44
Excelente. Mi respuesta estaba basada en "arduino" y utilizaba las funciones típicas de SDK de arduino a las que un usuario nuevo podría estar más acostumbrado, pero esta respuesta es lo que se debe hacer para los sistemas de "producción". En general, intente escapar de la asignación de memoria dinámica en sistemas integrados.
drodri 01 de

22

Esta función se puede usar para separar una cadena en pedazos según el carácter separador.

String xval = getValue(myString, ':', 0);
String yval = getValue(myString, ':', 1);

Serial.println("Y:" + yval);
Serial.print("X:" + xval);

Convertir cadena a int

int xvalue = stringToNumber(xval);
int yvalue = stringToNumber(yval);

Este fragmento de código toma una cadena y la separa en función de un carácter dado y devuelve el elemento entre el carácter separador

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]) : "";
}

1
esa es una hermosa respuesta perfecta! muchas gracias !
Curnelious

11

Podría hacer algo como lo siguiente, pero tenga en cuenta varias cosas:

Si lo usa readStringUntil(), esperará hasta que reciba el carácter o los tiempos de espera. Por lo tanto, con su cadena actual, la última posición durará un poco más, ya que tiene que esperar. Puede agregar un final &para evitar este tiempo de espera. Puede verificar fácilmente este comportamiento en su monitor, intente enviar la cadena con y sin el extra &y verá tal retraso de tiempo de espera.

En realidad no es necesario el índice de servo, que sólo puede enviar su serie de posiciones, y obtener el índice de servo por la posición de valor en la cadena, algo así como: 90&80&180&. Si usa el índice de servo, tal vez desee verificarlo (convertir a int, y luego hacer coincidir el índice de bucle i) para asegurarse de que nada salió mal con su mensaje.

Debe verificar que la cadena de retorno de readStringUntilno esté vacía. Si la función expira, no recibió suficientes datos y, por lo tanto, cualquier intento de extraer sus intvalores producirá resultados extraños.

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);
        }
    }
}

Esto parece una muy buena solución, gracias. El ejemplo lo aclara perfectamente
ValrikRobot

¿Qué pasaría si tuviéramos un número indefinido de entradas de servo? en mi ejemplo había 3. Pero, ¿qué pasaría si a veces fuera más o menos? ¿Puede ofrecer alguna sugerencia para manejar este escenario
ValrikRobot

1
Claro: hay dos posibilidades. 1. Envíe primero el número de servos: 3: val1 & val2 & val3 &, lea dicho número antes de comenzar el ciclo. 2. Utilice un terminador diferente para indicar que no tiene más servos, repita hasta encontrarlo: val1 y val2 y val3 & #, por ejemplo.
drodri

Me alegra que esta solución te haya ayudado, @ValrikRobot, ¿podrías validar la respuesta si fue útil?
drodri

1
o simplemente puede eliminar el for, por lo que el código funcionará cada vez que envíe un comando.
Lesto


4

La solución más simple es usar sscanf () .

  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);

Esto da el siguiente resultado:

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

¡Salud!


No funciona para serial.read () ... ¿alguna idea de por qué? Me sale el siguiente error:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro

4

Ver ejemplo en: https://github.com/BenTommyE/Arduino_getStringPartByNr

// splitting a string and return the part nr index split by separator
String 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;
}

3
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;
        }
    }   
}

2

jfpoilpret proporcionó una excelente respuesta para analizar el comando en serie en Arduino. Sin embargo, Attiny85 no tiene serial bidireccional: se debe usar SoftwareSerial. Así es como se transfiere el mismo código para Attiny85

#include <SoftwareSerial.h>

// Calculate based on max input size expected for one command
#define INPUT_SIZE 30

// Initialize SoftwareSerial
SoftwareSerial 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, "&");
    }
  }
}

Esquemas Attiny85 para números de pin ingrese la descripción de la imagen aquí

Sketch compila en:

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.

Entonces hay mucho espacio y memoria para el resto del código


Cómo leer de serie en un ATtiny85 no es realmente parte de la pregunta.
gre_gor

Perdón por divergir de la pregunta, pero la comunidad y los recursos disponibles para Attiny son mucho más pequeños que para Arduino. Las personas como yo que buscan respuestas usan Arduinopalabras clave y, a veces, se encuentran en situaciones muy difíciles, ya que implementar el código Arduino en Attiny no siempre es trivial. Tuve que convertir el código original para que funcionara en Attiny, lo probé y decidió compartirlo
goodevil

Este sitio está en formato de preguntas y respuestas. Las respuestas deberían responder la pregunta. El tuyo solo agrega algo que no está relacionado con él.
gre_gor

1
char str[] = "1:90&2:80&3:180";     // test sample serial input from servo
int 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
}

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 value
printf ("%s\n",pch);
}
void loop(){}

-1

Aquí está el método Arduino para dividir una cadena como respuesta a la pregunta "¿Cómo dividir una cadena en una subcadena?" declarado como un duplicado de la presente pregunta.

El objetivo de la solución es analizar una serie de posiciones GPS registradas en un archivo de tarjeta SD . En lugar de recibir una Cadena Serial, la Cadena se lee del archivo.

La función es StringSplit()analizar una Cadena sLine = "1.12345,4.56789,hello"a 3 Cadenas sParams[0]="1.12345", sParams[1]="4.56789"y sParams[2]="hello".

  1. String sInput: las líneas de entrada a analizar,
  2. char cDelim: el carácter delimitador entre parámetros,
  3. String sParams[]: la matriz de salida de parámetros,
  4. int iMaxParams: el número máximo de parámetros,
  5. Salida int: el número de parámetros analizados,

La función se basa en String::indexOf()y String::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);
}

Y el uso es realmente simple:

String sParams[3];
int iCount, i;
String sLine;

// reading the line from file
sLine = readLine();
// parse only if exists
if (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("");
}