Serial left a char behind

Hi Guys, I am with a problem with serial. I tried to implement a protocol with some commands and a char ´X´ as the end of string marker. Arduino Always replies at least with the string "OKX" The commands are always as bellow;

VERSION=?X ; requesting the version of the code in arduino ,arduino replies with a string
SETTEMP=100,10X ; setting target temp 100 C with a span of 10C, arduino replies with "OKX"
SETTEMP=?X ; requesting the last parameter of settemp, arduino replies with a string
SETDURATION=3000X ; setting max time to control, arduino replies with "OKX"

The problem is that some time it seems that retrieving the data from serial, the last char of the previous command is not removed from serial and the current string has unexpected char and arduino does not recognize the command. Example:

SETTEMP=100,5X , arduino parse this string properly, cleans the inputstring and following if I send SETDURATION=3000X, the string received is "5SETDURATION=3000X"

Will put the code after the 5 min passes....

#include <max6675.h>

/* Definições: GPIOs do Arduino utilizado na comunicação com o
   MAX6675 */
#define GPIO_SO       8
#define GPIO_CS       9
#define GPIO_CLK      10

/* Definição: baudrate da comunicação com Serial Monitor */
#define BAUDRATE_SERIAL_MONITOR    9600

/* Criação de objeto para comunicação com termopar */
MAX6675 termopar(GPIO_CLK, GPIO_CS, GPIO_SO);

int Relay_PIN = 4;



// ****************** Variáveis ******************************
boolean showdebug = true;                                                // to control debug message to be displayed
String Version1Str = "RogerThermoK V";                    // Version identification
String Version2Str="2.1 ";
String Version3Str="@ 17-Mar-2021 ";
String Version4Str="@ 11:00";
String VersionStr;

boolean stringComplete = false;  // whether the string is complete
String inputString = "";                                                  // a String to hold incoming data
unsigned long tempodecorrido;
unsigned long previousMillis ;
bool Running;
float temperatura;
String ParamStr[2] ;                                                      //  Max 2 parameters allowed
float setpoint;
float setpointspan;
int timeinterval;
unsigned long TimeIni;
unsigned long TimeNow;
float uppertemp;
float downtemp;
boolean relaystate;
long duration;
int timenow;
// ***********************************************************





void ParseSerialData(String StringToParse, String & strCommand,  String & strParameter) {

  bool ParseError;

  if (showdebug) {
    Serial.println("Parsing...");
    Serial.print("StringToParse : ");
    Serial.println(StringToParse);
  }

  int iPositionCharX = StringToParse.indexOf('X');       // get the possition of the EOS (End Of String char)
  int iPositionCharEqual = StringToParse.indexOf('=');   // get the position of the separator
  int limit = strlen(StringToParse.c_str());             // get the length of the String received
  ParseError = false;

  if ((iPositionCharX) == -1)                           // Check if EOS char was received  -1 means no received
  {
    // no EOS FOUND
    ParseError = true;
    strCommand = "ERROR=1";
    if (showdebug) {
      Serial.println("Missing EOS char detected.");
    }
  }

  if (not(ParseError)) {
    // Check for missing parameters
    if (iPositionCharEqual  == (iPositionCharX - 1))
    {
      // Missing parameter
      ParseError = true;
      strCommand = "ERROR=2";
      if (showdebug) {
        Serial.println("Missing Paramater detected.");
      }
    }

    if (not(ParseError)) {
      // Extract the command and Parammeter
      if (iPositionCharEqual > 2) {
      strCommand = StringToParse.substring(0, iPositionCharEqual); // extract the Command
        strCommand.trim()   ;                                        // remove any leading and trailing whitespace

        // Extract the Parameter
        strParameter = StringToParse.substring(iPositionCharEqual + 1, iPositionCharX);
        strParameter.trim()  ;                                       // remove any leading and trailing whitespace
      }
      else {
        strCommand = StringToParse.substring(0, iPositionCharX) ;    // remove the 'X' char from the string
        strCommand.trim() ;                                          // remove any leading and trailing whitespace
        strParameter = "";                                           // There is no parameter, only command received
      }

      if (showdebug) {
        Serial.print("StrCommand :");
        Serial.println(strCommand);
        Serial.print("strParameter:");
        Serial.println(strParameter);
      }
    }
  }
}




