Serial timing/buffering issue has me flummoxed

I’m running into a problem of bytes getting dropped in a low-volume but bursty serial transfer from a Mega (sender) to a NodeMCU (receiver). Serial2 on the Mega is connected to a level-shifter which then connects to D5/D6 on the NodeMCU. I’m using a very simple call-response between the boards. Every five seconds, the sender checks for an incoming ‘S’, sends 4 bytes of data, and then repeats (2 4-byte “packets” per cycle) The receiver, which is using SoftwareSerial, loops on sending a request ‘S’ then uses readBytes to read up to 4 bytes. If I put a 20msec delay in the sender between packets, everything is fine. If I take the delay out, however, the receiver is missing bytes, in a very regular pattern. With the very low volume of data, buffer overflow shouldn’t be an issue, there must be something else going on here, but I have no idea what! Can anyone offer some guidance?

Here’s what the sender is sending:

write: 0 1 2 3 
write: 4 5 6 7 

write: 8 9 10 11 
write: 12 13 14 15 

write: 16 17 18 19 
write: 20 21 22 23

Here’s what the receiver is receiving (without the 20ms delay in the sender)

read (4): 0 1 2 3 
read (1): 7 
read (4): 8 9 10 11 
read (1): 15 
read (4): 16 17 18 19 
read (1): 23

Here’s the sender (Mega) code:

void setup() {
  Serial2.begin(9600); // Serial communication to NodeMCU
  Serial.begin(9600);
  delay(1000);
}

uint8_t offset = 0;
unsigned long cachedTime = millis();

void loop() {
  if ((millis() - cachedTime) > 5000) {  // send a burst every 5 seconds
    for (uint8_t packet = 0; packet < 2; ++packet) {  // 2 transfers in quick succession
      if (Serial2.available() > 0) {  // do we have a request?
        int readIn = Serial2.read();  // consume incoming request
        if (readIn == 'S') {   // got a request, assemble data and send it
          Serial.print(F("write: "));
          for (uint8_t i = 0; i < 4; ++i) {   // load data and transfer
            byte transferByte = i + packet * 4 + offset;
            Serial2.write(transferByte);  // transfer byte
            Serial.print(transferByte);
            Serial.print(F(" "));
          }
          Serial.println();
          Serial.flush();
        }
      }
      //delay(20);   // With this delay active, the problem disappears!
    }
    Serial.println();
    Serial.flush();
    cachedTime = millis();
    offset += 8;
  }
}

Here’s the receiver (NodeMCU) code:

#include <SoftwareSerial.h>

SoftwareSerial synControlSerial(D5, D6);   //  D5 is RX,   D6 is TX for synth controller

void setup() {
  Serial.begin(9600);
  synControlSerial.begin(9600);
  delay(1000);
}

byte transferBytes[4];

void loop() {
  synControlSerial.print('S');  // send request
  synControlSerial.flush();

  int count = synControlSerial.readBytes(transferBytes, 4);  // read data, if none available, will timeout
  if (count > 0) {
    Serial.print(F("read ("));
    Serial.print(count);
    Serial.print(F("): "));
    for (uint8_t i = 0; i < count; ++i) {
      Serial.print(transferBytes[i]);
      Serial.print(F(" "));
    }
    Serial.println();
    Serial.flush();
  }
}

I think you have to redesign the serial communication.

Either send every 5 seconds data, or wait for a command and immediately return data.
When the NodeMCU sends a 'S', why should it wait up to 5 seconds ?

Can you add round brackets here: i + packet * 4 + offset;

The nodeMCU uses .readBytes().
That has a default timeout of 1 second, but the Mega could wait up to 5 seconds.
It reads 4 bytes, but how do you know that those 4 bytes belong to the same package ?

Why do you use the .flush() so often. Can you remove all of them ?

I don't know why you get that pattern. Perhaps the 5 second timer with the 1 second timeout results into that. However, that is not important. You have to make it reliable.

When you transfer data as readable ASCII, then it is easer to put a a special byte at the start and a special byte at the end. Something simple is only a CarriageReturn or LineFeed (or both) at the end.

The Serial library has buffers, and the sketch on both sides coud be doing other things. In such a situation it is possible to split the transmitted data en receiving data. Make it two seperate parts in the sketch. In that case, you have to add something to determine if the command was actually received.

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