Wie teile ich eine eingehende Zeichenfolge auf?


51

Ich sende eine Liste von Servopositionen über die serielle Verbindung an den Arduino im folgenden Format

1:90&2:80&3:180

Welches würde analysiert werden als:

servoId : Position & servoId : Position & servoId : Position

Wie würde ich diese Werte aufteilen und in eine Ganzzahl konvertieren?


Ich habe Slave (Arduino Uno) senden Zeichenfolge über serielle 30; 12.4; 1 und 1 Master (Esp8266) empfangen Zeichenfolge Ich möchte in Master haben getrennte Daten wie 30 12.4 1 und speichern Sie es in Micro
SD-

Antworten:


72

Im Gegensatz zu anderen Antworten halte ich mich aus Stringfolgenden Gründen lieber fern :

  • Dynamische Speichernutzung (die schnell zu Heap-Fragmentierung und Speicherüberlastung führen kann )
  • ziemlich langsam aufgrund von Bau- / Zerstörungs- / Auftragsbetreibern

In einer eingebetteten Umgebung wie Arduino (selbst für ein Mega mit mehr SRAM) würde ich lieber Standard-C-Funktionen verwenden :

  • strchr(): Suche nach einem Zeichen in einer C-Zeichenfolge (dh char *)
  • strtok(): Teilt eine C-Zeichenfolge basierend auf einem Trennzeichen in Teilzeichenfolgen auf
  • atoi(): konvertiert einen C-String in einen int

Das würde zu dem folgenden Codebeispiel führen:

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

Der Vorteil hierbei ist, dass keine dynamische Speicherzuordnung stattfindet; Sie können sogar inputeine lokale Variable in einer Funktion deklarieren , die die Befehle liest und ausführt. Sobald die Funktion zurückgegeben wird, wird die von input(im Stapel) belegte Größe wiederhergestellt.


Hatte nicht an das Speicherproblem gedacht. das ist toll.
ValrikRobot

4
Ausgezeichnet. Meine Antwort war sehr "Arduino" -basiert und verwendete typische Arduino SDK-Funktionen, an die ein neuer Benutzer besser gewöhnt sein könnte. Diese Antwort sollte jedoch für "Produktions" -Systeme erfolgen. Versuchen Sie im Allgemeinen, sich der dynamischen Speicherzuweisung in eingebetteten Systemen zu entziehen.
Drodri

22

Diese Funktion kann verwendet werden, um eine Zeichenfolge basierend auf dem Trennzeichen in Teile zu trennen.

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

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

String in int konvertieren

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

Dieser Codeabschnitt nimmt eine Zeichenfolge und trennt sie basierend auf einem bestimmten Zeichen und gibt das Element zwischen den Trennzeichen zurück

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
Das ist eine schöne, perfekte Antwort! Danke vielmals !
Curnelious

11

Sie könnten etwas wie das Folgende tun, aber bitte berücksichtigen Sie einige Dinge:

Wenn Sie verwenden readStringUntil(), wird gewartet, bis das Zeichen oder die Zeitüberschreitungen empfangen werden. Mit Ihrer aktuellen Zeichenfolge dauert die letzte Position also etwas länger, da sie warten muss. Sie können ein Trailing hinzufügen &, um diese Zeitüberschreitung zu vermeiden. Sie können dieses Verhalten problemlos in Ihrem Monitor überprüfen, versuchen, den String mit und ohne Zusatz zu senden, &und es wird eine solche Zeitüberschreitungsverzögerung angezeigt.

Sie brauchen eigentlich nicht den Servo - Index, können Sie Ihre Reihe von Positionen nur senden, und den Servoindex durch die Wertposition in der Zeichenfolge bekommen, so etwas wie: 90&80&180&. Wenn Sie den Servoindex verwenden, möchten Sie ihn möglicherweise überprüfen (in intden Schleifenindex i konvertieren und ihn dann abgleichen), um sicherzustellen, dass mit Ihrer Nachricht nichts schief gelaufen ist.

