serial communication more reliable?

Dear All,

I want to build a gauge for a big electrical motor. I get all informations about voltage, current, temperatures in a serial data stream (38400 baud), 25 byte long. I have no influence to the data stream, so I have to live with it. There's no start or stop sign or special byte mask to show beginnng or the end of the stream. With a checksum I can proof wether the stream in valid or not. The stream comes every 100ms and takes around 1ms to ransmit.

As long as I do nothing else, my sketch seems to work. But when I try to display the values on a connected LCD via SPI, the values are only 20% of the time in the exptected range (e.g. 0 to 10A for current).

The question is now, if you find my coding ok for this task or if I've made a basic mistake which consumes to much CPU cycles...

Here's my basic sketch:

byte PI300_data[25] = "";         // Datentelegramm vom PI300
boolean Data_valid = false;  // wenn Datentelegramm zulässig
byte i=0; //Zähler
float voltage = 0.00; // Akkuspannung
float current = 0.00; // Strom
int rpm =0; // Drehzahl
byte temp_batt = 0; // Akkutemperatur
byte temp_eng = 0; // Motortemperatur
byte temp_cont = 0; // Reglertemperatur
float capacity = 0.00; // Akkukapazität

void setup() {
  Serial.begin(38400); // initialize serial:
  Serial.setTimeout(4); //Setzt den Timeout auf 4ms. Wenn 4ms keine Daten vom PI300 kommen, ist das Datentelegramm komplett. 
}

void loop() {
if (Data_valid) { // print the string when a newline arrives:
  ParsePI300(); // Wenn Datentelegramm gültig, parsen
    Serial.print(voltage);
    Serial.println(" Volt");
    Serial.print(current);
    Serial.println(" A");
    Serial.print(capacity);
    Serial.println(" Ah");
    Serial.print(rpm);
    Serial.println(" U/Min.");
    Serial.print(temp_batt);
    Serial.println(" °C Akku");
    Serial.print(temp_eng);
    Serial.println(" °C Motor");
    Serial.print(temp_cont);
    Serial.println(" °C Steller");
    Serial.println("-----------");
    Data_valid=0;
  }
}

/*
  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() {
  i=Serial.readBytes(PI300_data, 25); // 25 Zeichen in den Buffer einlesen oder Timeout
  if (i = 25) { // Wenn wirklich 25 Zeichen gelesen wurden
    if (checksumOK) { //PI300-Telegram gültig, wenn Checksumme OK ist
      Data_valid = true;
    } else {
      Data_valid = false;  
    }
  }
}

boolean checksumOK()  { // Prüft die Checksumme des PI300-Datentelegramms. Nur das Lowbyte zählt.
  if (byte(PI300_data[0]+PI300_data[1]+PI300_data[2]+PI300_data[3]+PI300_data[4]+PI300_data[5]+PI300_data[6]+PI300_data[7]+PI300_data[8]+PI300_data[9]+PI300_data[10]+
      PI300_data[11]+PI300_data[12]+PI300_data[13]+PI300_data[14]+PI300_data[15]+PI300_data[16]+PI300_data[17]+PI300_data[18]+PI300_data[19]+PI300_data[20]+
      PI300_data[21]+PI300_data[22]+PI300_data[23]+0xAA) == PI300_data[24]) {
    return true; 
  }
  else {
    return false;
  }
}

void ParsePI300()  { // Parst das PI300-Datentelegramm in die einzlenen Werte
  voltage = float(word(PI300_data[4],PI300_data[3]))*0.01;
  current = (float(word(PI300_data[6],PI300_data[5]))-512)*0.7815;
  rpm = int(word(PI300_data[8],PI300_data[7]));
  temp_batt = PI300_data[9];
  temp_eng = PI300_data[10];
  temp_cont = PI300_data[11];
  capacity = float(word(PI300_data[17],PI300_data[16]))*0.1;
}

Thanks Peter

  if (i = 25) { // Wenn wirklich 25 Zeichen gelesen wurden

Why are you assigning 25 to i in an if statement?

My guess is that using Serial.readbytes() is blocking the Arduino for too long. And Arduino can work much faster than data can arrive.

I'm not a fan of serialEvent() as it is nothing more than you get with if (Serial.available() > 0).

Rather than use serialEvent() you could just check if (Serial.available() >= 25). Then the data will already be waiting for Serial.readbytes() to work on immediately.

Based on what you said

The stream comes every 100ms and takes around 1ms to ransmit.

I reckon you can treat the time-gap between messages as a delimiter and that would provide a more reliable system that would not get hung up waiting for data to arrive. Also it would guarantee that no message was a composite of the end and start of two other messages.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. It should be straightforward to modify the second example to watch for the long gap between messages and use that as an end-marker.

...R

Hi Paul,

grmpft, beginner mistake. Should be

if (i == 25) {

:-)

Thanks Peter

Thanks Robin,

I'll check the way of using the time gap as an end marker.

Thanks Peter

Bricki: I'll check the way of using the time gap as an end marker.

Save the value of micros() when each character is read and then check

if (micros() - mostRecentCharMicros >= suitableInterval) {
   // we are in the gap
}

...R

I want to build a gauge for a big electrical motor. I get all informations about voltage, current, temperatures in a serial data stream (38400 baud), 25 byte long. I have no influence to the data stream, so I have to live with it. There’s no start or stop sign or special byte mask to show beginnng or the end of the stream. With a checksum I can proof wether the stream in valid or not. The stream comes every 100ms and takes around 1ms to ransmit.

I don’t know how you get to 1ms but as the numbers are related there must be an error somewhere. Using 38400 baud transferring 25 bytes needs about 6.5ms (8N1). I agree that there’s enough pause between transfers but the busy part on the line is much more than you describe.

As long as I do nothing else, my sketch seems to work. But when I try to display the values on a connected LCD via SPI, the values are only 20% of the time in the exptected range (e.g. 0 to 10A for current).

It would help to see that sketch that makes you problems as the posted one seems to run ok.

Hi Guys,

@Robin: Thanks a lot! Now it works! I’m loosing only one packet of data right at the start. After that, I get every packet with a correct checksum. None of the values are jumping between expected and unexpected values anymore! Thank you for your help…

@pylon: You’re right. One data packet needs around 6ms, but with Robins hint, I’m checking now, if there is a pause of more than 5ms between two characters and if yes, I just read the first byte of the new packet and copy the last 25 byte from the buffer into my data stream to parse.

void loop(void) {
  if (Serial.available() > 0) { // Zeichen empfangen?
    if (millis() > lastrec+5) { // ist letztes Zeichen länger als 5ms her?
      if (i >= 24) { // mndestens 25 Zeichen empfangen?
        for (j = 0; j < 25; j = j + 1) { // die letzten 25 Zeichen des Buffers als PI300-Daten kopieren
          PI300_data[j]=buf[i-25+j];
        }  
      }
      i=0; // Pointer auf den Buffer wieder auf 0 setzen
      if (checksumOK()) { //PI300-Telegram gültig, wenn Checksumme OK ist
        ParsePI300();
        Data_valid=true;
      }  
    }
    while (Serial.available() > 0) { // solange Zeichen verfügbar sind
      buf[i] = Serial.read(); // lese Zeichen in den Buffer
      i++;
    }
  lastrec=millis(); //Zeit für letztes empfangenes Zeichen speichern
  }

Now I have to check and optimize the output routines to the TFT-Display, cause numerical output are flickering, cause I have to write the old value in black before I write the new value at the same position. This consumes to much time for comaring old and new value and print both values, old and new. But that’s another story…

Thanks
Peter