Làm cách nào để chia chuỗi đến?


51

Tôi đang gửi danh sách các vị trí servo thông qua kết nối nối tiếp tới arduino theo định dạng sau

1:90&2:80&3:180

Mà sẽ được phân tích thành:

servoId : Position & servoId : Position & servoId : Position

Làm thế nào tôi có thể chia các giá trị này lên và chuyển đổi chúng thành một số nguyên?


tôi có nô lệ (arduino uno) gửi chuỗi qua chuỗi 30, 12.4; 1 và 1 chuỗi hồi quy chính (Esp8266) tôi muốn trong tổng thể có dữ liệu tách biệt như 30 12.4 1 và lưu nó vào thẻ micro sd
mahmoudi

Câu trả lời:


72

Trái với những câu trả lời khác, tôi muốn tránh xa Stringvì những lý do sau:

  • sử dụng bộ nhớ động (có thể nhanh chóng dẫn đến phân mảnh heapcạn kiệt bộ nhớ )
  • khá chậm do các nhà khai thác xây dựng / phá hủy / chuyển nhượng

Trong một môi trường nhúng như Arduino (ngay cả đối với Mega có nhiều SRAM), tôi muốn sử dụng các hàm C tiêu chuẩn :

  • strchr(): tìm kiếm một ký tự trong chuỗi C (nghĩa là char *)
  • strtok(): tách chuỗi C thành chuỗi con, dựa trên ký tự dấu tách
  • atoi(): chuyển đổi một chuỗi C thành một int

Điều đó sẽ dẫn đến mẫu mã sau:

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

Ưu điểm ở đây là không có phân bổ bộ nhớ động diễn ra; bạn thậm chí có thể khai báo inputnhư một biến cục bộ bên trong một hàm sẽ đọc các lệnh và thực thi chúng; một khi hàm được trả về kích thước chiếm bởi input(trong ngăn xếp) được phục hồi.


Không nghĩ về vấn đề bộ nhớ. điều đó thật tuyệt.
ValrikRobot

4
Xuất sắc. Câu trả lời của tôi là rất "dựa trên arduino" và sử dụng các hàm SDK arduino điển hình mà người dùng mới có thể sử dụng nhiều hơn, nhưng câu trả lời này là những gì nên làm cho các hệ thống "sản xuất". Nói chung, cố gắng thoát khỏi phân bổ bộ nhớ động trong các hệ thống nhúng.
drodri

22

Hàm này có thể được sử dụng để tách một chuỗi thành các mảnh dựa trên ký tự phân tách là gì.

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

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

Chuyển đổi chuỗi thành int

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

Đoạn mã này lấy một chuỗi và phân tách nó dựa trên một ký tự đã cho và trả về Mục giữa ký tự phân tách

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
Đó là một câu trả lời đẹp hoàn hảo! cảm ơn rất nhiều !
Curnelious

11

Bạn có thể làm một cái gì đó như sau, nhưng vui lòng tính đến một số điều:

Nếu bạn sử dụng readStringUntil(), nó sẽ đợi cho đến khi nhận được ký tự hoặc thời gian chờ. Do đó, với chuỗi hiện tại của bạn, vị trí cuối cùng sẽ tồn tại lâu hơn một chút, vì nó phải chờ. Bạn có thể thêm một dấu vết &để tránh thời gian này. Bạn có thể dễ dàng kiểm tra hành vi này trong màn hình của mình, cố gắng gửi chuỗi có và không có thêm &và bạn sẽ thấy thời gian chờ như vậy.

Bạn thực sự không cần chỉ số servo, bạn chỉ có thể gửi chuỗi vị trí của mình và nhận chỉ số servo theo vị trí giá trị trong chuỗi, đại loại như : 90&80&180&. Nếu bạn sử dụng chỉ mục servo, có thể bạn muốn kiểm tra nó (chuyển đổi thành int, và sau đó khớp với chỉ số vòng lặp i) để đảm bảo rằng không có gì sai với thông điệp của bạn.

Bạn phải kiểm tra xem chuỗi trả về readStringUntilkhông trống. Nếu hết thời gian chức năng, bạn đã không nhận đủ dữ liệu và do đó mọi nỗ lực trích xuất intgiá trị của bạn sẽ tạo ra kết quả lạ.

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

Đây có vẻ là một giải pháp rất tốt cảm ơn bạn. Ví dụ xóa nó hoàn hảo
ValrikRobot

Điều gì xảy ra nếu chúng ta có một số lượng đầu vào servo không xác định? trong ví dụ của tôi có 3. Nhưng nếu đôi khi nó nhiều hơn hoặc ít hơn. Bạn có thể đưa ra bất kỳ đề nghị nào để xử lý một kịch bản như vậy không
ValrikRobot

1
Chắc chắn: Có hai khả năng. 1. Trước tiên hãy gửi số lượng servo: 3: val1 & val2 & val3 &, đọc số đó trước khi bắt đầu vòng lặp. 2. Sử dụng một bộ kết thúc khác để chỉ ra rằng bạn không có nhiều servo, lặp cho đến khi bạn tìm thấy nó: ví dụ: val1 & val2 & val3 & #.
drodri

Rất vui vì giải pháp này đã giúp bạn, @ValrikRobot, bạn có thể vui lòng xác nhận câu trả lời nếu nó hữu ích không?
drodri 31/03 '

1
hoặc bạn chỉ có thể loại bỏ for, và vì vậy mã sẽ chỉ hoạt động bất cứ khi nào bạn gửi lệnh.
Lesto


4

Giải pháp đơn giản nhất là sử dụng 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);

Điều này cho đầu ra sau:

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

Chúc mừng!


Nó không hoạt động cho serial.read () ... bất kỳ ý tưởng nào tại sao? Tôi nhận được lỗi sau:invalid conversion from 'int' to 'char*' [-fpermissive]
Alvaro

4

Xem ví dụ tại: 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 đã cung cấp câu trả lời tuyệt vời để phân tích lệnh nối tiếp trên Arduino. Tuy nhiên, Attiny85 không có nối tiếp hai chiều - SoftwareSerial phải được sử dụng. Đây là cách bạn chuyển cùng mã cho 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, "&");
    }
  }
}

Sơ đồ Attiny85 cho số pin nhập mô tả hình ảnh ở đây

Phác thảo biên dịch thành:

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.

Vì vậy, có rất nhiều không gian và bộ nhớ cho phần còn lại của mã


Làm thế nào để đọc từ nối tiếp trên ATtiny85 không thực sự là một phần của câu hỏi.
gre_gor

Xin lỗi vì chuyển hướng từ câu hỏi, nhưng cộng đồng và tài nguyên có sẵn cho Attiny nhỏ hơn so với Arduino. Những người như tôi đang tìm kiếm câu trả lời sử dụng Arduinotừ khóa và đôi khi gặp phải những tình huống rất khó khăn vì việc triển khai mã Arduino lên Attiny không phải lúc nào cũng tầm thường. Phải chuyển đổi mã gốc để hoạt động trên Attiny, đã thử nghiệm nó hoạt động và quyết định chia sẻ nó
chúc mừng

Trang web này ở định dạng Q & A. Câu trả lời nên trả lời câu hỏi. Bạn chỉ cần thêm một cái gì đó không liên quan đến nó.
gre_gor 16/03/18

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

Đây là phương pháp Arduino để phân tách một chuỗi dưới dạng trả lời cho câu hỏi "Làm thế nào để phân tách một chuỗi trong chuỗi con?" tuyên bố như một bản sao của câu hỏi hiện tại

Mục tiêu của giải pháp là phân tích một loạt các vị trí GPS được ghi vào tệp thẻ SD . Thay vì nhận được một Chuỗi từ Serial, Chuỗi được đọc từ tệp.

Hàm này StringSplit()phân tích một Chuỗi sLine = "1.12345,4.56789,hello"thành 3 Chuỗi sParams[0]="1.12345", sParams[1]="4.56789"& sParams[2]="hello".

  1. String sInput: các dòng đầu vào được phân tích cú pháp,
  2. char cDelim: ký tự phân cách giữa các tham số,
  3. String sParams[]: mảng đầu ra của tham số,
  4. int iMaxParams: số lượng tham số tối đa,
  5. Đầu ra int: số lượng tham số được phân tích cú pháp,

Chức năng này dựa trên 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);
}

Và cách sử dụng rất đơn giản:

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("");
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.