Serial.readString questions

Q1:
If I use Serial.print("ABC") and Serial.print("DEF") to send info to a Arduino_111 from PC/another Arduino, afterthat, the Arduino_111 performed StringA = Serial.readString() , what will I get in StringA? "ABC" or "ABCDEF"?

Q1_Alternate:
If StringA will read all six characters "ABCDEF," how can I only let it read "ABC" and only read "DEF" on the next reading cycle? Maybe change print content to Serial.print("ABC;") / ("DEF;") and use Serial.readStringUntil(";") ? Will that work? Is the ";" mark gonna included in the reading result? I mean will I get StringA = "ABC;" / "DEF;" OR still "ABC" / "DEF" ? (Will stop mark ";" been auto delete?)

Q2:
If I use Serial.println("ABC") , will the "\n" mark been read out by StringA = Serial.readString() ?
I mean, will StringA become "ABC\n"?

Thanks!

A1: If the PC sends these strings within 1 second you should get "ABCDEF".

Correct.

No, the delimiter isn't included.

A2:
The string read will be "ABC\r\n". println() always add a carriage return and a newline character.

I do not know your application, but I recommend to think about using Serial.read() in a separate routine to receive data. Reason is that Serial.readString() and also Serial.readStringUntil() wait and return whatever they have received after a certain timeout interval.

If you use something like this

String cmd = "";
char EOLChar   = char(13);

void Evaluate(String MyCmd){
   // Do something with the cmd
}

...

void CheckSerial() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c != EOLChar) {
      cmd = cmd + c;
     // if you want you can check cmd.length() here, issue an error message  
     // if it has become too long, delete all or parts of the string that 
     // was gathered until then
    }
    else {
      Evaluate(cmd);
      cmd = "";
    }
  }
}

You can check for input in the loop with CheckSerial(); without significant time delay and quite safely for your individual EOLChar. If you want to be even more safe you may also check the length of cmd regularly and handle an error when it becomes too long ...

Just a hint, in case you are not happy with the built-in solutions ... :wink:

A big thanks to you, your solution could be exactly what I'll need. :grinning:

For serial:

  1. C#.NET PC App   <=2-way com=>   Arduino USB Serial
  2. Arduino A       <=2-way com=>   Arduino B

For Serial Payload:

  • Command such as "FORWARD"(trivial direction), "P1"(PWM_power_level_1) ...
  • Status info such as "RFORWARD"(command Readback, begin with R), "S18"(Speed, begin with S) ...
  • Geolocation/Milepost read from RFID such as "K14+175", "Racho Yard Inbound 2" ...

Which method is used to set that timeout? Is Serial.setTimeout() ?

What if my command is too long and readString() can't finish reading them before it timeout? Will one command string I sent to be broken into two strings? (And the string_2's info will overwrite string_previou's info, which causes info broken or lost)

  • Last question:

Can Serial.read() read a "space" and write it in the cmd string? Such as "A612 B55 C123 F0". Will Axxx, Bxxx, Cxxx, Fxxx be separated by space in cmd string?

I will have a look later today for the rest, but the answer to this is yes, it can ....

Correct: see https://www.arduino.cc/reference/en/language/functions/communication/serial/settimeout/

What if my command is too long and readString() can't finish reading them before it timeout?

Timeout in general defines the time how long a routine waits when nothing happens (like: No character received, no Acknowledge received, ...). If in this case the transmitting part or the transmission path (just wire in this case, but think of the internet) delays/pauses for too long, the timeout will avoid that your application gets stuck. Even if it was different (what it is not) and as the timeout defaults to 1000 ms, even with 9600 Bd you may send around 960 characters during that time ... Go to 115200 Bd and you may transmit about 11.520 characters/sec.

When you asked for "space" you might have a look at

https://www.asciitable.com/

All data from 0 to 255 can be transmitted by Serial. Some have a defined role (control characters, numbers, letters, graphic elements), most of it coming from the good old time of teletype writers and V24 terminals. Not all of the characters will be visible and only a subset of them will be displayed as shown in the table. You can take advantage of the definitions e.g. by using some of the today mostly unused control characters for your own purposes.

Here is a simple example how one may set up the Serial command server, just take from it what you like ... It does not check for all possible errors but may give you some hints ...

/* ec2021

      Skeleton of a Simple Serial Command Server Application
*/
const String c_version = "Command Server Skeleton V1.0";
const int MaxCmdLen = 40;
String cmd = "";
int Direction = 0;

void PrintHelp() {
  Serial.println(F("-------------------------"));
  Serial.println(c_version);
  Serial.println(F("-------------------------"));
  Serial.println(F("?         for Help"));
  Serial.println(F("Axxx      Call routine A with value 0 <= xxx <= 999"));
  Serial.println(F("Bxx       Call routine B with value 0 <= xx  <=  99"));
  Serial.println(F("Dx        Set Direction value = [0,3,6,9] clockwise"));
  Serial.println(F("F         Set direction to FORWARD"));
  Serial.println(F("Px        Set PWM to power level x with 0<= x <= 9"));
  Serial.println(F("FORWARD   Set direction to FORWARD"));
  Serial.println(F("RIGHT     Set direction to RIGHT"));
  Serial.println(F("BACKWARD  Set direction to BACKWARD"));
  Serial.println(F("LEFT      Set direction to LEFT"));
  Serial.println(F("-------------------------"));
}

