ฉันจะแยกสตริงที่เข้ามาได้อย่างไร


51

ฉันกำลังส่งรายการตำแหน่งเซอร์โวผ่านการเชื่อมต่อแบบอนุกรมไปยัง arduino ในรูปแบบต่อไปนี้

1:90&2:80&3:180

ซึ่งจะแยกเป็น:

servoId : Position & servoId : Position & servoId : Position

ฉันจะแบ่งค่าเหล่านี้เป็นอย่างไรและแปลงเป็นจำนวนเต็มได้อย่างไร


ฉันมีทาส (arduino uno) ส่งสตริงผ่าน serial 30; 12.4; 1 และ 1 master (esp8266) สตริง recive ที่ฉันต้องการในต้นแบบมีข้อมูลแยกเช่น 30 12.4 1 และบันทึกไว้ใน micro sd card
majid mahmoudi

คำตอบ:


72

ตรงกันข้ามกับคำตอบอื่น ๆ ฉันควรอยู่ห่างจากStringเหตุผลดังต่อไปนี้:

  • การใช้หน่วยความจำแบบไดนามิก (ซึ่งอาจนำไปสู่การกระจายตัวของฮีปและหน่วยความจำหมดอย่างรวดเร็ว )
  • ค่อนข้างช้าเนื่องจากผู้ดำเนินการก่อสร้าง / ทำลาย / กำหนด

ในสภาพแวดล้อมแบบฝังตัวเช่น Arduino (แม้แต่เมกะที่มี SRAM มากกว่า) ฉันควรใช้ฟังก์ชั่น C มาตรฐาน :

  • strchr(): ค้นหาอักขระในสตริง C (เช่นchar *)
  • strtok(): แยกสตริง C ออกเป็นสตริงย่อยตามอักขระตัวคั่น
  • atoi(): แปลงสตริง C เป็น int

ซึ่งจะนำไปสู่ตัวอย่างโค้ดต่อไปนี้:

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

ข้อดีคือไม่มีการจัดสรรหน่วยความจำแบบไดนามิก คุณสามารถประกาศinputเป็นตัวแปรโลคอลภายในฟังก์ชันที่จะอ่านคำสั่งและเรียกใช้งานมัน เมื่อฟังก์ชันถูกส่งคืนขนาดที่ถูกครอบครองโดยinput(ในสแต็ก) จะถูกกู้คืน


ไม่ได้คิดถึงปัญหาหน่วยความจำ มันเยี่ยมมาก
ValrikRobot

4
ยอดเยี่ยม คำตอบของฉันเป็นไปตาม "arduino" มากและใช้ฟังก์ชั่น arduino SDK ทั่วไปซึ่งผู้ใช้นวนิยายสามารถคุ้นเคยได้มากกว่า แต่คำตอบนี้คือสิ่งที่ควรทำสำหรับระบบ "การผลิต" โดยทั่วไปแล้วพยายามหลีกเลี่ยงจากการจัดสรรหน่วยความจำแบบไดนามิกในระบบฝังตัว
drodri

22

ฟังก์ชั่นนี้สามารถใช้แยกสตริงออกเป็นชิ้น ๆ ตามลักษณะของตัวแยก

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

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

แปลง String เป็น int

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

รหัสอันนี้ใช้สตริงและแยกมันตามตัวอักษรที่กำหนดและส่งกลับรายการระหว่างตัวละครแยก

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
นั่นเป็นคำตอบที่สมบูรณ์แบบที่สวยงาม! ขอบคุณมาก !
Curnelious

11

คุณสามารถทำสิ่งต่อไปนี้ แต่โปรดคำนึงถึงหลายสิ่ง:

หากคุณใช้readStringUntil()มันจะรอจนกว่าจะได้รับตัวละครหรือหมดเวลา ดังนั้นด้วยสตริงปัจจุบันของคุณตำแหน่งสุดท้ายจะคงอยู่อีกต่อไปอีกเล็กน้อยเนื่องจากต้องรอ คุณสามารถเพิ่มส่วนท้าย&เพื่อหลีกเลี่ยงการหมดเวลานี้ คุณสามารถตรวจสอบพฤติกรรมนี้ได้ง่าย ๆ ในมอนิเตอร์ลองส่งสตริงที่มีและไม่มีส่วนเสริม&และคุณจะเห็นการหมดเวลาดังกล่าว

90&80&180&จริงๆแล้วคุณไม่จำเป็นต้องดัชนีเซอร์โวคุณก็สามารถส่งสายของคุณของตำแหน่งและได้รับดัชนีเซอร์โวโดยตำแหน่งค่าในสตริงสิ่งที่ชอบ: หากคุณใช้ดัชนีเซอร์โวคุณอาจต้องการตรวจสอบ (แปลงเป็นintแล้วจับคู่ดัชนีลูป i) เพื่อให้แน่ใจว่าไม่มีอะไรผิดปกติกับข้อความของคุณ

