Speeding up my Slow Serial!?

Hi everyone… I am using the serial function to transfer data between two Arduinos and I have noticed a bit of an issue. If the message is too long, then I get mis-reads and errors in the code which is causing some issues.

I’m transferring a couple of different numbers, the longer of the two is an integer between 0 and 7000. The way I transmit is like this:

    if (SPEED_MPH != SPEED_SEND) {
      SPEED_SEND = SPEED_MPH;
      Serial1.print('<');
      Serial1.print('S');
      Serial1.print(SPEED_SEND);
      Serial1.print('>');
    }

    if (RPM != RPM_SEND) {
      RPM_SEND = RPM;
      Serial1.print('<');
      Serial1.print('R');
      Serial1.print(RPM_SEND);
      Serial1.print('>');
    }

So for the longer of the two possible messages (RPM), the structure maximum size can be “”. Performance seems to improve a lot if I reduce the length by 1 character by dividing the number by 10 but that’s not ideal long term, so I need to reduce the length by at least 1, possible 2 characters which got me thinking. Is there a more efficient way to transmit the data by sending it as a Hex number or some other form?

Would love to hear some suggestions!

Thank you

What baud rate? How often do you transmit? What does the receiver code look like?

Baud rate is 250000 and the receiver code looks like this. I have played around with running the code up to every 10-20ms but it seems to run much more smoothly if I run it every cycle (on a Teensy 3.2).

void Get_Serial_Data() {
  //inChar = Serial1.read();

  if (Serial1.available() > 0) {
    rc = Serial1.read();
    if (rc == '>') {

      if (readString.indexOf("S") > 0) {  //is speed in the data packet?
        StrS1 = readString.indexOf('S');  //finds start location
        StrF1 = readString.lastIndexOf('>'); //finds finish location
        Pkt1 = readString.substring(StrS1 + 1, StrF1); //captures data String
      }

      if (readString.indexOf("R") > 0) {  //is rpm in the data packet?
        StrS2 = readString.indexOf('R');  //finds start location
        StrF2 = readString.lastIndexOf('>'); //finds finish location
        Pkt2 = readString.substring(StrS2 + 1, StrF2); //captures data String
      }


      readString = ""; //clears variable for new input

      //Read Serial Values from StepperDuino
      SPEED_MPH_SER = Pkt1.toInt();
      if (SPEED_MPH_SER >= 0 && MODE_SPEED == 1) SPEED_MPH = SPEED_MPH_SER;

      RPM_SER = Pkt2.toInt();
      if (RPM_SER >= 0 && MODE_RPM == 1) RPM = RPM_SER;


    }
    else {
      readString += rc; //makes the string readString
    }
  }
}

Depending on how variable the data is, even at that baud rate you could be filling up the receive buffer which will cause data to be dropped. Why do you need to send it so frequently?

I would go back to putting a time limit on how often you send data.

Also, I would abandon the use of String objects. You can parse that data with cstrings easily enough. Teensy has plenty of RAM for them, but they are slower and unnecessary.

Thanks Bill, I don't really need it that frequently - it's just driving a display so as fast as will be unnoticeable to the viewer would be ok - what kind of delay would you recommend?

I'm not sure what you mean about the string vs Cstring objects - can you explain a little more? Thanks!

I would think every 100ms would be amply fast. I assume also that there must be some minimum time spent getting a new RPM measurement, so there's no point sending it any faster than that anyway. Speed may well have a similar constraint.

As to Strings, they are a curse on a small Arduino type device because they use dynamic memory allocation which can lead to memory fragmentation and eventual crashes. Given that you have 64K of RAM, that is not really a concern, but they will still be slower than plain old null terminated C strings.

If you need speed C strings are a better choice. If you decide to send the data less frequently though, don't worry about it.

450nick:
I'm not sure what you mean about the string vs Cstring objects - can you explain a little more? Thanks!

Here is the best explanation that I've seen:

It occurs to me that even at 100ms, the display may be too flickery to be readable. Maybe start there with the expectation that you might have to slow it down.

Performance seems to improve a lot if I reduce the length by 1 character by dividing the number by 10 but that’s not ideal long term, so I need to reduce the length by at least 1, possible 2 characters which got me thinking. Is there a more efficient way to transmit the data by sending it as a Hex number or some other form?

You never use the start marker ‘<’ in your reading routine, so that can be eliminated from the sending routine.

Sending the data as 2 byte integers rather than as text will also eliminate all the String manipulation and conversion on the receiving end.