boolean ValueIsValid(String ValueString, int minimum, int maximum) {
  if (ValueString.length() < 1) {
    Serial.println(F("No Data"));
    return false;
  }
  char c = ValueString.charAt(0);
  if ((c == '-' || c == '+') && (ValueString.length() > 1)) c = ValueString.charAt(1); // If it is not a digit, it may by a plus or minus
  if (!isDigit(c)) {  
    Serial.println(F("ERROR: Not a Digit"));
    return false;
  }  
  // Any rubbish after the digit is neglected, just checking if there is a floating point
  if (ValueString.indexOf(".") > 0) {
    Serial.println(F("ERROR: Floating Point Value not allowed here"));
    return false;
    }
  if (ValueString.toInt() < minimum) {
    Serial.print(F("ERROR: Below Minimum of "));
    Serial.println(minimum);
    return false;
  };
  if (ValueString.toInt() > maximum) {
    Serial.print(F("ERROR: Above Maximum of "));
    Serial.println(maximum);
    return false;
  };
  return true;
}

void ProcedureA(String Data) {
  Serial.print(F("You called A with "));
  if (Data.length() > 0) {
    Serial.println(Data);
  }
  else {
    Serial.println(F("no Data"));
    return;
  };
  if (ValueIsValid(Data, 0, 999)) {
    Serial.print(F("Data has been evaluated as : "));
    Serial.println(Data.toInt());
  }
}

void ProcedureB(String Data) {
  Serial.print(F("You called B with "));
  if (Data.length() > 0) {
    Serial.println(Data);
  }
  else {
    Serial.println(F("no data"));
    return;
  };
  if (ValueIsValid(Data, 0, 99)) {
    Serial.print(F("Data has been evaluated as : "));
    Serial.println(Data.toInt());
  }
}

void ProcedureD(String Data) {
  Serial.print(F("You called D with "));
  if (Data.length() > 0) {
    Serial.println(Data);
  }
  else {
    Serial.println(F("no data"));
    return;
  };
  if (ValueIsValid(Data, 0, 11)) {
    int dir = Data.toInt();
    switch (dir) {
      case 0 : Direction = 0;
        Serial.println(F("Direction = FORWARD"));
        break;
      case 3 : Direction = 3;
        Serial.println(F("Direction = RIGHT"));
        break;
      case 6 : Direction = 6;
        Serial.println(F("Direction = BACKWARD"));
        break;
      case 9 : Direction = 9;
        Serial.println(F("Direction = LEFT"));
        break;
      default : Serial.println(F("ERROR: Invalid direction!"));
    }
  }
}



void ProcedureP(String Data) {
  Serial.print(F("You called P with "));
  if (Data.length() > 0) {
    Serial.println(Data);
  }
  else {
    Serial.println(F("no data"));
    return;
  }
  if (ValueIsValid(Data, 0, 9)) {
    Serial.print(F("Data has been evaluated as : "));
    Serial.println(Data.toInt());
  }
}


void CheckCommands() {
  if (Serial.available() > 0) { // read the incoming command
    char c = Serial.read();
    if (c != char(13)) {
      cmd = cmd + c;
    }
    else {
      Evaluate(cmd);
      cmd = "";
    };
    if (cmd.length() > MaxCmdLen) {
      cmd = "";
      Serial.println(F("ERROR: Command too long!"));
    };
  }
}

void Evaluate(String Command) {
  byte mode = 0;
  // set a mode > 0 if there is a complete word command
  if (Command.startsWith(F("FORWARD")))  mode = 1;
  if (Command.startsWith(F("RIGHT")))    mode = 2;
  if (Command.startsWith(F("BACKWARD"))) mode = 3;
  if (Command.startsWith(F("LEFT")))     mode = 4;
  switch (mode) {
    case 1 : ProcedureD(F("0"));
      break;
    case 2 : ProcedureD(F("3"));
      break;
    case 3 : ProcedureD(F("6"));
      break;
    case 4 : ProcedureD(F("9"));
      break;
  }
  if (mode == 0) {
    char Cmd = Command.charAt(0);
    switch (Cmd) {
      case 'A' : ProcedureA(Command.substring(1));
        break;
      case 'B' : ProcedureB(Command.substring(1));
        break;
      case 'D' : ProcedureD(Command.substring(1));
        break;
      case 'F' : if (Command.length() == 1) {
          ProcedureD(F("0"));
        }
        else  {
          Serial.print(F("ERROR: Invalid command = "));
          Serial.println(Command);
        }
        break;
      case 'P' : ProcedureP(Command.substring(1));
        break;
      case '?' : PrintHelp();
        break;
      default  : Serial.println(F("?        for Help"));
        break;
    }
  }
}

void setup() {
  Serial.begin(115200);
  Serial.print(F("Hello, you just started "));
  Serial.println(c_version);
}


void loop() {
  CheckCommands();
}