void ParseParameters(String StringToParse) {
  // Rotina feita para pegar no max 2 parametros
  String ParamStr1, ParamStr2;
  bool Erro;
  Erro = false;
  int lenghtstring = StringToParse.length();
  int iPositionCharComma = StringToParse.indexOf(',');

  if (iPositionCharComma == 0) {
    // Erro nesta merda !
    Erro = true;
  }

  if ((iPositionCharComma == -1) && (Erro == false) ) {
    StringToParse.trim();
    ParamStr[1] = StringToParse;
  }

  if (iPositionCharComma > 1) {
    ParamStr[1] = StringToParse.substring(0, iPositionCharComma);
    ParamStr[1].trim();
    ParamStr[2] = StringToParse.substring(iPositionCharComma + 1, lenghtstring);
    ParamStr[2].trim();
  }
}

// --------------------------
void setup() {

  
  
  // initialize serial:
  Serial.begin(BAUDRATE_SERIAL_MONITOR);
  
  inputString.reserve(50);				// reserve 50 bytes for the inputString:

  if (showdebug) {
    Serial.println(VersionStr);
  }


  // Set relay pin as output
  pinMode(Relay_PIN, OUTPUT);
  digitalWrite(Relay_PIN, LOW);
  relaystate = false;
  Running == false;
}
// --------------------------
void loop() {

  String CommandStr;
  String ParameterStr;
  String ReplyMsg;

  if (stringComplete) {
    stringComplete = false;
    ParseSerialData(inputString, CommandStr, ParameterStr);
    inputString = "";

    if (CommandStr == "VERSION") {
      Serial.print(Version1Str);
      Serial.print(Version2Str);
      Serial.print(Version3Str);
      Serial.print(Version4Str);
      Serial.println("X");
    }

    if (CommandStr == "LIGA") {
      if (showdebug) {
        Serial.println("System Control is On.");
      }
      Serial.println("OKX");
      previousMillis = millis();
      TimeIni = previousMillis;
      Running = true;
    }


    if (CommandStr == "DESLIGA") {
      if (showdebug) {
        Serial.println("System Control is Off.");
      }
      Serial.println("OKX");
      digitalWrite(Relay_PIN, LOW);
      Running = false;
    }

    if (CommandStr == "SETDURATION") {        //<====
      ParseParameters(ParameterStr);          //<====
      duration = ParamStr[1].toInt();         //<====
      Serial.println("OKX");
      if (showdebug) {
        Serial.println("************************");
        Serial.print("Setduration changed to ");
        Serial.print(duration);
        Serial.println(" msec");
        Serial.println("************************");
      }
    }

       if (CommandStr == "SETTEMP") {
      ParseParameters(ParameterStr);
      setpoint = ParamStr[1].toFloat();
      setpointspan = ParamStr[2].toFloat();
      Serial.println("OKX");
      if (showdebug) {
        Serial.println("************************");
        Serial.print("Setpoint = ");
        Serial.print(setpoint);
        Serial.print ("°C with span of ");
        Serial.print (setpointspan);
        Serial.println ("°C");
        Serial.println("************************");
      }
    }

    if (CommandStr == "DATA") {
      Serial.print(millis());  //param0
      Serial.print(",");
      if (Running == true) {
        Serial.print(millis() - TimeIni);
      }
      else {
        Serial.print("0");
      }
      Serial.print(",");
      temperatura = termopar.readCelsius();
      Serial.print(temperatura);   // param1
      Serial.print(",");
      Serial.print(relaystate);  // param2
      Serial.print(",");
      Serial.print(setpoint);   // param3
      Serial.print(",");
      Serial.print(setpointspan);
      Serial.print(",");
      Serial.print(timeinterval);
      Serial.print(",");
      Serial.print(duration);
      Serial.println("X");
    }

  }

  if (Running == true) {
    temperatura = termopar.readCelsius();
    downtemp = setpoint - setpointspan;
    uppertemp = setpoint + setpointspan;
    timenow = millis() - TimeIni;
    if ((timenow / 1000) > duration) {
      Running = false;
    }
    if (showdebug) {
      Serial.println("min,max");
      Serial.print(downtemp);
      Serial.print(",");
      Serial.println(uppertemp);
      Serial.println("**************");
      Serial.print(temperatura);   // param1
      Serial.print(",");
      Serial.print(relaystate);  // param2
      Serial.print(",");
      Serial.print(setpoint);   // param3
      Serial.print(",");
      Serial.print(setpointspan);
      Serial.print(",");
      Serial.print(timeinterval);
      Serial.print(",");
      Serial.print(duration);
    }

    if ( temperatura < downtemp ) {

      if (relaystate == false) {
        digitalWrite(Relay_PIN, HIGH);
        relaystate = true;
        if (showdebug) {
          Serial.println("Ligando rele");
        }
      }
    }

    if ( temperatura > uppertemp ) {
      if (relaystate == true) {
        digitalWrite(Relay_PIN, LOW);
        relaystate = false;
        if (showdebug) {
          Serial.println("Desligando rele");
        }
      }
    }
  }
  else {
    digitalWrite(Relay_PIN, LOW);
    relaystate = false;
  }


  temperatura = termopar.readCelsius();
  delay(500);
  if (showdebug) {
    Serial.println(temperatura);
  }
}