It would be good to review Robin2’s tutorial on Serial Input Basics

cattledog:
You never use the start marker ‘<’ in your reading routine, so that can be eliminated from the sending routine.

Sending the data as 2 byte integers rather than as text will also eliminate all the String manipulation and conversion on the receiving end.

It would be good to review Robin2’s tutorial on Serial Input Basics

Thanks guys I will look into this… On the point about the “<”, I noticed that if I don’t have a character before the first one I want to read, the read code does not see the message, so I left it in there. Any idea why this is the case as I would indeed like to remove it.

On the point about the “<”, I noticed that if I don’t have a character before the first one I want to read, the read code does not see the message, so I left it in there. Any idea why this is the case as I would indeed like to remove it.

if (readString.indexOf("S") > 0)
if (readString.indexOf("R") > 0)

Without the ‘<’ the character index of these letters is = 0.

Thanks Cattledog - that has helped a lot. So now I’ve got rid of the leading < and have divided the RPM signal by 10 to give me a maximum packet length currently of 4 characters.

I still see some mis-reads, and I’ve tried playing with the interval period between sends and receipts but it occurred to me that my send code will potentially send a number of messages with zero gap between them, even if I’m only executing the send code every few milliseconds. The receive code then will read the first message but if the second arrives while it’s still processing the first then perhaps this is causing the problem?

I think maybe then I need a better send code, presently it will only send values that have changed, but I need to add something in there to make sure it will wait between sending each packet. I can’t think of a good way to do this - any ideas? Here’s the current sending code (I have 4 variables but am just working on 2 to keep it simple for now):

void Send_Serial_Data() {

  if (SPEED_MPH != SPEED_SEND) {
    SPEED_SEND = SPEED_MPH;
    Serial1.print('S');
    Serial1.print(SPEED_SEND);
    Serial1.print('>');
  }

  if (RPM != RPM_SEND) {
    RPM_SEND = RPM;
    Serial1.print('R');
    Serial1.print(RPM_SEND / 10);
    Serial1.print('>');
  }

//  if (COOLANT_TEMP != CT_SEND) {
//    CT_SEND = COOLANT_TEMP;
//    Serial1.print('T');
//    Serial1.print(CT_SEND);
//    Serial1.print('>');
//  }
//
//  if (FUEL_LEVEL != FL_SEND) {
//    FL_SEND = FUEL_LEVEL;
//    Serial1.print('F');
//    Serial1.print(FL_SEND);
//    Serial1.print('>');
//  }

}

Use millis. Blink without delay shows you how to use it in a way that's directly applicable to what you're doing.

Thanks Bill,

I understand how to use millis, but for example say both RPM and Speed have changed, then once the sending code triggers I'll need to send Speed, then wait and send RPM after a certain time has passed.

I'm not sure how to do this without blocking the code and preventing other things from happening...? I guess I need something like a queuing system that will look at what needs to be sent from the 4 variables (speed/rpm/fuel level/coolant temp) and send what has changed but with an appropriate gap between packets.

Just use the BWD method to send every so often. When you decide it's time to send, send whichever value has changed. Or both. Or none.

There's a 64 character buffer for Serial at each end, so it's not going to overflow if both have changed.

So am I building my messages wrong? Presently I'm doing something like:

    Serial1.print('S');
    Serial1.print(SPEED_SEND);
    Serial1.print('>');
    Serial1.print('R');
    Serial1.print(RPM_SEND / 10);
    Serial1.print('>');

Should I really be doing something like this??:

SerialMessage = "&" && SPEED_SEND && "R" && (RPM_SEND/10) && ">";
Serial1.print(SerialMessage);

or like this:

    Serial1.print('S');
    Serial1.print(SPEED_SEND);
    Serial1.print('R');
    Serial1.print(RPM_SEND / 10);
    Serial1.print('>');

No, you can do just what you're doing now. The only difference is that you only do it periodically. That is, you use BWD to see if it's time to send. Then you use what you have that only sends changed values, which may be none, one or two.

I'm confused.. I thought the problem here was that the large message was taking too long to send, so I should be sending smaller packets with gaps in between them for processing time?

To do this I would need to figure out what needed to be sent and then send the small packets but ensure that each one had a gap in between. I already have a piece of code that can put an interval in the calling of the serial send function, but if I increase this value, it doesn't solve the problem of fragmented messages, it just slows everything down. I think the problem was that each time I executed the send code I was sending more than one message with no gap between.

