如何分割传入的字符串?


51

我通过串行连接以以下格式通过arduino发送伺服位置列表

1:90&2:80&3:180

可以解析为:

servoId : Position & servoId : Position & servoId : Position

我将如何拆分这些值,并将其转换为整数?


我有slave(arduino uno)通过串行30; 12.4; 1和1个master(esp8266)接收字符串,我希望master中的接收字符串已分离出像30 12.4 1这样的数据,并将其保存在micro sd卡中
majid mahmoudi

Answers:


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 2014年

4
优秀的。我的答案非常基于“ arduino”,并使用了典型的arduino SDK函数,新用户可能会更习惯于此,但是此答案是“生产”系统应采取的措施。通常,尝试摆脱嵌入式系统中的动态内存分配。
drodri 2014年

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 2014年

如果我们有不确定数量的伺服输入怎么办?在我的示例中,值为3。但是,如果有时更多或更少,该怎么办。你能提供用于处理这种情况下任何建议
ValrikRobot

1
当然:有两种可能性。1.首先发送伺服编号:3:val1&val2&val3&,在启动循环之前读取该编号。2.使用另一个终结器来表示您不再有舵机,请循环直到找到它为止:例如val1&val2&val3&#。
drodri 2014年

很高兴这个解决方案对您有帮助,@ValrikRobot,如果有用的话,您能验证一下答案吗?
drodri 2014年

1
或者您可以只删除for,因此该代码将在您发送命令时随时起作用。
Lesto 2014年


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编译为:

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

该网站为问答格式。答案应该回答问题。您只添加与之无关的内容。
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()一个String解析sLine = "1.12345,4.56789,hello"为3个String 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("");
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.