4

Using Serial.Write() in a time sensitive application. I realize that the number of characters being written not only had an impact on the time spent writing in the buffer, but actually had a bigger impact than expected.

Consider the following sketch, which measures the time it takes to write NB_CHARS characters, using Serial.Write(). I sampled 1000 times in order to get a more reliable measure.

#define NB_CHARS 100

void setup()
{
  Serial.begin(115200);

  uint8_t chars[NB_CHARS];
  for (int i=0; i<NB_CHARS; i++)
  {
    chars[i] = 66;
  }

  unsigned long time1, time2;
  unsigned long totalTime = 0;

  delay(100);

  for (int i=0; i<1000; i++)
  {
    time1 = micros();
    Serial.write(chars, NB_CHARS);
    time2 = micros();
    totalTime += time2 - time1;

    delay(10);
    Serial.println("");
    delay(10);
  }

  float meanTime = (float)totalTime / 1000.0f;
  Serial.print("meanTime = ");
  Serial.println(meanTime);
}

void loop()
{

}

Here are some results for different NB_CHARS values:

NB_CHARS: Time (microseconds)
1: 12
2: 18
3: 24
[...]
99: 2912
100: 2997
101: 3082
[...]
199: 11412
200: 11497
201: 11582

It seems that at first, each new character adds around 6 microseconds of delay, but afterward it becomes 85 microseconds. I tested every values from 1 to 100 to find where it changes and it seems to be right after 70:

Serial.Write() delay as a function of number of chars

It seems weird to me.

A quick Google search gets me to this 2013 post by Tom Carpenter. I am not sure how true this is or if it is still relevant.

Part of the trouble is that the Hardware Serial library is so incredibly inefficient (I don't think they could have made it less efficient if they tried!). After each byte is sent, there is an interrupt called which spends a lifetime loading the next byte into the buffer to send. The write function also wastes lots of time doing calculations as to where to put a character in a ring buffer.

[...]

Why is there such a "long" delay for each character and is there a way to make it faster?

What happens after 70 characters that slows it down even further?

  • set higher baud rate. at least 460800 baud should go with good connections. – Juraj Sep 10 '18 at 20:18
  • the quote is not true anymore. at bit rates over 500kbit/s the interrupt is not used if the buffer is empty. (bit rate, not baud rate) – Juraj Sep 10 '18 at 20:21
13

The Arduino core has a 64 byte transmission buffer. Characters are sent out of that buffer by the hardware.

When you blast lots of characters out of serial the first 64 just get put into that buffer - almost instantly.

Once that buffer is full your sketch blocks until the hardware has sent a character to make room.

Since it's sending right from the moment you place the first character into the buffer you get a little more than 64 bytes worth of buffering time (64 + the number that have been sent by the time the buffer fills).

From that point on you are entirely at the mercy of the baud rate you have chosen.

To get more predictable throughput (and maximise the speed) you should bypass the Ardiuno core and manipulate the UART registers directly.

  • "you should bypass the Ardiuno core and manipulate the UART registers directly". Any good library for this? – Alex Millette Sep 10 '18 at 18:59
  • 5
    @AlexMillette Ummm... no. That's the whole point. You will be avoiding any library / core functions and using the registers directly. Read the datasheet. – Majenko Sep 10 '18 at 19:02
  • 2
    I think it would be handy @Majenko if you could point Alex at some example code.... – boatcoder Sep 11 '18 at 1:25
  • 3
    There is a fair chance that once you bypass the core, your code will run as slow as the right part, and not as fast as the left one. – IMil Sep 11 '18 at 3:27
  • 1
    For smooth serial communication you really want an MCU with DMA to do the transfers for you... – Majenko Sep 11 '18 at 9:21
3

Majenko already gave the right answer: as soon as the buffer is full, everything falls apart (for a time sensitive application).

It is possible to enlarge the serial buffer for your project. However, in the future, with a new Arduino version, you might have forgotten that you have changed a library and the project does no longer work that well.

I want to point out that you can use the usb serial port that is inside some microcontrollers and processors. That is not a real serial uart with a baudrate and a clock, but a usb CDC device.
A atmega32u4 and the arm m0+ have such a usb CDC serial port.
The arduino leonardo, micro and pro micro use the atmega32u4.
The arduino zero, m0, mkr series use the arm m0+ processor.

What about these numbers:

NB_CHARS: Time (microsec)
1: 6.93
2: 7.60
3: 8.06
[...]
99: 153
100: 154
101: 152
[...]
199: 1685
200: 1624
201: 1705

That is with a arm m0+ processor. I used the same sketch as you, but I changed the Serial to SerialUSB.

2

Your "long" delay is not too long at all.

If you measure the throughput of the right part, you have about 30 characters sent in 2500 microseconds. This means the rate is roughly (30 * 8 * 1,000,000) / 2500, which is 96,000 bits/sec.

It's a bit less than 115,200 you specified, but serial protocols use service bits, so in fact you'd have to replace 8 with 10. This gives 120,000 baud which is within measurement error.

The left part is actually much faster than you required, but as @Majenko correctly noticed, this is thanks to buffering. Apparently, you can increase the buffer size up to 256 bytes. If you need consistency instead of maximum speed, you may decrease the buffer size instead.

Finally, you should test performance in a realistic scenario. In your example you just keep sending data in a loop; obviously you soon fill up the buffer and run into port speed limit. However, in real life your loop would probably read some input, do some calculations, then send the result. In this case, you'd be using the buffer more effectively.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.