Cum împart un șir de intrare?

Trimit o listă de poziții servo prin conexiunea serială către arduino în formatul următor

1:90&2:80&3:180 

Care ar fi analizat ca:

servoId : Position & servoId : Position & servoId : Position

Cum aș împărți aceste valori și le voi converti în un număr întreg?

Comentarii

  • Am slave (arduino uno) send string via serial 30; 12.4; 1 and 1 master (esp8266) recive string vreau ca master să aibă date separate, cum ar fi 30 12.4 1 și să le salvez în card micro SD

Răspunde

Contrar la alte răspunsuri, aș prefera să stau departe de String din următoarele motive:

  • utilizarea dinamică a memoriei (care poate duce rapid la fragmentarea heap și epuizarea memoriei )
  • destul de lent din cauza constr operatori de acțiune / distrugere / atribuire

Într-un mediu încorporat precum Arduino (chiar și pentru un Mega care are mai multe SRAM), aș prefera să folosesc funcții C standard :

  • strchr() : căutați un caracter într-un șir C (de ex char *)
  • strtok() : despărțiri un șir C în șiruri de caractere, bazat pe un caracter separator
  • atoi() : convertește un C șir la un int

Acest lucru ar duce la următorul exemplu de cod:

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

Avantajul este că nu are loc nicio alocare dinamică a memoriei; puteți chiar să declarați input ca o variabilă locală în interiorul unei funcții care ar citi comenzile și le va executa; odată ce funcția este returnată, dimensiunea ocupată de input (în stivă) este recuperată.

Comentarii

  • Nu se gândise la problema memoriei ‘ este minunat.
  • Excelent. Răspunsul meu a fost foarte ” bazat pe arduino ” și folosind funcții tipice SDK arduino la care un utilizator nou ar putea fi mai obișnuit, dar acest răspuns este ce ar trebui făcut pentru ” producție ” sisteme. În general, încercați să scăpați de alocarea dinamică a memoriei în sistemele încorporate.

Răspuns

Această funcție poate fi utilizată pentru a separa un șir în bucăți pe baza caracterului de separare.

String xval = getValue(myString, ":", 0); String yval = getValue(myString, ":", 1); Serial.println("Y:" + yval); Serial.print("X:" + xval); 

Conversia șirului în int

int xvalue = xvalue.toInt(xval); int yvalue = yvalue.toInt(yval); 

Această bucată de cod ia un șir și îl separă pe baza unui caracter dat și returnează Elementul dintre caracterul de separare

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

Comentarii

  • iată un răspuns perfect frumos! mulțumesc mult!
  • Ar trebui să o alegeți pe aceasta. (Thumbs up)
  • elegant! funcționează perfect pentru a trimite perechi de valori cheie

Răspuns

Ați putea face ceva de genul următor, dar vă rugăm să luați în considerare cont mai multe lucruri:

Dacă utilizați readStringUntil(), acesta va aștepta până când va primi caracterul sau expirările. Astfel, cu șirul curent, ultima poziție va dura puțin mai mult, deoarece trebuie să aștepte. Puteți adăuga un & final pentru a evita acest timeout. Puteți verifica cu ușurință acest comportament pe monitor, puteți încerca să trimiteți șirul cu și fără & suplimentar și veți vedea o astfel de întârziere.

De fapt, faceți nu aveți nevoie de indexul servo, puteți doar să trimiteți șirul de poziții și să obțineți indexul servo după poziția valorii din șir, ceva de genul: 90&80&180&. Dacă utilizați indexul servo, poate doriți să-l verificați (convertiți la int și apoi potriviți indexul buclei i) pentru a vă asigura că nimic nu a mers în neregulă cu mesajul dvs.

Trebuie să verificați dacă șirul returnat de la readStringUntil nu este gol. Dacă expirarea funcției, nu ați primit suficiente date și, prin urmare, orice încercare de a extrage valorile int va produce rezultate ciudate.

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

