SerialX (X=1-4) are unbuffered? surprised

I just started playing with a new GIGA, and thought I would start off simple and try out some of the basic things, like GPIO, UARTS, SPI and the like.

One of the Serial tests, I like to try is to daisy chain one serial port to the next to see how well they work together. Which I expected that would work as the cheat sheet shows:

This not only means that you may print different values to different ports and monitor them separately, which is useful enough in and of itself, but that you may also communicate with 4 different serial enabled devices simultaneously.

Here is a modified version of one of my normal tests, that I updated from the UNO R4 version, that I have/had working with an update to the UART code (PR pending)

l2 TX -> Serial3 RX, Serial3 TX -> Serial4 RX....


#define SPD 115200
int loop_count = 0;

#define BUFFER_SIZE 80

class BufferInfoClass {
public:
  BufferInfoClass() {
    clear();
  }
  char buffer[BUFFER_SIZE];
  uint8_t cb_buffer;
  uint8_t cb_copy[BUFFER_SIZE];
  uint8_t cb_copy_cnt;
  void clear() {
    cb_buffer = 0;
    cb_copy_cnt = 0;
  }
};

BufferInfoClass buffers[5];
uint32_t millis_last_input = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  while (!Serial && millis() < 4000)
    ;
  Serial.begin(115200);
  delay(800);
  Serial.println("Test all Serials");
  Serial.print("Baud rate: ");
  Serial.println(SPD, DEC);
  Serial1.begin(SPD);
  Serial2.begin(SPD);
  Serial3.begin(SPD);
  Serial4.begin(SPD);
}

#define MIN(a, b) ((a) <= (b) ? (a) : (b))


void CopyFromTo(Stream &SerialF, Stream &SerialT, uint8_t buffer_index) {
  int available;
  int available_for_write;
  int cb;
  BufferInfoClass *buf = &buffers[buffer_index];
  if ((available = SerialF.available()) != 0) {
    Serial.print(buffer_index, DEC);
    Serial.print(":Avail: ");
    Serial.print(available, DEC);
    available_for_write = SerialT.availableForWrite();
    Serial.print(" ");
    Serial.println(available_for_write, DEC);
    cb = MIN(MIN(available, available_for_write), BUFFER_SIZE);
    if (cb) {
      SerialF.readBytes(&buf->buffer[buf->cb_buffer], cb);
      SerialT.write(&buf->buffer[buf->cb_buffer], cb);
      buf->cb_buffer += cb;
      buf->cb_copy[buf->cb_copy_cnt] = cb;
      buf->cb_copy_cnt++;
      millis_last_input = millis();
    }
  }
}

void memory_dump(const char *pb, uint8_t cb) {
  const char *pbA = pb;
  uint8_t cbA = cb;

  Serial.print("\t");
  for (uint8_t i = 0; i < cb; i++) {
    if (*pb < 0x10) Serial.write('0');
    Serial.print(*pb, HEX);
    Serial.print(" ");
    pb++;
  }

  Serial.print("\n\t ");
  for (uint8_t i = 0; i < cbA; i++) {
    if (*pbA >= ' ') Serial.write(*pbA);
    else Serial.write(' ');
    Serial.print("  ");
    pbA++;
  }
  Serial.println();
}

void print_buffer_header(uint8_t index) {
  BufferInfoClass *buf = &buffers[index];
  Serial.print("  ");
  Serial.print(buf->cb_buffer, DEC);
  Serial.print(" (");
  for (uint8_t i = 0; i < buf->cb_copy_cnt; i++) {
    if (i != 0) Serial.print(",");
    Serial.print(buf->cb_copy[i], DEC);
  }
  Serial.print(")");
}

void CompareBuffers(uint8_t index1, uint8_t index2) {
  if (buffers[index1].cb_buffer == buffers[index2].cb_buffer) {
    if (memcmp(buffers[index1].buffer, buffers[index2].buffer, buffers[index1].cb_buffer) == 0) {
      Serial.println(" ** Match **");
      return;
    } else {
      Serial.println(" ** different **");
    }
  } else {
    Serial.println(" ** counts different **");
  }
  memory_dump(buffers[index1].buffer, buffers[index1].cb_buffer);
  memory_dump(buffers[index2].buffer, buffers[index2].cb_buffer);
}

