使用Arduino将serial.read()转换为可用字符串

77
我正在使用两个Arduino板,使用NewSoftSerialRF收发器相互发送纯文本字符串。
每个字符串可能有20-30个字符长度。如何将Serial.read()转换为字符串,以便我可以执行if x == "testing statements"等操作?

请检查我下面的答案,它比你选择的答案更直接/简单。 - Ihab
15个回答

120

无限字符串读取:

String content = "";
char character;
    
while(Serial.available()) {
     character = Serial.read();
     content.concat(character);
}
      
if (content != "") {
     Serial.println(content);
}

在Arduino Leonardo上比任何其他读取方法更可靠。由于concat可能会使用RAM,这可能是一个问题,但如果草图可以承受它,它似乎是完成此操作的最佳方法。 - Daniel F
23
非常实用且简单。不过,我发现在串口读取每个字符时需要添加一个小延迟 - 否则它会将每个字符打印到单独的行上而不是连接在一起。 void setup() { Serial.begin(9600); // 初始化串口 } void loop() { String content = ""; char character; while(Serial.available()) { character = Serial.read(); content.concat(character); delay (10); // 添加延迟以避免打印每个字符在新行上。 } if (content != "") { Serial.println(content); } } - So Over It
4
如果您想将此代码移动到从“loop()”调用的单独函数中,非常重要的是您使用return(content);而不是Serial.println()。否则,您将陷入无限循环,因为它会捕获函数打印到串行端口的任何内容并尝试重新处理它。 - depwl9992
如果在任何情况下仍然无法正常工作,请尝试使用 content.trim() 方法。 - Yakob Ubaidi
2
你必须在打印内容后清除它,否则if块将再次执行,即使没有新的串行数据到达。 - Md. Minhazul Haque

65

来自于Help with Serial.Read()获取字符串

char inData[20]; // Allocate some space for the string
char inChar = -1; // Where to store the character read
byte index = 0; // Index into array; where to store the character

void setup() {
    Serial.begin(9600);
    Serial.write("Power On");
}

char Comp(char* This) {
    while (Serial.available() > 0) // Don't read unless there
                                   // you know there is data
    {
        if(index < 19) // One less than the size of the array
        {
            inChar = Serial.read(); // Read a character
            inData[index] = inChar; // Store it
            index++; // Increment where to write next
            inData[index] = '\0'; // Null terminate the string
        }
    }

    if (strcmp(inData, This) == 0) {
        for (int i=0; i<19; i++) {
             inData[i] = 0;
        }
        index = 0;
        return(0);
    }
    else {
        return(1);
    }
}

void loop()
{
    if (Comp("m1 on") == 0) {
        Serial.write("Motor 1 -> Online\n");
    }
    if (Comp("m1 off") == 0) {
        Serial.write("Motor 1 -> Offline\n");
    }
}

如果这不起作用,请尝试添加 inData.trim()。如果我们正在使用Arduino控制台,则会有换行符。这个https://dev59.com/zYHba4cB1Zd3GeqPXOEp对我很有用。 - Yakob Ubaidi
1
这本不应该起作用。循环内的第二个“if”语句将永远不会触发,因为您已经从第一个“if”比较中读取了串行数据。 - Levi Roberts

57

你可以使用 Serial.readString()Serial.readStringUntil() 从Arduino的串行端口解析字符串。

你也可以使用Serial.parseInt()从串行端口读取整数值。

int x;
String str;
    
void loop() 
{
     if(Serial.available() > 0)
     {
        str = Serial.readStringUntil('\n');
        x = Serial.parseInt();
     }
}

要通过串口发送的值为my string\n5,结果将是str = "my string"x = 5


2
我在手动将串口输入读入字符缓冲区时遇到了奇怪的电压波动,但使用Serial.readStringUntil()解决了所有问题,并使代码更易读!谢谢! - Eddie Fletcher
2
我进行了测试,确实更容易。但是与字符缓冲区相比,这个操作需要更多的时间和延迟。 - Toni Tegar Sahidi
有趣,你能分享更多关于你的发现以及这里所涉及到的时间差异有多大的细节吗? 如果你有详细的数字,我会很高兴如果你能与我们分享。谢谢! - Ihab

13

我曾经也问过同样的问题,经过一些调查研究,我找到了类似的东西。

这对我来说非常好用。我用它来远程控制我的Arduino。

// Buffer to store incoming commands from serial port
String inData;
    
void setup() {
    Serial.begin(9600);
    Serial.println("Serial conection started, waiting for instructions...");
}
    
void loop() {
    while (Serial.available() > 0)
    {
        char recieved = Serial.read();
        inData += recieved; 
    
        // Process message when new line character is recieved
        if (recieved == '\n')
        {
            Serial.print("Arduino Received: ");
            Serial.print(inData);
                
            // You can put some if and else here to process the message juste like that:

            if(inData == "+++\n"){ // DON'T forget to add "\n" at the end of the string.
              Serial.println("OK. Press h for help.");
            }   

    
            inData = ""; // Clear recieved buffer
        }
    }
}

1
比其他更好的最佳选择 :-) - Jeevanantham
这真的很有效!我将“\n”替换为“\r”,以适应我的用例。 - enthusiasticgeek

5
这将会更加容易:
char data [21];
int number_of_bytes_received;

if(Serial.available() > 0)
{
    number_of_bytes_received = Serial.readBytesUntil (13,data,20); // read bytes (max. 20) from buffer, untill <CR> (13). store bytes in data. count the bytes recieved.
    data[number_of_bytes_received] = 0; // add a 0 terminator to the char array
} 

bool result = strcmp (data, "whatever");
// strcmp returns 0; if inputs match.
// http://en.cppreference.com/w/c/string/byte/strcmp


if (result == 0)
{
   Serial.println("data matches whatever");
} 
else 
{
   Serial.println("data does not match whatever");
}

3
最好且最直观的方法是与 loop()setup() 一起使用 Arduino 定义的serialEvent() 回调。 我曾经构建过一个处理消息接收的小型库,但从没来得及开源它。 该库接收以 \n 结尾的行,表示命令和任意有效负载,由空格分隔。 您可以轻松地调整它以使用自己的协议。 首先,是一个名为 SerialReciever.h 的库:
#ifndef __SERIAL_RECEIVER_H__
#define __SERIAL_RECEIVER_H__
    
class IncomingCommand {
  private:
    static boolean hasPayload;
  public:
    static String command;
    static String payload;
    static boolean isReady;
    static void reset() {
      isReady = false;
      hasPayload = false;
      command = "";
      payload = "";
    }
    static boolean append(char c) {
      if (c == '\n') {
        isReady = true;
        return true;
      }
      if (c == ' ' && !hasPayload) {
        hasPayload = true;
        return false;
      }
      if (hasPayload)
        payload += c;
      else
        command += c;
      return false;
    }
};
    
boolean IncomingCommand::isReady = false;
boolean IncomingCommand::hasPayload = false;
String IncomingCommand::command = false;
String IncomingCommand::payload = false;
    
#endif // #ifndef __SERIAL_RECEIVER_H__

要使用它,在您的项目中执行以下操作:

#include <SerialReceiver.h>
    
void setup() {
  Serial.begin(115200);
  IncomingCommand::reset();
}
    
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    if (IncomingCommand::append(inChar))
      return;
  }
}

使用接收到的命令:

void loop() {
  if (!IncomingCommand::isReady) {
    delay(10);
    return;
  }

executeCommand(IncomingCommand::command, IncomingCommand::payload); // I use registry pattern to handle commands, but you are free to do whatever suits your project better.
    
IncomingCommand::reset();

2
以下是更加健壮的实现,它处理异常输入和竞争条件:
  • 检测异常长的输入值,并安全地丢弃它们。例如,如果源代码存在错误并生成了没有预期终止符的输入;或者有恶意行为。
  • 确保字符串值始终以 null 结尾(即使缓冲区大小已经被完全填充)。
  • 等待完整的值被捕获。例如,传输延迟可能导致 Serial.available() 在其余的值到达之前返回零。
  • 当多个值到达时,不会跳过它们,除非它们在串行输入缓冲区的限制下无法处理。
  • 可以处理作为另一个值前缀的值(例如,“abc” 和 “abcd” 都可以被读入)。
为了更有效率和避免内存问题,它有意使用字符数组而不是 String 类型。它还避免使用 readStringUntil() 函数,在输入到达之前不会超时。
原始问题没有说明可变长度的字符串是如何定义的,但我将假定它们由单个换行符终止,这将把它转换为一行读取问题。
int read_line(char* buffer, int bufsize)
{
  for (int index = 0; index < bufsize; index++) {
    // Wait until characters are available
    while (Serial.available() == 0) {
    }

    char ch = Serial.read(); // read next character
    Serial.print(ch); // echo it back: useful with the serial monitor (optional)

    if (ch == '\n') {
      buffer[index] = 0; // end of line reached: null terminate string
      return index; // success: return length of string (zero if string is empty)
    }

    buffer[index] = ch; // Append character to buffer
  }

  // Reached end of buffer, but have not seen the end-of-line yet.
  // Discard the rest of the line (safer than returning a partial line).

  char ch;
  do {
    // Wait until characters are available
    while (Serial.available() == 0) {
    }
    ch = Serial.read(); // read next character (and discard it)
    Serial.print(ch); // echo it back
  } while (ch != '\n');

  buffer[0] = 0; // set buffer to empty string even though it should not be used
  return -1; // error: return negative one to indicate the input was too long
}

以下是一个使用它从串行监视器读取命令的示例:
const int LED_PIN = 13;
const int LINE_BUFFER_SIZE = 80; // max line length is one less than this

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  Serial.print("> ");

  // Read command

  char line[LINE_BUFFER_SIZE];
  if (read_line(line, sizeof(line)) < 0) {
    Serial.println("Error: line too long");
    return; // skip command processing and try again on next iteration of loop
  }

  // Process command

  if (strcmp(line, "off") == 0) {
      digitalWrite(LED_PIN, LOW);
  } else if (strcmp(line, "on") == 0) {
      digitalWrite(LED_PIN, HIGH);
  } else if (strcmp(line, "") == 0) {
    // Empty line: no command
  } else {
    Serial.print("Error: unknown command: \"");
    Serial.print(line);
    Serial.println("\" (available commands: \"off\", \"on\")");
  }
}

2
String content = "";
char character;

if(Serial.available() >0){
    //reset this variable!
    content = "";

    //make string from chars
    while(Serial.available()>0) {
        character = Serial.read();
        content.concat(character);
}
    //send back   
    Serial.print("#");
    Serial.print(content);
    Serial.print("#");
    Serial.flush();
}

警告:有时您的字符串将被分成两个或更多部分。请放置一些“消息结束”字符以检查如果您将所有字符连接起来。 - flamaniac

2
如果你想从串口读取消息并需要单独处理每条消息,我建议使用类似这样的分隔符将消息分成部分:
String getMessage()
{
  String msg=""; //the message starts empty
  byte ch; // the character that you use to construct the Message 
  byte d='#';// the separating symbol 

  if(Serial.available())// checks if there is a new message;
  {
      while(Serial.available() && Serial.peek()!=d)// while the message did not finish
      {
          ch=Serial.read();// get the character
          msg+=(char)ch;//add the character to the message
          delay(1);//wait for the next character
      }
     ch=Serial.read();// pop the '#' from the buffer
     if(ch==d) // id finished
         return msg;
     else
         return "NA";
  }
  else
      return "NA"; // return "NA" if no message;
}

每次使用该函数时,您将获得一条单独的消息。

2
这要归功于magma。很棒的答案,但它使用了c++风格的字符串,而不是c风格的字符串。一些用户可能会发现这更容易。
String string = "";
char ch; // Where to store the character read

void setup() {
    Serial.begin(9600);
    Serial.write("Power On");
}
    
boolean Comp(String par) {
    while (Serial.available() > 0) // Don't read unless
                                       // there you know there is data
    {
        ch = Serial.read(); // Read a character
        string += ch; // Add it
    }
    
    if (par == string) {
       string = "";
       return(true);
    }
    else {
       //dont reset string
       return(false);
    }
}
    
void loop()
{
    if (Comp("m1 on")) {
        Serial.write("Motor 1 -> Online\n");
    }
    if (Comp("m1 off")) {
        Serial.write("Motor 1 -> Offline\n");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接