Multiple Serial Port Communication at Max Speed

I need some help with my Project on an Arduino Due

I need to specifically communicate with 1 of 4 devices as fast as possible.

This communication works perfectly, or appears to, until there is some sort of a delay brought in either by the delay function or when one of the other serial devices use a delay.

I have only included the code for the single device communication.

  • The Device waits for a request before sending data.
  • I need 3 sets of data of which 2 are only required at startup.
  • The real time data I require as fast as possible.
  • There is no start identifier, but there is and end identifier which is the same as the command you send to request.
  • Each of these commands send a different length of bytes

I have 3 other devices that I communicate with of which two do not require speed

  1. GPS Using TinyGPS library
  2. Gyro Using the DFRobot_WT61PC library
  3. Nextion 7 Inch.

The Nextion screen I also need to communicate to as quickly as possible.

Please could you look at my approach and let me know where I am going wrong. I think it could have to do with buffer overuns as it appears to not receive the end character for quite a while ( maybe 30 seconds ) then some data comes through and so on. When the data comes through I always have the full set of data requested. It just takes random amounts of time to return it.

const static int numChars = 1794;  // Set this to the size of the parameter data array received from the device which is the biggest array returned
int receivedBytes[numChars];   // to accumulate and store the received data
int recindex = 0;

const int  RealtimeDataMessage = 252;
const int  ParameterDataMessage = 253;
const int  DeviceInfoDataMessage = 255;

bool receivedDeviceParameters = false;
bool receivedDeviceInfo = false;
bool canRequest = true;

int RequestSequence = 1;


        /* Variables for Timed Events */
unsigned long TimeOutPeriod = 2000;  // ms
unsigned long previousTime = 0;  // ms


void setup() 
{
  Serial.begin(115200);
  Serial1.begin(19200);
}

void loop() 
{
  unsigned long sp_currentTime = millis();
  
  RequestDeviceData();
  ReadDeviceSerialData();
  if(sp_currentTime - previousTime >= TimeOutPeriod && !canRequest )
  {
    canRequest = true;
    previousTime = sp_currentTime;
  }
}


void RequestDeviceData()
{
  if(!canRequest)
  {
    return;
  }
  
  canRequest = false;

  if(receivedDeviceParameters && receivedDeviceInfo)
  {
    RequestSequence = 3;
  }

  if(RequestSequence == 1)
  {
    if(!receivedDeviceParameters) // Only need to get this once at start up
    {
      if (Serial1.availableForWrite())
      {
        Serial1.write(ParameterDataMessage); 
      }
    }
  }

  if(RequestSequence == 2)
  {
    if(!receivedDeviceInfo) // Only need to get this once at start up
    {
      if (Serial1.availableForWrite())
      {
        Serial1.write(DeviceInfoDataMessage); 
      }
    }
  }

  if(RequestSequence == 3)
  {
    if (Serial1.availableForWrite())
    {
      Serial1.write(RealtimeDataMessage); // Need this as quicky as we can possibly read
    }
  }

  RequestSequence += 1;
  if(RequestSequence > 3)
  {
      RequestSequence = 1;
  }
  //delay(1000);  By implementing this delay I get mixed results.
}


void ReadDeviceSerialData()
{
  
  if (Serial1.available() > 0) 
  {
    // Read in request
    int inByte = Serial1.read();
    receivedBytes[recindex] = inByte;
    recindex+=1;

    switch(inByte) 
    {
      case RealtimeDataMessage: // Check for the terminating byte
        
        if(recindex == 14) // Length of the expected message is 14 bytes
        {
          processdata(recindex);
          canRequest = true;
          recindex = 0;
        }

      break;

      case ParameterDataMessage:
        if(recindex == 1794) // Length of the expected message is 1794 bytes
        {
          receivedDeviceParameters = true;
          processdata(recindex);
          canRequest = true;
          recindex = 0;
        }

      break;
      
      case DeviceInfoDataMessage:
        if(recindex == 17 || recindex == 28) // Length of the expected message is 17 or 28 depending on the device type
        {
          receivedDeviceInfo = true;
          processdata(recindex);
          canRequest = true;
          recindex = 0;
        }

      break;
    }
   
  }
}

void processdata(int bytecount)
{

  for(int i = 0; i < bytecount; i++)
  {
    Serial.println(receivedBytes[i]);
  }

  Serial.println("Completed Processing. : ");
  Serial.println(receivedDeviceParameters);
  Serial.println(receivedDeviceInfo);
  Serial.println("");
}