void loop() {
  CopyFromTo(Serial, Serial1, 0);
  CopyFromTo(Serial1, Serial, 1);
  CopyFromTo(Serial2, Serial2, 2);
  CopyFromTo(Serial3, Serial3, 3);
  CopyFromTo(Serial4, Serial4, 4);

  // now see if we should compare the data yet or not
  if (buffers[0].cb_buffer && ((millis() - millis_last_input) > 100)) {
    Serial.println("Check buffers: ");

    print_buffer_header(0);
    Serial.println();

    for (uint8_t i = 1; i < 5; i++) {
      print_buffer_header(i);
      CompareBuffers(0, i);
    }
    for (uint8_t i = 0; i < 5; i++) buffers[i].clear();
  }

  digitalWrite(LED_BUILTIN, HIGH);
  delayMicroseconds(100);  // give time form things to propagate
  digitalWrite(LED_BUILTIN, LOW);
}

I added some debug prints in the code, which confirmed what I expected.
The call Serial1.availableForWrite() will always return 0.

Digging some into the code base, I see that the Serial objects are using the
UnbufferedSerial class internally. I am not sure where the actual unbuffered write code is, as the UnbufferedSerial.h file shows the write methods as overwrite...

SerialX.flush() does not appear to work either:
That is my expectations is that it will not return, until the Serial transfer has been completed.

Simple sketch:

void setup() {
  Serial1.begin(115200);
  pinMode(2, OUTPUT);
}

void loop() {
  digitalWrite(2, HIGH);
  Serial1.print("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  Serial1.flush();
  digitalWrite(2, LOW);
  delay(1);
  digitalWrite(2, HIGH);
  for (char ch = 'a'; ch <= 'z'; ch++) Serial1.write(ch);
  Serial1.flush();
  digitalWrite(2, LOW);

  delay(1000);
}


My expectations are that the pin 2 should not go low, until the last bits are sent.

Now back to more playing

1 Like

Quick notes:
I ran the same sketch minus debug outputs on a different board...

abcdefghijklmnopqrstuvwxyz
Check buffers: 
  27 (20,7)
  27 (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1) ** Match **
  27 (1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,2,1,1,1,2,1,1,1) ** Match **
  27 (1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,2,1,1,1) ** Match **
  27 (1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1) ** Match **

Note: Normally I would extend this to cover the other Serial ports (7 total)

Also I could also update to use the Scheduler, maybe something like:

//connect  Serial1 TX -> Serial2 RX, Serial2 TX -> Serial3 RX, Serial3 TX -> Serial4 RX....
// Include Scheduler since we want to manage multiple tasks.
#include <Scheduler.h>


#define SPD 115200
int loop_count = 0;

#define BUFFER_SIZE 80

class BufferInfoClass {
public:
  BufferInfoClass() {
    clear();
  }
  char buffer[BUFFER_SIZE];
  uint8_t cb_buffer;
  uint8_t cb_copy[BUFFER_SIZE];
  uint8_t cb_copy_cnt;
  void clear() {
    cb_buffer = 0;
    cb_copy_cnt = 0;
  }
};

BufferInfoClass buffers[5];
uint32_t millis_last_input = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  while (!Serial && millis() < 4000)
    ;
  Serial.begin(115200);
  delay(800);
  Serial.println("Test all Serials");
  Serial.print("Baud rate: ");
  Serial.println(SPD, DEC);
  Serial1.begin(SPD);
  Serial2.begin(SPD);
  Serial3.begin(SPD);
  Serial4.begin(SPD);

  // Start the other tasks
  Scheduler.startLoop(loop2);
  Scheduler.startLoop(loop3);
  Scheduler.startLoop(loop4);


}

#define MIN(a, b) ((a) <= (b) ? (a) : (b))


