Bagaimana cara membagi string yang masuk?

51

Saya mengirim daftar posisi servo melalui koneksi serial ke Arduino dalam format berikut

1:90&2:80&3:180

Yang akan diuraikan sebagai:

servoId : Position & servoId : Position & servoId : Position

Bagaimana saya membagi nilai-nilai ini, dan mengubahnya menjadi integer?

ValrikRobot
sumber
saya punya budak (Arduino uno) mengirim string yang melalui serial 30; 12,4; 1 dan 1 master (esp8266) string recive saya ingin di master telah dipisahkan Data seperti 30 12.4 1 dan simpan di kartu sd mikro
majid Mahmoudi

Jawaban:

72

Berlawanan dengan jawaban lain, saya lebih suka menjauh Stringkarena alasan berikut:

  • penggunaan memori dinamis (yang dapat dengan cepat menyebabkan tumpukan fragmentasi dan kehabisan memori )
  • cukup lambat karena operator konstruksi / penghancuran / penugasan

Dalam lingkungan tertanam seperti Arduino (bahkan untuk Mega yang memiliki lebih banyak SRAM), saya lebih suka menggunakan fungsi C standar :

  • strchr(): mencari karakter dalam string C (yaitu char *)
  • strtok(): membagi string C menjadi substring, berdasarkan karakter pemisah
  • atoi(): mengonversi string C menjadi int

Itu akan mengarah pada contoh kode berikut:

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

Keuntungannya di sini adalah tidak ada alokasi memori dinamis yang terjadi; Anda bahkan dapat mendeklarasikan inputsebagai variabel lokal di dalam fungsi yang akan membaca perintah dan menjalankannya; setelah fungsi dikembalikan ukuran yang ditempati oleh input(dalam tumpukan) dipulihkan.

jfpoilpret
sumber
Tidak memikirkan masalah memori. ini bagus.
ValrikRobot
4
Luar biasa. Jawaban saya sangat berdasarkan "arduino" dan menggunakan fungsi SDK arduino khas yang pengguna baru dapat lebih terbiasa, tetapi jawaban ini adalah apa yang harus dilakukan untuk sistem "produksi". Secara umum, cobalah untuk melarikan diri dari alokasi memori dinamis dalam sistem tertanam.
drodri
22

Fungsi ini dapat digunakan untuk memisahkan string menjadi potongan-potongan berdasarkan apa karakter pemisahnya.

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

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

Konversi String ke int

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

Potongan kode ini mengambil string dan memisahkannya berdasarkan karakter yang diberikan dan mengembalikan item di antara karakter yang memisahkan

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
sumber
1
itu jawaban sempurna yang indah! Terima kasih banyak !
Curnelious
11

Anda dapat melakukan sesuatu seperti yang berikut ini, tetapi harap pertimbangkan beberapa hal:

Jika Anda menggunakan readStringUntil(), itu akan menunggu sampai menerima karakter atau batas waktu. Jadi, dengan string Anda saat ini, posisi terakhir akan bertahan sedikit lebih lama, karena harus menunggu. Anda dapat menambahkan trailing &untuk menghindari batas waktu ini. Anda dapat dengan mudah memeriksa perilaku ini di monitor Anda, cobalah untuk mengirim string dengan dan tanpa ekstra &dan Anda akan melihat penundaan batas waktu tersebut.

Anda benar-benar tidak perlu indeks servo, Anda hanya dapat mengirim string Anda dari posisi, dan indeks servo dengan posisi nilai dalam string, seperti: 90&80&180&. Jika Anda menggunakan indeks servo, mungkin Anda ingin memeriksanya (konversikan ke int, dan kemudian cocok dengan indeks loop i) untuk memastikan bahwa tidak ada yang salah dengan pesan Anda.