/*
  SerialEvent occurs whenever a new data comes in the hardware serial RX. This
  routine is run between each time loop() runs, so using delay inside loop can
  delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
  unsigned long previousMillis = millis(); // refresh counter variable

  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();

    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it:
    unsigned long currentMillis = millis(); // refresh counter variable
    if ((currentMillis - previousMillis) >= 2000) {
      Serial.println("Error");
      inputString = "";
    }
    if ((inChar == 'X') || (inChar == 'x')) {
      inputString.toUpperCase();
      if (showdebug) {
        Serial.print("String Received : ");
        Serial.println(inputString);
      }
      stringComplete = true;
    }
  }
}

below the serial monitor

0.00
0.00
String Received : VERSION=?X
Parsing...
StringToParse : VERSION=?X
StrCommand :VERSION
strParameter:?
RogerThermoK V2.1 @ 17-Mar-2021 @ 11:00X
0.00
0.00
0.00
0.00
0.00
0.00
0.00

0.00
String Received : SETTEMP=100,5X
Parsing...
StringToParse : SETTEMP=100,5X
StrCommand :SETTEMP
strParameter:100,5
OKX
************************
Setpoint = 100.00°C with span of 5.00°C
************************
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
String Received : 5SETDURATION=3000X
Parsing...
StringToParse : 5SETDURATION=3000X
StrCommand :5SETDURATION
strParameter:3000
0.00
0.00

It seems to be something related to Strings.... always the Strings are the culprit !!!

I´ve just moved the one line in the code and .... now it works.

// ****************** Variáveis ******************************[color=#222222][/color]
boolean showdebug = true;                                                // to control debug message to be displayed[color=#222222][/color]
String Version1Str = "RogerThermoK V";                    // Version identification[color=#222222][/color]
String Version2Str="2.1 ";[color=#222222][/color]
String Version3Str="@ 17-Mar-2021 ";[color=#222222][/color]
String Version4Str="@ 11:00";[color=#222222][/color]
String VersionStr;[color=#222222][/color]

to

// ****************** Variáveis ******************************[color=#222222][/color]
String inputString = "";
boolean showdebug = true;                                                // to control debug message to be displayed[color=#222222][/color]
String Version1Str = "RogerThermoK V";                    // Version identification[color=#222222][/color]
String Version2Str="2.1 ";[color=#222222][/color]
String Version3Str="@ 17-Mar-2021 ";[color=#222222][/color]
String Version4Str="@ 11:00";[color=#222222][/color]
String VersionStr;[color=#222222][/color]

If someone can help me to throw away the Strings in my code, I appreciate it.

AngeloGomes:
If someone can help me to throw away the Strings in my code, I appreciate it.

I suggest you check out the Serial Input Basics tutorial then.
hope that helps...

Thank you.

I read it, although I disagree about the Serialevent comment there and I cannot ignore it as my Loop() performs time-consuming tasks.

As I said before, it seems that the problem is not the Serial, but something related to Strings memory leaking or something like that.

I have to stop use Strings everytime and everywhere.

AngeloGomes:
I read it, although I disagree about the Serialevent comment there and I cannot ignore it as my Loop() performs time-consuming tasks.

If you mean that your loop contains long delays, then your serial inputs will be ignored and possibly overflow. That will happen regardless of whether you use C strings or the String class.

The problem is not receiving data from Serial...

Guys, the problem is another one....

AngeloGomes:
The problem is not receiving data from Serial...

Guys, the problem is another one....

Okay, what is it?

The code attached seems to do what you want. Update (see later post for corrected code)

First check out my tutorial on Taming Arduino Strings for how to use Arduino Strings reliably, but not really your problem here.

There are a few problems with your code
i) serialEvent is not interrupt driven as you might expect. Rather is runs just once at the end of each call to loop() if Serial data is available so it is worse then collecting Serial data in your loop code because there you can collect data as often as you like during the loop(). I see by your comments you are aware of this.

ii) your Serial prints will slow your loop down.
a) increase the #define BAUDRATE_SERIAL_MONITOR 115200
b) checkout my tutorial on Arduino Serial I/O for the Real World for how to add a non-blocking Serial output buffer

I have replaced your serialEvent with a readStringUntil method
The advantage of using readStringUntil(input,'X',50) is you can call it multiple times in the loop() to keep checking for input and then call the processing method plus other methods

You can also increase the RX buffer size, see Arduino Serial I/O for the Real World for the details

The delay at the end of the loop is bad, I have replaced it with a printTemp() method and a timer

Sample output

 got a line of input 'SETTEMP=100,5X'
Parsing...
StringToParse : SETTEMP=100,5X
StrCommand :SETTEMP
strParameter:100,5
OKX
************************
Setpoint = 100.00°C with span of 5.00°C
************************
0.00
..
0.00
 got a line of input 'SETDURATION=3000X'
Parsing...
StringToParse : SETDURATION=3000X
StrCommand :SETDURATION
strParameter:3000
OKX
************************
Setduration changed to 3000 msec
************************

Looking through your code more closely I found that you are not indexing the parameters correctly
String ParamStr[2] ; // indices are 0, 1 NOT 1,2

attached is the corrected code.

serialCharLeft.ino (12.5 KB)

Just for interest here is a SafeString version of the code.
It uses SafeStringReader to replace all the readuntil code and
has much more robust toFloat() etc numeric conversions
Apart from replacing Strings with SafeStrings the main change to the code is to replace

strCommand = StringToParse.substring(0, iPositionCharX) ;

with

StringToParse.substring(strCommand, 0, iPositionCharX) ;

and

setpoint = ParamStr[0].toFloat();

with

      bool failure = false;
      cSFA(sfParamStr0, ParamStr[0]);
      if (sfParamStr0.toFloat(setpoint)) {        
      } else {
        failure = true;
        Serial.print(F("Invalid float:'")); Serial.print(sfParamStr0); Serial.println("'");
      }

Arduino String toFloat() (and the low level c-string method it is based on) will just return 0 for invalid floats.
SafeString.toFloat(result) returns false for invalid floats and leaves the result unchanged.

SF_serialCharLeft.ino (11.2 KB)

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.