void CopyFromTo(Stream &SerialF, Stream &SerialT, uint8_t buffer_index) {
  int available;
  int cb;
  BufferInfoClass *buf = &buffers[buffer_index];
  if ((available = SerialF.available()) != 0) {
    // available for write not implementd.
    //available_for_write = SerialT.availableForWrite();
    //Serial.print(" ");
    //Serial.println(available_for_write, DEC);
    cb = MIN(available, BUFFER_SIZE);
    if (cb) {
      SerialF.readBytes(&buf->buffer[buf->cb_buffer], cb);
      SerialT.write(&buf->buffer[buf->cb_buffer], cb);
      buf->cb_buffer += cb;
      buf->cb_copy[buf->cb_copy_cnt] = cb;
      buf->cb_copy_cnt++;
      millis_last_input = millis();
    }
  }
  yield();
}

void memory_dump(const char *pb, uint8_t cb) {
  const char *pbA = pb;
  uint8_t cbA = cb;

  Serial.print("\t");
  for (uint8_t i = 0; i < cb; i++) {
    if (*pb < 0x10) Serial.write('0');
    Serial.print(*pb, HEX);
    Serial.print(" ");
    pb++;
  }

  Serial.print("\n\t ");
  for (uint8_t i = 0; i < cbA; i++) {
    if (*pbA >= ' ') Serial.write(*pbA);
    else Serial.write(' ');
    Serial.print("  ");
    pbA++;
  }
  Serial.println();
}

void print_buffer_header(uint8_t index) {
  BufferInfoClass *buf = &buffers[index];
  Serial.print("  ");
  Serial.print(buf->cb_buffer, DEC);
  Serial.print(" (");
  for (uint8_t i = 0; i < buf->cb_copy_cnt; i++) {
    if (i != 0) Serial.print(",");
    Serial.print(buf->cb_copy[i], DEC);
  }
  Serial.print(")");
}

void CompareBuffers(uint8_t index1, uint8_t index2) {
  if (buffers[index1].cb_buffer == buffers[index2].cb_buffer) {
    if (memcmp(buffers[index1].buffer, buffers[index2].buffer, buffers[index1].cb_buffer) == 0) {
      Serial.println(" ** Match **");
      return;
    } else {
      Serial.println(" ** different **");
    }
  } else {
    Serial.println(" ** counts different **");
  }
  memory_dump(buffers[index1].buffer, buffers[index1].cb_buffer);
  memory_dump(buffers[index2].buffer, buffers[index2].cb_buffer);
}

void loop() {
  CopyFromTo(Serial, Serial1, 0);

  // now see if we should compare the data yet or not
  if (buffers[0].cb_buffer && ((millis() - millis_last_input) > 100)) {
    Serial.println("Check buffers: ");

    print_buffer_header(0);
    Serial.println();

    for (uint8_t i = 1; i < 5; i++) {
      print_buffer_header(i);
      CompareBuffers(0, i);
    }
    for (uint8_t i = 0; i < 5; i++) buffers[i].clear();
  }

  digitalWrite(LED_BUILTIN, HIGH);
  delayMicroseconds(100);  // give time form things to propagate
  digitalWrite(LED_BUILTIN, LOW);
}

void loop1() {
  CopyFromTo(Serial1, Serial, 1);
}
void loop2() {
  CopyFromTo(Serial2, Serial2, 2);
}
void loop3() {
  CopyFromTo(Serial3, Serial3, 3);
}
void loop4() {
  CopyFromTo(Serial4, Serial4, 4);
}
But this shows very little IO overlapping:

image

In this case it is better overlap to only do one byte at a time...
Changed this function:

void CopyFromTo(Stream &SerialF, Stream &SerialT, uint8_t buffer_index) {
  if (SerialF.available() != 0) {
    BufferInfoClass *buf = &buffers[buffer_index];
    int ch = SerialF.read();
    SerialT.write(ch);
    buf->buffer[buf->cb_buffer++] = ch;
    buf->cb_copy[buf->cb_copy_cnt] = 1;
    buf->cb_copy_cnt++;
    millis_last_input = millis();
  }
  yield();
}

But this is not the way I would really want to code things...

1 Like