450nick:
I'm confused.. I thought the problem here was that the large message was taking too long to send, so I should be sending smaller packets with gaps in between them for processing time?

I don't think so. If you send continuously, you might fill the buffer, but with even a small delay and just sending a few characters, it should be fine.

What do you see if you send the data to a terminal program? Is it clean?

Your baud rate might be too high or wires too long and you're getting corruption. Or your receiver code has a flaw perhaps.

I suggest that you use the version you have with the gap between sending and then look elsewhere for the problem.

wildbill:
I don't think so. If you send continuously, you might fill the buffer, but with even a small delay and just sending a few characters, it should be fine.

What do you see if you send the data to a terminal program? Is it clean?

Your baud rate might be too high or wires too long and you're getting corruption. Or your receiver code has a flaw perhaps.

I suggest that you use the version you have with the gap between sending and then look elsewhere for the problem.

Ok so I've set a delay of 5ms between send bursts, and I've set the send code to:

void Send_Serial_Data() {

  if (SPEED_MPH != SPEED_SEND) {
    SPEED_SEND = SPEED_MPH;
    Serial1.print('S');
    Serial1.print(SPEED_SEND);
    Serial1.print('S');
    //Serial1.print('>');
  }

  if (RPM != RPM_SEND) {
    RPM_SEND = RPM;
    Serial1.print('R');
    Serial1.print(RPM_SEND / 10);
    Serial1.print('R');
    //Serial1.print('>');
  }

//  if (COOLANT_TEMP != CT_SEND) {
//    CT_SEND = COOLANT_TEMP;
//    Serial1.print('T');
//    Serial1.print(CT_SEND);
//    Serial1.print('>');
//  }
//
//  if (FUEL_LEVEL != FL_SEND) {
//    FL_SEND = FUEL_LEVEL;
//    Serial1.print('F');
//    Serial1.print(FL_SEND);
//    Serial1.print('>');
//  }
Serial1.print('>');
}

I've set the receive code to:

void Get_Serial_Data() {
  //inChar = Serial1.read();

  if (Serial1.available() > 0) {
    rc = Serial1.read();
    if (rc == '>') {

      if (readString.indexOf("S") >= 0) {  //is speed in the data packet?
        StrS1 = readString.indexOf('S');  //finds start location
        Serial.println(StrS1);
        StrF1 = readString.lastIndexOf('S'); //finds finish location
        Pkt1 = readString.substring(StrS1 + 1, StrF1); //captures data String
      }

      if (readString.indexOf("R") >= 0) {  //is rpm in the data packet?
        StrS2 = readString.indexOf('R');  //finds start location
        StrF2 = readString.lastIndexOf('R'); //finds finish location
        Pkt2 = readString.substring(StrS2 + 1, StrF2); //captures data String
      }

      if (readString.indexOf("T") >= 0) {  //is temp in the data packet?
        StrS3 = readString.indexOf('T');  //finds start location
        StrF3 = readString.lastIndexOf('T'); //finds finish location
        Pkt3 = readString.substring(StrS3 + 1, StrF3); //captures data String
      }

      if (readString.indexOf("F") >= 0) {  //is fuel in the data packet?
        StrS4 = readString.indexOf('F');  //finds start location
        StrF4 = readString.lastIndexOf('F'); //finds finish location
        Pkt4 = readString.substring(StrS4 + 1, StrF4); //captures data String
      }

      Serial.print("Speed = ");
      Serial.println(Pkt1);
      Serial.print("RPM = ");
      Serial.println(Pkt2);


      readString = ""; //clears variable for new input

      //Read Serial Values from StepperDuino
      SPEED_MPH_SER = Pkt1.toInt();
      if (SPEED_MPH_SER >= 0 && MODE_SPEED == 1) SPEED_MPH = SPEED_MPH_SER;

      RPM_SER = Pkt2.toInt() * 10;
      if (RPM_SER >= 0 && MODE_RPM == 1) RPM = RPM_SER;

      COOLANT_TEMP_SER = Pkt3.toInt();
      if (COOLANT_TEMP_SER >= 0 && MODE_COOLANT_TEMP == 1) COOLANT_TEMP = COOLANT_TEMP_SER;

      FUEL_LEVEL = Pkt4.toInt();

    }
    else {
      readString += rc; //makes the string readString
    }
  }
}

I have another issue now in that I'm not seeing any numbers received. How do I send the data to a terminal like you describe? I'd love to see a proper stream of what's coming in to understand what the problem is...