I was hoping someone here can explain the time required to load the tx serial buffer on an Uno or Mega2560...
A little background
I have a sketch which gets 32 bytes of data from 4 different I2C devices.Then I sort and package all that data up in an array with a few other things totaling up to 136 bytes. I then send that data out via serial to raspberry pi over usb. I have increased the tx buffer in hardwareserial.h to 256 bytes to accommodate for this large array.
The sketch takes approximately 5.4 milliseconds (ms) to execute. 3.6ms of that is the 128 bytes over I2C and the rest (1.8ms) is the time to Serial.write 136 bytes at 460800 baud. My goal is to never overrun the tx buffer so 460800 baud is the minimum I can use (460800/1360bits=2.9ms).
The problem comes in that I thought Serial.write would return almost instantly as long as the buffer had enough room for what was being sent. Meaning if I was sending 50 bytes and the buffer was 64 bytes (and the buffer was clear) then Serial.write would return immediately, the uart would take care of serial comms and the sketch would continue (the essence of non - blocking no?). But my experience is not the case. I get a linear increase in time based on the amount of data sending...
data(bytes) time to Serial.write(ms)
25 0.232
50 0.46
75 0.69
100 0.928
150 1.492
200 1.94
250 2.396
but then also peculiarly I get a difference in time to return depending on what baudrate I am using (the below is sending 50 bytes)...
baud time to Serial.write(ms)
115200 0.46
230400 0.536
460800 0.712
921600 0.5
So I guess it boils down to 2 main questions (with a few sub questions )
Why does Serial.write take so long to return if the buffer has room for what is being sent?
-Can you explain the timing?
-Can it be faster?
Why is Serial.write return time baud dependent?
Below is some code that I have been using to benchmark times....
unsigned int micros1;
unsigned int micros2;
unsigned int micros3;
unsigned int micros4;
unsigned int micros5;
unsigned int micros6;
unsigned int micros7;
unsigned int micros8;
unsigned int prevMicros4;
byte buf[50];
void setup() {
// put your setup code here, to run once:
Serial.begin(230400);
Serial3.begin(115200);
Serial.println("waiting");
delay(3000);
Serial.println("ready....set....go!");
}
void loop() {
micros1 = micros();
for (unsigned int i=0; i < sizeof(buf)-1; i++)
{
buf[i]=200;
}
//delay(0); //play with the delay to get the tx buffer to overflow
micros2 = micros();
Serial3.write(buf, sizeof(buf));
micros3=micros();
//time to load buffer + delay
Serial.print(micros2-micros1);
Serial.print(",");
//time for serial.write
Serial.print(micros3-micros2);
Serial.print(",");
//entire previous loop time. this time is the amount of time the serial.write has to unload its buffer.
Serial.print(micros4-prevMicros4);
Serial.println();
prevMicros4=micros4;
micros4=micros();
}
Yeah I only want the lower 16 bits hence the int declare. I have alot of data to send over rather slow I2c so if my data is more or less reliable/deterministic, 65 ms should be more than enough to get a relative timestamp when data is coming at .5 ms per "sample" (Each "sample" contains 2 bytes for a timestamp and 2 bytes for an ADC value. So 8 "samples" per payload of 32 bytes times 4 I2C channels)
It was not necessary to increase the outgoing serial buffer size. The print(), write(), and println() methods will simply block when the buffer gets full. That really shouldn't cause you problems.
Since you are possibly seeing micros() overflow, your statement that you don't care is nonsense. Since you are trying to use the values for timing, you DO care. Fix that problem. Run the code again. If you still have questions, ask them again.
Why is Serial.write return time baud dependent?
If Serial.write() blocks, of course the time it blocks will depend on the time it takes to shift out enough characters for write() to write its data. That, of course, depends on the baud rate.
I am aware that serial.write etc block until there is room in the buffer. The whole goal of what I am trying to do is speed up the system. I do not want serial to block. That is why I increased the buffer size. I could alternatively have made several smaller serial.write calls throughout the program but I didn't choose to do that.
Regarding micros overflow, I definitely see overflow and it is dealt with accordingly. I never said I don't care. I have large Matlab post processing scripts to parse through the data so overflow in the timestamp is dealt with there.
Regarding the code I posted, it runs the same whether you declare longs or ints. I just tried it.
PaulS:
If Serial.write() blocks, of course the time it blocks will depend on the time it takes to shift out enough characters for write() to write its data. That, of course, depends on the baud rate.
This is true and I know this. The times that I posted are with write() NOT blocking so my questions still stand...
Why does a non-blocking Serial.write take so long to return if the buffer has room for what is being sent?
-Can you explain the timing?
-Can it be faster?
Why is a non-blocking Serial.write return time baud dependent?
Why does a non-blocking Serial.write take so long to return if the buffer has room for what is being sent?
First, we do not know that there is room in the buffer. There IS a method in the Serial class that will tell you how much room is available.
If you have a 128 byte buffer, but it contains 100 bytes, and you try to write 36 bytes to it, Serial.write() WILL block.
Before you try to write n bytes to the buffer, make sure that there is room for n bytes, or be prepared to wait.
-Can you explain the timing?
I think you know what the timing is about. It takes time to shift data out, depending on how often the interrupt happens to shift a byte out and the baud rate and other things that determine how many bits are shifted out, at what timing.
-Can it be faster?
Only if you change the baud rate can you shift the bits put faster.
Only if you reduce the amount of other things that the Arduino is doing can you allow the serial interrupt to happen more often.
Only if you don't write when the buffer doesn't have enough room.
Why is a non-blocking Serial.write return time baud dependent?
Again with the assumption that Serial.write() doesn't get blocked...
J_Maz:
I thought Serial.write would return almost instantly as long as the buffer had enough room for what was being sent.
I does - provided you make sure there is room.
I have been experimenting with this adaptation of your program. Note Lines 48-50. (Just ignore the comments at the top).
// python-build-start
// action, upload
// board, arduino:avr:mega:cpu=atmega2560
// port, /dev/ttyACM0
// ide, 1.6.3
// python-build-end
// ide, 1.6.3
// ide, 1.5.6-r2
// arduino:avr:uno
// arduino:avr:mega:cpu=atmega2560
unsigned long micros1;
unsigned long micros2;
unsigned long micros3;
unsigned long micros4;
unsigned long micros5;
unsigned long micros6;
unsigned long micros7;
unsigned long micros8;
unsigned long prevMicros4;
unsigned long serial3Baud = 500000;
byte buf[200];
byte numBytes = 150;
void setup() {
// put your setup code here, to run once:
Serial.begin(230400);
Serial3.begin(serial3Baud);
Serial.println("waiting");
//~ delay(3000);
Serial.println("ready....set....go!");
Serial.print(Serial3.availableForWrite());
for (byte n = 0; n < 8; n++) {
micros1 = micros();
for (unsigned int i=0; i < numBytes; i++)
{
buf[i]=200;
}
//delay(0); //play with the delay to get the tx buffer to overflow
micros2 = micros();
while (Serial3.availableForWrite() < 63) {
}
micros5= micros();
Serial3.write(buf, numBytes);
micros3=micros();
//time to load buffer + delay
Serial.print("Baudrate ");
Serial.print(serial3Baud);
Serial.print(" ");
Serial.print(numBytes);
Serial.print(" bytes ");
Serial.print(" Load buffer ");
Serial.print(micros2-micros1);
Serial.print(" write ");
//time for serial.write
Serial.print(micros3-micros5);
Serial.print(" loop time ");
//entire previous loop time. this time is the amount of time the serial.write has to unload its buffer.
Serial.print(micros4-prevMicros4);
Serial.println();
prevMicros4=micros4;
micros4=micros();
}
}
void loop() {
}
These two files will prove what I am talking about for non-blocking Serial.writes()...
SerialWriteTiming-changing_bauds.ino shows a clear increase in time to return only for 460800baud:
Now Checking: 50 Bytes @ 57600 baud: Serial.write() time = 436 microseconds
Now Checking: 50 Bytes @ 115200 baud: Serial.write() time = 468 microseconds
Now Checking: 50 Bytes @ 230400 baud: Serial.write() time = 500 microseconds
Now Checking: 50 Bytes @ 460800 baud: Serial.write() time = 700 microseconds
Now Checking: 50 Bytes @ 921800 baud: Serial.write() time = 500 microseconds
Now Checking: 50 Bytes @ 1500000 baud: Serial.write() time = 404 microseconds
and
SerialWriteTiming-changing_bytes.ino shows the linear increase in time to return based on how much data you are sending:
Now Checking: 25 Bytes @ 115200 baud: Serial.write() time = 228 microseconds
Now Checking: 50 Bytes @ 115200 baud: Serial.write() time = 468 microseconds
Now Checking: 75 Bytes @ 115200 baud: Serial.write() time = 684 microseconds
Now Checking: 100 Bytes @ 115200 baud: Serial.write() time = 920 microseconds
Now Checking: 150 Bytes @ 115200 baud: Serial.write() time = 1380 microseconds
Now Checking: 200 Bytes @ 115200 baud: Serial.write() time = 1832 microseconds
Now Checking: 300 Bytes @ 115200 baud: Serial.write() time = Serial.write() will always block with writing data larger than buffer!
PaulS:
First, we do not know that there is room in the buffer.
Yes I do know that there is room. And I just posted code to prove it. Same timing results as my first post.
PaulS:
It takes time to shift data out, depending on... the baud rate
I get that it takes time to shift the data out based on a baudrate but I am not talking about that. I am referring to the time it takes for Serial.write() to RETURN when the buffer clearly has room for the data being sent.
I guess I thought that if TX buffer had room for what was being sent, Serial.write() would return almost instantly as Serial.write() was just pointing the UART to a memory location.... That does not appear to be the case though with the timing figures I have presented.
(I have changed it back to 64 bytes so you and I can discuss this on equal ground but Keep in mind I HAD changed my TX buffer size to 256 bytes in hardwareSerial.h.)
Robin2:
It does
But it doesn't return instantly and your code shows that. Just change the numBytes to something lower than the buffer size and you will see that Serial.write() takes a while to return (0.96ms with a numBytes=50)
The only thing I would change in your code while (Serial3.availableForWrite() < numBytes)
to ensure that you never Serial.write() until there is room available for what you are sending.
Serial.write() has some data that it needs to copy into the outgoing buffer. It is not simple pointer manipulation that it does.
Keep in mind I HAD changed my TX buffer size to 256 bytes in hardwareSerial.h
Is that the right place? The latest versions of the IDE have multiple HardwareSerial classes, for the different Arduinos and the different serial ports on each. I am suspicious that maybe you didn't make the Mega buffers larger.
What does Serial.availableForWrite() show before you send anything? That will confirm that you've set the buffer to the size you expected.
I've written some programs which depend on this, so they check this at the top of the program and just sit there producing error messages on all available outputs, basically saying "change this file... recompile me"
J_Maz:
But it doesn't return instantly and your code shows that. Just change the numBytes to something lower than the buffer size and you will see that Serial.write() takes a while to return (0.96ms with a numBytes=50)
I know it does not return instantly - it has work to do. But if the buffer is empty and the data is less than the buffer size the write duration is not affected by the baud rate.
Conversely, if you send data that is longer than the buffer size the duration is affected by the baud rate. IIRC at 500,000 baud the buffer was being emptied faster than it could fill.
And I forgot to mention that my tests were done with the standard 64 byte buffer.
It is probably possible to write directly to the buffer bypassing Serial.
It is certainly possible to write your own code to send Serial data without using the Serial library or its bufffer at all. That way you could also eliminate the "cost" of copying the data from your array to the serial buffer.
With SERIAL_TX_BUFFER_SIZE set to 256 in hardwareSerial.h, Serial.availableForWrite() shows 255 before writing anything as expected. I already checked this. So the file location appears to be right.
Robin2:
I know it does not return instantly - it has work to do.
Gotcha. Understood. I was under the impression that when something is classified as non-blocking it should have a more or less instant return (not a couple milliseconds).
Robin2:
But if the buffer is empty and the data is less than the buffer size the write duration is not affected by the baud rate.
Ah but it is. See the timing values I already posted. Around the 460800 baud region there is a spike in time to send the same data size.
Robin2:
It is probably possible to write directly to the buffer bypassing Serial.
This is very interesting. Any idea how to do this? I have looked at tried to look at Serial source code but I really cant figure out how it works....
Robin2:
It is certainly possible to write your own code to send Serial data without using the Serial library or its bufffer at all. That way you could also eliminate the "cost" of copying the data from your array to the serial buffer.
This sounds messy and quite possibly out of my skillset.
With SERIAL_TX_BUFFER_SIZE set to 256 in hardwareSerial.h, Serial.availableForWrite() shows 255 before writing anything as expected. I already checked this.
I'm posting from my phone, so I can't see the source, but what type of variable is used for the buffer pointer?
J_Maz:
This sounds messy and quite possibly out of my skillset.
It is not particularly difficult - it really just involves writing data to some of the on-chip registers. You need to study the Atmel datasheet - IIRC it includes suitable code snippets. You could probably use Serial.begin() to do the initialization.
9us per byte doesn't seem like an impossibly large time for the print/write code to spend doing nothing more than copying the bytes to the buffer... 30-70 instructions.
HW serial doesn't seem to have an optimized multi byte write; looks like it will all go through print's virtual function.
Seems like a rather long time to me. Considering the below takes only 0.5us per byte. 15 x faster
for (unsigned int i=0; i < sizeof(buf)-1; i++)
{
buf[i]=200;
}
But to Robin2's point maybe it would be faster to write directly to the chips outgoing serial registers period I would have to figure out how to do that.
Do you see anything that would indicate why the time to return is longer especially around the for 460800 baud region?