คุณต้องตรวจสอบว่าสตริงที่ส่งคืนจากreadStringUntilไม่ว่างเปล่า หากการหมดเวลาของฟังก์ชั่นคุณไม่ได้รับข้อมูลเพียงพอดังนั้นความพยายามใด ๆ ในการแยกintค่าของคุณจะให้ผลลัพธ์ที่แปลก

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

ดูเหมือนว่าจะเป็นทางออกที่ดีมากขอบคุณ ตัวอย่างจะถูกลบออกอย่างสมบูรณ์แบบ
ValrikRobot

ถ้าเรามีจำนวนเซอร์โวอินพุตที่ไม่ได้กำหนด ในตัวอย่างของฉันมี 3. แต่ถ้าบางครั้งมันมากกว่าหรือน้อยกว่า คุณสามารถให้คำแนะนำสำหรับการจัดการสถานการณ์ดังกล่าวได้หรือไม่
ValrikRobot

1
แน่นอน: มีความเป็นไปได้สองอย่าง 1. ส่งจำนวนเซอร์โวก่อน: 3: val1 & val2 & val3 & อ่านหมายเลขดังกล่าวก่อนเริ่มลูป 2. ใช้เทอร์มิเนเตอร์ที่แตกต่างกันเพื่อระบุว่าคุณไม่มีเซอร์โวอีกต่อไปวนซ้ำจนกว่าคุณจะพบมัน: val1 & val2 & val3 & # เป็นต้น
drodri

ดีใจที่โซลูชันนี้ช่วยคุณ @ValrikRobot คุณช่วยตรวจสอบคำตอบได้ไหมถ้ามันมีประโยชน์
drodri

1
หรือคุณสามารถลบคำสั่ง for และรหัสจะใช้ได้เมื่อใดก็ตามที่คุณส่งคำสั่ง
Lesto


4

ทางออกที่ง่ายที่สุดคือการใช้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);

สิ่งนี้ให้ผลลัพธ์ต่อไปนี้:

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

ไชโย!


มันไม่ทำงานสำหรับ serial.read () ... ความคิดใดทำไม ฉันได้รับข้อผิดพลาดดังต่อไปนี้:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro

4

ดูตัวอย่างได้ที่: 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 อย่างไรก็ตาม Attiny85 ไม่มีซีเรียลสองทิศทาง - ต้องใช้ SoftwareSerial นี่คือวิธีที่คุณพอร์ตรหัสเดียวกันสำหรับ 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 แผนงานสำหรับหมายเลขพิน ป้อนคำอธิบายรูปภาพที่นี่

ร่างรวบรวมเป็น:

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.

ดังนั้นมีพื้นที่และหน่วยความจำมากมายสำหรับรหัสที่เหลือ


วิธีอ่านจากซีเรียลใน ATtiny85 ไม่ใช่ส่วนหนึ่งของคำถาม
gre_gor

ขออภัยที่แยกจากคำถาม แต่ชุมชนและแหล่งข้อมูลสำหรับ Attiny นั้นเล็กกว่า Arduino คนอย่างฉันที่กำลังมองหาคำตอบใช้Arduinoคำหลักและบางครั้งเข้าสู่สถานการณ์ที่ยุ่งยากมากเนื่องจากการติดตั้งรหัส Arduino ลงบน Attiny นั้นไม่ได้เป็นเรื่องเล็กน้อยเสมอไป ต้องแปลงรหัสต้นฉบับให้ทำงานบน Attiny ทดสอบใช้งานได้และตัดสินใจที่จะแชร์
goodevil

ไซต์นี้อยู่ในรูปแบบถาม & ตอบ คำตอบควรตอบคำถาม คุณแค่เพิ่มสิ่งที่ไม่เกี่ยวข้องกับมัน
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

นี่คือวิธีArduinoเพื่อแยกสตริงเป็นคำตอบสำหรับคำถาม"วิธีการแยกสตริงในสตริงย่อย" ประกาศเป็นสำเนาของคำถามปัจจุบัน

วัตถุประสงค์ของการแก้ปัญหาคือการแยกตำแหน่งGPS ที่บันทึกไว้ในไฟล์การ์ด SD แทนที่จะได้รับ String จากSerialString จะอ่านจากไฟล์

ฟังก์ชั่นStringSplit()แยกสตริงsLine = "1.12345,4.56789,hello"3 Strings sParams[0]="1.12345", และsParams[1]="4.56789"sParams[2]="hello"

  1. String sInput: บรรทัดอินพุตที่ถูกวิเคราะห์คำ
  2. char cDelim: อักขระตัวคั่นระหว่างพารามิเตอร์
  3. String sParams[]: อาร์เรย์เอาต์พุตของพารามิเตอร์
  4. int iMaxParams: จำนวนพารามิเตอร์สูงสุด
  5. เอาท์พุทint: จำนวนพารามิเตอร์แยกวิเคราะห์

ฟังก์ชั่นนี้เป็นไปตามString::indexOf()และ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);
}

และการใช้งานนั้นง่ายมาก:

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("");
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.