들어오는 문자열을 어떻게 분할합니까?


51

직렬 연결을 통해 다음 형식으로 arduino에 서보 위치 목록을 보내고 있습니다.

1:90&2:80&3:180

다음과 같이 파싱됩니다.

servoId : Position & servoId : Position & servoId : Position

이 값을 어떻게 나누고 정수로 변환합니까?


내가 마스터 30 12.4 1과 같은 데이터를 구분하고 마이크로 SD 카드에 저장하려면 1 1 마스터 (esp8266) 받아 봐 문자열, 12.4, 나는 슬레이브 (아두 이노 우노) 시리얼 (30)를 통해 문자열을 보내는이
마지드 mahmoudi

답변:


72

다른 답변과 반대로, 나는 String다음과 같은 이유로 멀리 떨어져 있습니다 .

  • 동적 메모리 사용 (빠른 힙 조각화메모리 소모로 이어질 수 있음 )
  • 건설 / 파괴 / 할당 연산자로 인해 상당히 느림

Arduino와 같은 임베디드 환경 (SRAM이 더 많은 Mega의 경우에도)에서는 표준 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);

문자열을 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);
        }
    }
}

이것은 매우 좋은 해결책 감사합니다. 예제는 그것을 완벽하게
정리

정의되지 않은 수의 서보 입력이있는 경우 어떻게합니까? 내 예에는 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는 큰 제공 대답을 아두 이노에 시리얼 명령을 구문 분석. 그러나 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키워드를 사용 하고 Attiny에 Arduino 코드를 구현하는 것이 항상 쉬운 것은 아니기 때문에 때로는 까다로운 상황에 처하게됩니다. Attiny에서 작동하도록 원본 코드를 변환하고 작동을 테스트하고 공유하기로 결정했습니다.
goodevil

이 사이트는 Q & A 형식입니다. 답변은 질문에 답변해야합니다. 당신은 단지 관련이없는 것을 추가합니다.
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 방법입니다. 현재 질문의 복제본으로 선언되었습니다.

이 솔루션의 목적은 SD 카드 파일에 기록 된 일련의 GPS 위치 를 구문 분석하는 것 입니다. 에서 문자열을받는 대신 파일에서 문자열을 읽습니다.Serial

이 함수는 StringSplit()문자열을 구문 분석 sLine = "1.12345,4.56789,hello"3 개 문자열로 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.