Comment diviser une chaîne entrante?

51

J'envoie une liste de positions d'asservissement via la connexion série à l'arduino au format suivant

1:90&2:80&3:180

Ce qui serait analysé comme:

servoId : Position & servoId : Position & servoId : Position

Comment pourrais-je fractionner ces valeurs et les convertir en un entier?

ValrikRobot
la source
J'ai esclave (Arduino Uno) envoyer chaîne via série 30; 12.4; 1 et 1 maître (esp8266) recive chaîne que je veux en maître ont séparé des données comme 30 12.4 1 et enregistrez-le dans une carte micro sd
majid mahmoudi

Réponses:

72

Contrairement à d’autres réponses, je préférerais rester à l’écart Stringpour les raisons suivantes:

  • utilisation dynamique de la mémoire (pouvant conduire rapidement à la fragmentation du tas et à l' épuisement de la mémoire )
  • assez lent en raison des opérateurs de construction / destruction / affectation

Dans un environnement embarqué tel qu'Arduino (même pour un Mega disposant de davantage de SRAM), je préférerais utiliser les fonctions C standard :

  • strchr(): rechercher un caractère dans une chaîne C (ie char *)
  • strtok(): divise une chaîne C en sous-chaînes, en fonction d'un caractère séparateur
  • atoi(): convertit une chaîne C en un int

Cela conduirait à l'exemple de code suivant:

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

L'avantage ici est qu'aucune allocation de mémoire dynamique n'a lieu; vous pouvez même déclarer inputune variable locale dans une fonction qui lirait les commandes et les exécuterait; une fois que la fonction est retournée, la taille occupée par input(dans la pile) est récupérée.

jfpoilpret
la source
Je n'avais pas pensé au problème de mémoire. c'est bien.
ValrikRobot
4
Excellent. Ma réponse était très "arduino" et utilisait les fonctions typiques du SDK arduino auxquelles un nouvel utilisateur pourrait être plus habitué, mais cette réponse est ce qu'il convient de faire pour les systèmes "de production". En règle générale, essayez d'échapper à l'allocation de mémoire dynamique dans les systèmes intégrés.
drodri
22

Cette fonction peut être utilisée pour séparer une chaîne en morceaux en fonction du caractère de séparation.

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

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

Convertir une chaîne en int

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

Ce morceau de code prend une chaîne et la sépare en fonction d'un caractère donné, puis renvoie l'élément entre le caractère séparateur.

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]) : "";
}
Odis Harkins
la source
1
c'est une belle réponse parfaite! Merci beaucoup !
Curnelious
11

Vous pouvez faire quelque chose comme ce qui suit, mais veuillez prendre en compte plusieurs choses:

Si vous utilisez readStringUntil(), il attendra de recevoir le caractère ou les délais. Ainsi, avec votre chaîne actuelle, la dernière position durera un peu plus longtemps, car elle doit attendre. Vous pouvez ajouter une fin &pour éviter ce délai. Vous pouvez facilement vérifier ce comportement sur votre moniteur, essayer d’envoyer la chaîne avec et sans l’extra &et vous verrez un tel délai.

En fait , vous n'avez pas besoin de l'indice servo, vous pouvez envoyer votre chaîne de positions et obtenir l'indice servo par la position de valeur dans la chaîne, quelque chose comme: 90&80&180&. Si vous utilisez le servo-index, vous voudrez peut-être le vérifier (convertir en int, puis faire correspondre l'index de boucle i) pour vous assurer que votre message n'a pas mal tourné.

Vous devez vérifier que la chaîne renvoyée par readStringUntiln'est pas vide. Si la fonction a expiré, vous n'avez pas reçu suffisamment de données et toute tentative d'extraction de vos intvaleurs produira des résultats étranges.

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);
        }
    }
}
drodri
la source
Cela semble être une très bonne solution merci. L'exemple clarifie parfaitement
ValrikRobot
Et si nous avions un nombre indéfini d'entrées de servo? dans mon exemple, il y en avait 3. Mais si, parfois, c'était plus ou moins. Pouvez-vous offrir une suggestion pour gérer un tel scénario
ValrikRobot
1
Bien sûr: il y a deux possibilités. 1. Envoyez d'abord le nombre de servos: 3: val1 & val2 & val3 &, lisez-le avant de lancer la boucle. 2. Utilisez un terminateur différent pour indiquer que vous n'avez plus de servos. Bouclez jusqu'à ce que vous le trouviez: val1 & val2 & val3 & #, par exemple.
drodri
Heureuse que cette solution vous ait aidé, @ ValrikRobot, pourriez-vous valider la réponse si elle était utile?
drodri
1
ou vous pouvez simplement supprimer le pour et le code fonctionnera dès que vous enverrez une commande.
Lesto
4

La solution la plus simple consiste à utiliser 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);

Cela donne la sortie suivante:

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

À votre santé!

Mikael Patel
la source
Cela ne fonctionne pas pour serial.read () ... aucune idée pourquoi? J'ai l'erreur suivante:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro
4

Voir l'exemple à: 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;
}
Ben-Tommy Eriksen
la source
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;
        }
    }   
}
Jam Ville
la source
2

jfpoilpret a fourni une excellente réponse pour l'analyse de la commande série sur Arduino. Cependant, Attiny85 n’a pas de série bidirectionnelle - SoftwareSerial doit être utilisé. C’est ainsi que vous portez le même code pour 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, "&");
    }
  }
}

Schémas Attiny85 pour les numéros de broches entrez la description de l'image ici

Sketch compile 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.

Donc, il y a beaucoup d'espace et de mémoire pour le reste du code

bon mal
la source
Comment lire un feuilleton sur un ATtiny85 ne fait pas vraiment partie de la question.
gre_gor
Désolé de vous écarter de la question, mais la communauté et les ressources disponibles pour Attiny sont beaucoup plus petites que pour Arduino. Les gens comme moi qui cherchent des réponses utilisent des Arduinomots clés et se retrouvent parfois dans des situations très délicates, car implémenter du code Arduino sur Attiny n'est pas toujours trivial. Devait convertir le code original pour pouvoir travailler sur Attiny, l’essayer et décider de le partager
goodevil
Ce site est au format Q & R. Les réponses devraient répondre à la question. Le vôtre ajoute simplement quelque chose qui n’y est pas lié.
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
}
pakled
la source
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(){}
Vitalicus
la source
-1

Voici la méthode Arduino pour diviser une chaîne en réponse à la question "Comment diviser une chaîne en sous-chaîne?" déclarée comme un duplicata de la présente question.

L'objectif de la solution est d'analyser une série de positions GPS enregistrées dans un fichier de carte SD . Au lieu d'avoir une chaîne reçue de Serial, la chaîne est lue à partir d'un fichier.

La fonction consiste à StringSplit()analyser une chaîne sLine = "1.12345,4.56789,hello"en 3 chaînes sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: les lignes d’entrée à analyser,
  2. char cDelim: le caractère séparateur entre les paramètres,
  3. String sParams[]: le tableau de sortie en sortie de paramètres,
  4. int iMaxParams: le nombre maximum de paramètres,
  5. Sortie int: le nombre de paramètres analysés,

La fonction est basée sur String::indexOf()et 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);
}

Et l'utilisation est vraiment 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("");
}
J. Piquard
la source