Comentarii

  • Vă pare o soluție foarte bună, vă mulțumim. Exemplul clarifică perfect
  • Ce se întâmplă dacă am avea un număr nedefinit de intrări servo? în exemplul meu a existat 3. Dar dacă uneori a fost mai mult sau mai puțin. Puteți oferi vreo sugestie pentru gestionarea unui astfel de scenariu
  • Sigur: există două posibilități. 1 .Trimiteți mai întâi numărul de servere: 3: val1 & val2 & val3 &, citiți un astfel de număr înainte de a începe bucla. 2. Utilizați un alt terminator pentru a indica că nu mai aveți servere, buclați până când îl găsiți: val1 & val2 & val3 & #, de exemplu.
  • Mă bucur că această soluție v-a ajutat, @ValrikRobot, ați putea valida răspunsul dacă a fost util?
  • sau dvs. poate elimina doar for, astfel încât codul să funcționeze oricând trimiteți o comandă.

Răspuns

Puteți utiliza Stream.readStringUntil(terminator) trecând un terminator diferit pentru fiecare parte.

Pe fiecare parte apoi apelați String.toInt

Răspuns

Soluția cea mai simplă este de a utiliza 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); 

Acest lucru oferă următorul rezultat:

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

Noroc!

Comentarii

  • Nu funcționează pentru serial.read () … orice i dea de ce? Primesc următoarea eroare: invalid conversion from 'int' to 'char*' [-fpermissive]

Răspuns

Vedeți exemplu la: 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; } 

Răspuns

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

Răspuns

jfpoilpret a oferit un răspuns excelent pentru analizarea comenzii seriale pe Arduino. Totuși Attiny85 nu are serial bidirecțional – SoftwareSerial trebuie utilizat. Acesta este modul în care portați același cod pentru 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, "&"); } } }  

Schemele Attiny85 pentru numerele de pin introduceți descrierea imaginii aici

Sketch se compilează în:

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. 

Deci, există o mulțime de spațiu și memorie pentru restul de cod

Comentarii

  • Cum se citește din serial pe un ATtiny85 nu este cu adevărat o parte a întrebării.
  • Ne pare rău pentru divergând de la întrebare, dar comunitatea și resursele disponibile pentru Attiny sunt mult mai mici decât pentru Arduino. Oamenii ca mine care caută răspunsuri folosesc cuvântul cheie Arduino și uneori intră în situații foarte dificile ca implementare a codului Arduino pe Attiny nu este întotdeauna banal. A trebuit să convertească codul original pentru a lucra pe Attiny, l-a testat funcționând și a decis să facă sha re it
  • Acest site este în format Q & A. Răspunsurile ar trebui să răspundă la întrebare. Al tău adaugă doar ceva care ‘ nu are legătură cu acesta.

Răspunde

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 } 

Răspuns

Iată Arduino metodă pentru a împărți un șir ca răspuns la întrebarea „Cum se împarte un șir în subșir?” declarat ca un duplicat al prezentei întrebări.

Obiectivul soluției este de a analiza o serie de GPS poziții conectate la un fișier card SD . În loc să aibă un șir primit de la Serial, șirul este citit din fișier.

Funcția este StringSplit() parse un șir sLine = "1.12345,4.56789,hello" la 3 șiruri sParams[0]="1.12345", sParams[1]="4.56789" & sParams[2]="hello".

  1. String sInput: liniile de intrare care trebuie analizate,
  2. char cDelim: caracterul delimitator dintre parametri,
  3. String sParams[]: matricea de ieșire a parametrilor,
  4. int iMaxParams: numărul maxim de parametri,
  5. ieșire int: numărul de analizate parametri,

Funcția se bazează pe String::indexOf() și 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); } 

Și utilizarea este foarte simplă:

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

Răspunde

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(){} 

Răspuns

Nu este un răspuns la întrebarea dvs., dar poate fi util pentru cineva. Dacă șirul dvs. are un prefix specific, puteți folosi pur și simplu startsWith și substring. De exemplu,

void loop () if(Serial.available()){ command = Serial.readStringUntil("\n"); Serial.println("Received command: " + command); if(command.startsWith("height")) { Serial.println(command.substring(7)); } } } 

Și apoi trimiteți height 10 ce va extrage 10 .

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *