Gelen bir dizgeyi nasıl bölerim?


51

Arduino'ya seri bağlantı ile aşağıdaki formatta bir servo pozisyonları listesi gönderiyorum

1:90&2:80&3:180

Hangisi ayrıştırılır:

servoId : Position & servoId : Position & servoId : Position

Bu değerleri nasıl bölüp bir tam sayıya dönüştürebilirim?


Ben köle (arduino uno) seri 30 üzerinden dize göndermek var; 12.4; 1 ve 1 master (esp8266) recive dize master istiyorum istiyorum 12 12.4 1 gibi veri ayrılmış ve micro sd kart
majid mahmoudi

Yanıtlar:


72

Aksine diğer cevaplara göre, Stringaşağıdaki sebeplerden uzak durmayı tercih ederim :

  • dinamik bellek kullanımı (hızlı bir şekilde yığın parçalanmasına ve bellek tükenmesine yol açabilir )
  • inşaat / imha / atama operatörleri nedeniyle oldukça yavaş

Arduino gibi gömülü bir ortamda (daha fazla SRAM içeren bir Mega için bile) standart C işlevlerini kullanmayı tercih ederim :

  • strchr(): bir C dizesinde bir karakter aramak (yani char *)
  • strtok(): bir C dizesini ayırıcı karaktere dayalı olarak alt dizgelere böler
  • atoi(): bir C dizesini bir dizgeye dönüştürür. int

Bu, aşağıdaki kod örneğine yol açar:

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

Buradaki avantaj, dinamik bir bellek tahsisinin gerçekleşmemesidir; inputkomutları okuyacak ve bunları çalıştıracak bir fonksiyonun içinde yerel bir değişken olarak bile tanımlayabilirsiniz ; İşlev döndürüldüğünde, işgal edilen input(istifte) boyut geri kazanılır.


Hafıza sorununu düşünmemiştim. bu harika.
ValrikRobot

4
Mükemmel. Cevabım çok "arduino" temelli ve yeni bir kullanıcının daha fazla kullanabileceği tipik arduino SDK işlevlerini kullanıyordu, ancak bu cevap "üretim" sistemleri için yapılması gerekenler. Genel olarak, yerleşik sistemlerde dinamik bellek ayırma işleminden kaçmaya çalışın.
drodri

22

Bu fonksiyon, ayırma karakterinin ne olduğuna bağlı olarak bir ipi parçalara ayırmak için kullanılabilir.

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

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

String'i int'ye dönüştür

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

Bu kod yığınını bir dizge alır ve verilen bir karaktere göre ayırır ve ayıran karakter arasındaki öğeyi döndürür.

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
bu güzel, mükemmel bir cevap! çok teşekkürler !
Curnelious

11

Aşağıdaki gibi bir şey yapabilirsiniz, ancak lütfen birkaç şeyi göz önünde bulundurun:

Eğer kullanırsanız readStringUntil()o karakter veya zaman aşımına alana kadar, bu bekleyecektir. Bu nedenle, mevcut dizginizle, son konum beklemesi gerektiği gibi biraz daha uzun sürecektir. Bu zaman &aşımını önlemek için bir iz ekleyebilirsiniz . Bu davranışı monitörünüzde kolayca kontrol edebilir, dizgiyi ekli ve eksiz olarak göndermeyi deneyebilirsiniz &ve böyle bir zaman aşımı gecikmesi göreceksiniz.

Aslında sadece pozisyonların sizin dize gönderebilir, servo endeksi gerekir ve benzeri dizede şey değer konumuna göre servo endeksi alamadım: 90&80&180&. Eğer servo endeks kullanıyorsanız, intmesajınızdaki hiçbir şeyin ters gitmediğinden emin olmak için belki kontrol etmelisiniz (dönüştürün ve sonra döngü indeksi i ile eşleşin).

Dönen dizginin readStringUntilboş olmadığını kontrol etmeniz gerekir . İşlev zaman aşımına uğrarsa, yeterli veri alamazsınız ve bu nedenle intdeğerlerinizi çıkarma girişimi garip sonuçlar doğurur.

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

Bu çok iyi bir çözüm gibi görünüyor teşekkür ederim. Örnek mükemmel şekilde
temizliyor

Tanımlanmamış bir servo girişi olsaydı ne olurdu? Benim örneğimde 3 vardı. Ama ya bazen daha fazla ya da daha az olsaydı. Böyle bir senaryoyu ele almak için herhangi bir öneri sunabilir misiniz
ValrikRobot

1
Tabii: İki olasılık var. 1. Önce servo sayısını gönderin: 3: val1 & val2 & val3 &, döngüyü başlatmadan önce bu sayıyı okuyun. 2. Başka servo bulunmadığını belirtmek için farklı bir sonlandırıcı kullanın, bulana kadar döngüleyin: val1 & val2 & val3 & #, örneğin.
drodri

Bu çözümün size yardımcı olmasına sevindim, @ValrikRobot, lütfen yararlı olsaydı cevabı doğrulayabilir misiniz?
drodri

1
ya da sadece for 'u kaldırabilirsiniz ve böylece bir komut gönderdiğinizde kod sadece çalışacaktır.
Lesto


4

En basit çözüm sscanf () kullanmaktır .

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

Bu, aşağıdaki çıktıyı verir:

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

Şerefe!


Serial.read () için çalışmıyor ... herhangi bir fikir neden? Aşağıdaki hatayı alıyorum:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro

4

Bkz. Örnek: 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, Arduino'da seri komutun ayrıştırılması için harika bir cevap verdi. Ancak Attiny85 iki yönlü seri içermiyor - SoftwareSerial kullanılmalı. Attiny85 için aynı kodu bu şekilde yerleştirirsiniz

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

Pin numaraları için Attiny85 şemaları görüntü tanımını buraya girin

Kroki şunları derler:

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.

Yani kodun geri kalanı için bol miktarda alan ve bellek var.


Bir ATtiny85'de seriden nasıl okunacağının sorusu gerçekten değildir.
gre_gor

Sorudan ayrıldığım için üzgünüm, ancak Attiny için mevcut olan topluluk ve kaynaklar Arduino'dan çok daha küçük. Benim gibi cevapları arayanlar Arduinoanahtar kelimeyi kullanır ve bazen çok zor durumlara girerler, çünkü Arduino kodunu Attiny'ye uygulamak her zaman önemsiz değildir. Attiny'de çalışmak için orijinal kodu dönüştürmek zorunda kaldı, çalışmasını test etti ve paylaşmaya karar verdi
goodevil

Bu site soru-cevap formatındadır. Cevaplar soruyu cevaplamalıdır. Sizinki sadece onunla ilgisi olmayan bir şey ekler.
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

Burada, "Bir dize alt dize nasıl bölünür?" Sorusunun cevabı olarak bir dize bölmek için Arduino yöntemi verilmiştir. Mevcut sorunun bir kopyası olarak ilan edildi.

Çözümün amacı, bir SD kart dosyasına kaydedilen bir dizi GPS konumunu ayrıştırmaktır . Bir String almak yerine , String dosyadan okunur.Serial

İşlev StringSplit()bir Dize sLine = "1.12345,4.56789,hello"3 Dizge olarak ayrıştırır sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: ayrıştırılacak girdi satırları,
  2. char cDelim: parametreler arasındaki sınırlayıcı karakter,
  3. String sParams[]: parametrelerin çıktı dizisi,
  4. int iMaxParams: maksimum parametre sayısı,
  5. Çıktı int: ayrıştırılan parametrelerin sayısı,

İşlev şunlara dayanır String::indexOf()ve 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);
}

Ve kullanımı gerçekten basittir:

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("");
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.