You could preferably use flow charts to describe the flow of execution instead of word sallad.

Depending on the firmware a delay() can really stop a task for the indicated time. If there is a Due forum ask your questions there.

If I understand the delay function correctly, it is working and doing exactly what it should and essentially freezes the program for that amount of time? I am wanting to know if that pause in time caused by the delay, or even a long running process is enough for the Serial port to discard bytes in that period if the data coming in is bigger than the serial port buffer. If so what approach should be taken? Use more than one Arduino to communicate to each device and send processed data to a single Arduino?

The meaning of "program" varies across frameworks. On RTOS based controllers run (multiple) tasks which are suspended by delay(). Using millis() for delays keeps a task busy and can block its core.

If you are expecting input then you should handle it in time. If a buffer overrun occurs your code is too slow.

A delay() preceding Serial.read() (in the same "task") will cause lost data if the Serial input buffer is smaller than the amount of data that is sent to it in that time. ("delay" here includes any processing time used by other code as well.)

The usual "fixes" include:

  1. avoid delay()
  2. increase serial input buffer size.
  3. implement some sort of flow-control.

Due has lots of RAM. How big are your (biggest) messages?
Could a device be sending more than one message before the receiver gets a chance to read the first one, for whatever reason?

Thank you for your reply. The delay in the code is only for my testing purposes, to try and simulate the time the other 3 devices take to complete processing on the other 3 serial ports. I only have one message that is 1794 bytes, but only has to be read once at start up. I haven't included it in the start section because I want to retry getting it if it doesn't come through. The real time data is only 14 bytes and reads 100% without the other devices.

I am using libraries for the other 3 devices. I would not presume to think I could improve on those, so I am not sure there is anything I can do to speed the basic read of that, especially the GPS data.

I know serial is slow , so how does one go about reading many serial devices when at least for one, its critical that the read speed is as fast as possible?

I think I have prevented a message from been requested again until I get a response from the request. I may have overlooked something though?

"As fast as possible" is a pretty meaningless phrase.
If you have a Serial Port that is being sent large amounts of data at 115200, and it has an input buffer of 128 bytes (Due default), then you MUST read that data at least every 11 ms. You can do a lot of processing in 11ms, but you can't delay() for 15ms, nor wait for 20 bytes to show up on your 9600bps "other" serial port.

Your code looks pretty good; I don't see it waiting to read data, or even to write data (although it does potentially block in processdata(), writing several times the amount of input data to Serial.)
A more formally structured state machine might be better.
This looks like a bug/typo:

 if(RequestSequence == 3)
  {
    if (Serial.availableForWrite())
    {
      Serial1.write(RealtimeDataMessage); // Need this as quicky as we can possibly read
    }
  }

(shouldn't that be "Serial1.avaiableForWrite()" ?)

int receivedBytes[numChars];   // to accumulate and store the received data

Did you save room for a terminating null? (Do you need to?)
You don't check for exceeding the length of the buffer, so really bad things are likely to happen if you DO miss part of the message, and end up trying to fit (parts of) two messages in the buffer.

Also, I don't understand:

The Nextion screen I also need to communicate to as quickly as possible.

The Nextion is a human readable display, right? One useful optimization that is frequently overlooked is not to write to human-oriented displays faster than humans can read... (but your example didn't show the sort of data you would actually be writing.)

I am using libraries for the other 3 devices. I would not presume to think I could improve on those

Unfortunately, many libraries use delay(), and may need to be modified when used in conjunction with other time-critical things. :frowning:

Thank you for taking the time to reply.

Yes that is a typo. I have copied the code out of the main project to test on another Arduino so I don't have to keep disconnecting everything.

This makes sense regarding the speed at which the port needs to be read. I am thinking about using another Arduino for this device and pass the data back to the DUE via I2C?

As fast as possible

means that as soon as I get the message back I want to ask immediately for the next one and not wait.

This is just for debugging, it is not part of the main project code.

Thank you for that info, I overlooked that, I also don't need a terminating null.

Yes I agree with your sentiments regarding the speed at what humans are able to read. This project is for a new DIY engine management system which I installed into my vehicle. The main reason for "as fast as possible" is so that I do not see jerky readings, there is nothing critical about the application. The two readings in particular are the RPM and the inclinometer graphics. I have attached one screen which may give a better idea.

With data.

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