Quick update:
I opened an issue on the flush does not flush...
GIGA R1 - SerialX.flush() does not work · Issue #737 · arduino/ArduinoCore-mbed (github.com)

Also was curious, and I am betting that the board does not use the UARTs FIFO buffer either...

void setup() {
  Serial1.begin(115200);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);

  digitalWrite(3, HIGH);
  digitalWrite(3, LOW);
  digitalWrite(3, HIGH);
  digitalWrite(3, LOW);
  digitalWrite(3, HIGH);
  digitalWrite(3, LOW);

}

void loop() {
  digitalWrite(2, HIGH);
  Serial1.print("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  digitalWrite(3, HIGH);
  Serial1.flush();
  digitalWrite(2, LOW);
  delay(1);
  digitalWrite(2, HIGH);
  for (char ch = 'a'; ch <= 'z'; ch++) Serial1.write(ch);
  digitalWrite(3, LOW);
  Serial1.flush();
  digitalWrite(2, LOW);

  delay(1000);
}

@KurtE Hi, does this mean every write to a serial is 'blocking'? It would explain the hi micros I'm seeing with my code that uses Serial. I'm migrating from mega and it's not smooth.

Thanks, and great posts btw

Update: So yep, no buffers writing to Serial. Can anyone share the rationale for using the Unbuffered class? It's crippled my project.

Has anyone already cut a version of Serial that uses a TX buffer? If I need to cut my own, would a rewrite of Serial or bypassing and using mbed directly make more sense?

Thanks

1 Like

Thanks,

I first ran into unbuffered stuff on UNO R4. I wrote a version there, that I submitted as a PR... But so far it has not been touched.

I thought about trying to convert some of the Serial objects to be buffered. I have not looked at it in awhile, but wile playing with USBHost to Serial adapters, I ran into the issue on how best to setup code to flush the output if nothing added in some time frame... Current solution thee as to use another thread. Keep looking for a simpler solution.

Note: also most of the SerialX.flush() are busted as well. That is many of them do not wait until the data has been fully transmitted.

Thanks for the info.

I've now switched to using the mbed::BufferedSerial class directly for all but my blackbox serial device / usb console.

Performance is blistering. I've retained Serial for those cases where I'm using Print funcs. Still considering best options for switching them to buffered.

Update: blackbox now migrated. Keeping console on blocking SerialUSB

1 Like

Glad it is working. Still wondering why, they don't use the buffered for normal Serial...

@KurtE @anon6971307 I'm having issues which I think are related to this on my Giga. I'm using all the available ports and when incoming traffic is busy I get a lot of dropped messages.

From my research I've found that dropped messages can be due to too small a buffer to hold the incoming stream.

My knowledge on how serial works at this level is a bit sketchy, but would I be right in saying the lack of buffer could cause these skips, or is the expected behaviour that the Giga should wait and process each incoming bit?

I'd like to learn more about using mbed:BufferedSerial directly like anon6971307 mentioned, can someone suggest a starting point?

Thanks

Hi David, each Serial RX has a 256byte ring buffer so as long as you are reading from Serial frequently enough you shouldn't be dropping bits.

The Serial TX is unbuffered so your code will wait until the write to pins has completed. Even at the higher baud rates this can slow down your code considerably. If you want to explorer mbed::BufferedSerial, I'd start here BufferedSerial - API references and tutorials | Mbed OS 6 Documentation

2 Likes

You might have issues if you are sending larger packets of data, as
mentioned the transfer code will hang until the last character so if their combined time of output exceeds the time to receive 256 characters the excess ones can be lost.

Other issues with losing data:
If your code spends a significant time with interrupts disabled and/or
the code is servicing higher level priority (lower value) interrupts. in which case the input register of the UART could be overwritten and data lost.

I keep meaning to look on how the Serial USB code is currently working. It has been a while so don't remember if the USB here is running at full speed or high speed mode and buffer size (64 or 512 byte packets ). And does it have multiple USB Packets in can queue...

I keep meaning to look at how hard it would be to switch the SerialX objects code to
use the underlying buffered implementation...

But now playing with camera stuff...

1 Like