Anda harus memeriksa bahwa string yang kembali dari readStringUntiltidak kosong. Jika fungsi timeout, Anda tidak menerima data yang cukup, dan dengan demikian setiap upaya untuk mengekstraksi intnilai Anda akan menghasilkan hasil yang aneh.

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
sumber
Ini sepertinya solusi yang sangat bagus terima kasih. Contoh ini membersihkannya dengan sempurna
ValrikRobot
Bagaimana jika kita memiliki jumlah input servo yang tidak ditentukan? dalam contoh saya ada 3. Tapi bagaimana jika kadang-kadang lebih, atau kurang. Bisakah Anda menawarkan saran untuk menangani skenario seperti itu
ValrikRobot
1
Tentu: Ada dua kemungkinan. 1. Kirim dulu jumlah servos: 3: val1 & val2 & val3 &, baca nomor tersebut sebelum memulai loop. 2. Gunakan terminator yang berbeda untuk menunjukkan Anda tidak memiliki servos lagi, putar sampai Anda menemukannya: val1 & val2 & val3 & #, misalnya.
drodri
Senang solusi ini membantu Anda, @ValrikRobot, bisakah Anda memvalidasi jawabannya jika itu berguna?
drodri
1
atau Anda bisa menghapus for, dan kode itu hanya akan berfungsi kapan saja Anda mengirim perintah.
Lesto
4

Solusi paling sederhana adalah dengan menggunakan 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);

Ini menghasilkan output sebagai berikut:

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

Tepuk tangan!

Mikael Patel
sumber
Ini tidak berfungsi untuk serial.read () ... tahu mengapa? Saya mendapatkan kesalahan berikut:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro
4

Lihat contoh di: 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
sumber
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
sumber
2

jfpoilpret memberikan jawaban yang bagus untuk parsing perintah serial di Arduino. Namun Attiny85 tidak memiliki serial dua arah - SoftwareSerial harus digunakan. Ini adalah bagaimana Anda memasukkan kode yang sama untuk 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, "&");
    }
  }
}

Skema Attiny85 untuk nomor pin masukkan deskripsi gambar di sini

Kompilasi sketsa menjadi:

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.

Jadi ada banyak ruang dan memori untuk sisa kode

selamat tinggal
sumber
Cara membaca dari serial pada ATtiny85 tidak benar-benar bagian dari pertanyaan.
gre_gor
Maaf karena menyimpang dari pertanyaan, tetapi komunitas dan sumber daya yang tersedia untuk Attiny jauh lebih kecil daripada untuk Arduino. Orang-orang seperti saya mencari jawaban menggunakan Arduinokata kunci dan kadang-kadang masuk ke situasi yang sangat sulit karena menerapkan kode Arduino ke Attiny tidak selalu sepele. Harus mengkonversi kode asli untuk bekerja di Attiny, mengujinya bekerja dan memutuskan untuk membaginya
goodevil
Situs ini dalam format tanya jawab. Jawaban harus menjawab pertanyaan. Anda hanya menambahkan sesuatu yang tidak ada hubungannya dengan itu.
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
sumber
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
sumber
-1

Berikut adalah metode Arduino untuk memecah sebuah String sebagai jawaban untuk pertanyaan "Bagaimana cara memecah string dalam substring?" dinyatakan sebagai duplikat dari pertanyaan ini.

Tujuan dari solusi ini adalah mengurai serangkaian posisi GPS yang dicatat dalam file kartu SD . Alih-alih memiliki String yang diterima dari Serial, String dibaca dari file.

Fungsi ini StringSplit()mem-parsing sebuah String sLine = "1.12345,4.56789,hello"ke 3 Strings sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: jalur input yang akan diuraikan,
  2. char cDelim: karakter pembatas antara parameter,
  3. String sParams[]: array output dari parameter,
  4. int iMaxParams: jumlah maksimum parameter,
  5. Output int: jumlah parameter yang diuraikan,

Fungsi ini didasarkan pada String::indexOf()dan 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);
}

Dan penggunaannya sangat sederhana:

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
sumber