Sie müssen sicherstellen, dass der zurückgegebene String von readStringUntilnicht leer ist. Wenn bei der Funktion eine Zeitüberschreitung auftritt, haben Sie nicht genügend Daten erhalten. Daher führt jeder Versuch, Ihre intWerte zu extrahieren , zu merkwürdigen Ergebnissen.

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

Dies scheint eine sehr gute Lösung zu sein, danke. Das Beispiel macht es perfekt
klar

Was wäre, wenn wir eine undefinierte Anzahl von Servoeingängen hätten? In meinem Beispiel war es 3. Aber was, wenn es manchmal mehr oder weniger war? Können Sie einen Vorschlag zur Behandlung eines solchen Szenarios machen
ValrikRobot

1
Klar: Es gibt zwei Möglichkeiten. 1. Senden Sie zuerst die Anzahl der Servos: 3: val1 & val2 & val3 &, lesen Sie diese Nummer, bevor Sie die Schleife starten. 2. Verwenden Sie einen anderen Terminator, um anzuzeigen, dass Sie keine Servos mehr haben. Schleifen Sie, bis Sie ihn finden: zum Beispiel val1 & val2 & val3 & #.
Drodri

Ich bin froh, dass Ihnen diese Lösung geholfen hat, @ValrikRobot. Können Sie bitte die Antwort validieren, wenn sie nützlich ist?
Drodri

1
oder Sie können einfach das for entfernen, und der Code funktioniert immer dann, wenn Sie einen Befehl senden.
Lesto


4

Die einfachste Lösung ist die Verwendung von 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);

Dies ergibt folgende Ausgabe:

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

Prost!


Es funktioniert nicht für serial.read () ... eine Idee warum? Ich erhalte die folgende Fehlermeldung:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro

4

Siehe Beispiel unter: 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 lieferte eine großartige Antwort für das Parsen von seriellen Befehlen auf Arduino. Attiny85 hat jedoch keine bidirektionale Seriennummer - SoftwareSerial muss verwendet werden. So portieren Sie denselben Code für 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, "&");
    }
  }
}

Attiny85-Schaltpläne für PIN-Nummern Bildbeschreibung hier eingeben

Skizze kompiliert 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.

Es gibt also viel Platz und Speicher für den Rest des Codes


Wie man auf einem ATtiny85 aus der Serie liest, ist nicht wirklich Teil der Frage.
gre_gor

Es tut uns leid, dass ich von der Frage abgewichen bin, aber die Community und die Ressourcen, die für Attiny verfügbar sind, sind viel kleiner als für Arduino. Leute wie ich, die nach Antworten suchen, verwenden ArduinoSchlüsselwörter und geraten manchmal in sehr schwierige Situationen, da das Implementieren von Arduino-Code auf Attiny nicht immer trivial ist. Musste den Originalcode konvertieren, um mit Attiny zu arbeiten, testete ihn und entschied sich, ihn zu teilen
goodevil

Diese Seite ist im Q & A-Format. Antworten sollten die Frage beantworten. Ihr fügt nur etwas hinzu, das nichts damit zu tun hat.
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

Hier ist die Arduino- Methode, um eine Zeichenfolge als Antwort auf die Frage "Wie wird eine Zeichenfolge in Teilzeichenfolgen aufgeteilt?" als Duplikat der vorliegenden Frage deklariert.

Das Ziel der Lösung besteht darin, eine Reihe von GPS- Positionen zu analysieren, die in einer SD-Kartendatei gespeichert sind. Anstatt einen String empfangen zu lassen Serial, wird der String aus einer Datei gelesen.

Die Funktion StringSplit()parst einen String sLine = "1.12345,4.56789,hello"in 3 Strings sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: die zu analysierenden Eingabezeilen,
  2. char cDelim: das Trennzeichen zwischen den Parametern,
  3. String sParams[]: das Ausgabearray von Parametern,
  4. int iMaxParams: die maximale Anzahl von Parametern,
  5. Ausgabe int: die Anzahl der analysierten Parameter,

Die Funktion basiert auf String::indexOf()und 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);
}

Und die Bedienung ist denkbar einfach:

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("");
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.