Come divido una stringa in arrivo?


51

Sto inviando un elenco di posizioni servo tramite la connessione seriale all'Arduino nel seguente formato

1:90&2:80&3:180

Che sarebbe analizzato come:

servoId : Position & servoId : Position & servoId : Position

Come dividerei questi valori e li convertirò in un numero intero?


ho uno slave (arduino uno) che invia la stringa tramite seriale 30; 12.4; 1 e 1 master (esp8266) stringa di ricezione che voglio nel master hanno dati separati come 30 12.4 1 e salvarli in micro sd card
majid mahmoudi

Risposte:


72

Contrariamente ad altre risposte, preferirei stare lontano Stringper i seguenti motivi:

  • utilizzo dinamico della memoria (che può portare rapidamente alla frammentazione dell'heap e all'esaurimento della memoria )
  • abbastanza lento a causa di operatori di costruzione / distruzione / incarico

In un ambiente embedded come Arduino (anche per un Mega che ha più SRAM), preferirei usare le funzioni C standard :

  • strchr(): cerca un carattere in una stringa C (es. char *)
  • strtok(): divide una stringa C in sottostringhe, in base a un carattere separatore
  • atoi(): converte una stringa C in un int

Ciò porterebbe al seguente esempio di codice:

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

Il vantaggio qui è che non ha luogo alcuna allocazione dinamica della memoria; puoi persino dichiarare inputcome una variabile locale all'interno di una funzione che leggerebbe i comandi ed eseguirli; una volta restituita la funzione, inputviene ripristinata la dimensione occupata da (nello stack).


Non avevo pensato al problema di memoria. è fantastico
ValrikRobot

4
Eccellente. La mia risposta era molto "arduino" e utilizzava le tipiche funzioni SDK di arduino alle quali un nuovo utente poteva essere più abituato, ma questa risposta è ciò che dovrebbe essere fatto per i sistemi di "produzione". In generale, prova a sfuggire all'allocazione dinamica della memoria nei sistemi incorporati.
Drodri,

22

Questa funzione può essere utilizzata per separare una stringa in pezzi in base al carattere di separazione.

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

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

Converti stringa in int

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

Questo blocco di codice prende una stringa e la separa in base a un determinato carattere e restituisce l'elemento tra il carattere di separazione

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
questa è una bellissima risposta perfetta! molte grazie !
Curnelious il

11

Potresti fare qualcosa di simile al seguente, ma tieni in considerazione diverse cose:

Se lo usi readStringUntil(), attenderà fino a quando non riceverà il personaggio o i timeout. Pertanto, con la stringa corrente, l'ultima posizione durerà un po 'più a lungo, poiché deve attendere. È possibile aggiungere un finale &per evitare questo periodo di tempo. Puoi facilmente controllare questo comportamento sul tuo monitor, provare a inviare la stringa con e senza extra &e vedrai tale ritardo di timeout.

In realtà non è necessario l'indice servo, si può semplicemente inviare la stringa di posizioni, e ottenere l'indice servo dalla posizione del valore nella stringa, qualcosa come: 90&80&180&. Se si utilizza l'indice di servo, forse si desidera controllarlo (convertire in int, quindi abbinare l'indice di ciclo i) per assicurarsi che nulla sia andato storto con il messaggio.

Devi controllare che la stringa di ritorno da readStringUntilnon sia vuota. Se la funzione scade, non hai ricevuto abbastanza dati e quindi qualsiasi tentativo di estrarre i tuoi intvalori produrrà strani risultati.

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

Questa sembra un'ottima soluzione, grazie. L'esempio lo chiarisce perfettamente
ValrikRobot

E se avessimo un numero indefinito di ingressi servo? nel mio esempio ce n'erano 3. Ma cosa succede se a volte fosse più o meno. Puoi offrire qualche suggerimento per gestire un simile scenario
ValrikRobot

1
Sicuro: ci sono due possibilità. 1. Invia prima il numero di servi: 3: val1 & val2 & val3 &, leggi tale numero prima di iniziare il loop. 2. Usa un terminatore diverso per indicare che non hai più servi, continua finché non lo trovi: val1 & val2 & val3 & #, per esempio.
Drodri,

Sono contento che questa soluzione ti abbia aiutato, @ValrikRobot, potresti per favore confermare la risposta se fosse utile?
Drodri,

1
oppure puoi semplicemente rimuovere il for, quindi il codice funzionerà ogni volta che invii un comando.
Lesto,


4

La soluzione più semplice è usare 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);

Questo dà il seguente output:

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

Saluti!


Non funziona per serial.read () ... hai idea del perché? Ottengo il seguente errore:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro,

4

Vedi esempio su: 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 ha fornito un'ottima risposta per l'analisi del comando seriale su Arduino. Tuttavia Attiny85 non ha un seriale bidirezionale: è necessario utilizzare SoftwareSerial. Questo è il modo in cui porti lo stesso codice per 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, "&");
    }
  }
}

Schemi Attiny85 per numeri di pin inserisci qui la descrizione dell'immagine

Lo schizzo viene compilato in:

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.

Quindi c'è molto spazio e memoria per il resto del codice


Come leggere da seriale su un ATtiny85 non fa davvero parte della domanda.
gre_gor,

Ci scusiamo per il divergere dalla domanda, ma la community e le risorse disponibili per Attiny sono molto più piccole rispetto ad Arduino. Le persone come me in cerca di risposte usano Arduinoparole chiave e talvolta si imbattono in situazioni molto difficili poiché l'implementazione del codice Arduino su Attiny non è sempre banale. Ho dovuto convertire il codice originale per funzionare su Attiny, testarlo funzionando e ho deciso di condividerlo
goodevil

Questo sito è in formato Domande e risposte. Le risposte dovrebbero rispondere alla domanda. Il tuo aggiunge solo qualcosa che non è correlato ad esso.
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

Ecco il metodo Arduino per dividere una stringa come risposta alla domanda "Come dividere una stringa in sottostringa?" dichiarato come duplicato della presente domanda.

L'obiettivo della soluzione è analizzare una serie di posizioni GPS registrate in un file della scheda SD . Invece di ricevere una stringa Serial, la stringa viene letta dal file.

La funzione è StringSplit()analizzare una stringa sLine = "1.12345,4.56789,hello"in 3 stringhe sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: le righe di input da analizzare,
  2. char cDelim: il carattere delimitatore tra i parametri,
  3. String sParams[]: l'array di output dei parametri,
  4. int iMaxParams: il numero massimo di parametri,
  5. Output int: il numero di parametri analizzati,

La funzione si basa su String::indexOf()e 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);
}

E l'uso è davvero semplice:

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("");
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.