With Arduino Uno Rev3, I am trying to maintain a delicate timing while handling data transmission. I want to send 6 bytes at a time, which takes around ~44 us when I time Serial.write()
. The time it takes from Arduino Serial to computer is irrelevant in my case. I only mind the run time of the function.
My idea is sending not all 6 bytes but a few at a time, so that the total sending time overhead would be distributed between time-sensitive tasks. Note that I will never be exceeding the buffer size, so the Serial.write()
will never wait for actually transmission to be completed.
const uint8_t MSG_LEN = 6;
uint8_t snd_buffer[MSG_LEN];
uint32_t us_now = 0L;
uint8_t snd_byte_cnt = MSG_LEN;
uint8_t dummy_var = 0;
void setup() {
Serial.begin(115200);
delay(1000);
}
void loop() {
us_now = micros();
operation_dummy();
com_dummy();
//the aim is to use no delay functions
}
void com_dummy(){ //ex communication
if (snd_byte_cnt < MSG_LEN){ //bytes need to be sent
Serial.write(snd_buffer[snd_byte_cnt]); //send the target byte
snd_byte_cnt++; //move to the next byte ind
} //else nothing to be sent
}
void operation_dummy() { //ex operation
dummy_var++; //do something (time sensitive in reality)
if (snd_byte_cnt == MSG_LEN){ //if no data pending to be sent
* (uint32_t *) &snd_buffer[1] = us_now; //modify buffer between 1st and 5th bytes (both inclusive)
snd_buffer[MSG_LEN - 1] = dummy_var;
snd_byte_cnt = 0; //trigger start sending
} //else wait for next cycle to send
}
My question is, how many bytes should be used with Serial.write()
at a time?
I've heard the Serial library processes 16 bits (2 bytes) at a time. What happens if I try to send a single byte? Does it wait a certain time for the second byte and if doesn't arrive then it pushes a single byte? (Edit: this turned out to be a misunderstanding and only the FIFO pointer is 16 bits, not the data to be sent. * Source: https://forum.arduino.cc/t/serial-write-time/233360/23 )*
Would never calling a sleep function and using micros()
OR the baudrate affect my time keeping? I think micros()
and actually transmission uses interrupts. So different baudrates may delay micros()
calls differently since both need to disable interrupts.
-
Which Arduino board are you using?fabianoriccardi– fabianoriccardi2024年09月14日 15:04:58 +00:00Commented Sep 14, 2024 at 15:04
-
1the UART transmits one bit at a time ... that's how serial communication works ... the title of your post does not match the rest of the postjsotola– jsotola2024年09月14日 16:27:50 +00:00Commented Sep 14, 2024 at 16:27
-
1Pure USART would send 10bit per byte (8N1) at desired baudrate, however whenever you use USB-To-Serial or CDC (on boards with native USB), it'll be completely dependent on USBKIIV– KIIV2024年09月14日 18:14:01 +00:00Commented Sep 14, 2024 at 18:14
-
Since you did not say what Arduino you use, at what baudrate, and at what rate you send the 6 bytes, we can only answer generally. Please edit your question to add that information. Please add also a reference to the claim of processing two bytes - it is nonsense.the busybee– the busybee2024年09月15日 07:15:02 +00:00Commented Sep 15, 2024 at 7:15
-
Why did you tag "softwareserial", do you use it?the busybee– the busybee2024年09月15日 20:00:45 +00:00Commented Sep 15, 2024 at 20:00
2 Answers 2
The Arduino Serial
writes one byte at a time. There is a method for
writing a buffer of arbitrary length, but all this method does is
repeatedly call write(uint8_t)
for each byte within the
buffer:
size_t Print::write(const uint8_t *buffer, size_t size)
{
size_t n = 0;
while (size--) {
if (write(*buffer++)) n++;
else break;
}
return n;
}
Notice that there is no waiting between the bytes.
If you do not fill the transmit buffer, this should be very fast: all
write(uint8_t)
does is push the byte into a ring buffer. Unless your
timings are at the microseconds level, you can just send the six bytes
at once.
I've heard the Serial library processes 16bits (2bytes) at a time?
Very dubious. Do you have a reference for this statement?
Edit: If you are struggling with microsecond-range timings, you could send the bytes by directly writing to the UART data register, instead of relying on the Arduino core library to do so. In order to do this, you would:
Disable the "USART, Data Register Empty" interrupt, which is the interrupt used by the Arduino core for sending the bytes to the port.
Before sending a byte, test the "USART, Data Register Empty" flag in order to know whether the UART is ready to accept a byte for sending.
Write the byte you want to send directly into the UART data register, thus completely bypassing the TX buffer used by the Arduino core.
By bypassing the ring buffer and associated interrupt, you should be able to win a few microseconds this way.
Here is a version of your example code using this approach:
const uint8_t MSG_LEN = 6;
uint8_t snd_buffer[MSG_LEN];
uint32_t us_now = 0L;
uint8_t snd_byte_cnt = MSG_LEN;
uint8_t dummy_var = 0;
void setup() {
Serial.begin(115200);
UCSR0B &= ~_BV(UDRIE0); //disable "USART, Data Register Empty" interrupt
delay(1000);
}
void loop() {
us_now = micros();
operation_dummy();
com_dummy();
//the aim is to use no delay functions
}
void com_dummy(){ //ex communication
if (snd_byte_cnt < MSG_LEN && bit_is_set(UCSR0A, UDRE0)){ //bytes need to be sent and can be sent
UDR0 = snd_buffer[snd_byte_cnt]; //send the target byte
snd_byte_cnt++; //move to the next byte ind
} //else nothing to be sent
}
void operation_dummy() { //ex operation
dummy_var++; //do something (time sensitive in reality)
if (snd_byte_cnt == MSG_LEN){ //if no data pending to be sent
* (uint32_t *) &snd_buffer[1] = us_now; //modify buffer between 1st and 5th bytes (both inclusive)
snd_buffer[MSG_LEN - 1] = dummy_var;
snd_byte_cnt = 0; //trigger start sending
} //else wait for next cycle to send
}
-
Except for the UNO-R4. It blocks for the entire length of the serial transmission.Delta_G– Delta_G2024年09月14日 14:58:28 +00:00Commented Sep 14, 2024 at 14:58
-
@Delta_G: Wow! That seems silly. Do you have an idea why they would do that?Edgar Bonet– Edgar Bonet2024年09月14日 15:30:50 +00:00Commented Sep 14, 2024 at 15:30
-
Maybe it's just a basic implementation without optimizations.fabianoriccardi– fabianoriccardi2024年09月14日 16:03:39 +00:00Commented Sep 14, 2024 at 16:03
-
2@EdgarBonet, I'm not sure why they did it this way. I see they were trying to use the FSP UART implementation and I think they just got confused at how to do it. I've got a PR in to fix it but it has been ignored for a long time. github.com/arduino/ArduinoCore-renesas/pull/304Delta_G– Delta_G2024年09月14日 18:28:32 +00:00Commented Sep 14, 2024 at 18:28
-
@Delta_G I wondered why is your code so misaligned at some points in diffs and there is mixture of tabs and spaces... Super anoying as there is always someone with different tabstop :DKIIV– KIIV2024年09月14日 19:58:00 +00:00Commented Sep 14, 2024 at 19:58
I want to send 6 bytes at a time, which takes around ~44 us when I time Serial.write().
You are timing the time it takes for Serial.write to put bytes into an internal buffer (which holds 64 bytes), not the time taken to transmit them. You haven't said what baud rate you are using, but I doubt you are transmitting at 1363652 bits per second.
6 bytes in 44 μs is one byte in 7.3333 μs.
Each byte consists of 10 bits (start bit, 8 data bits, stop bit)
Therefore you are talking about 0.7333 μs per bit.
1/7.333e-6 = 1363652 bits per second.
A quick test shows that it does indeed take 48 μs (on my Uno and my IDE) to put 6 bytes into the serial buffer.
Changing the write to write only one byte improves the time taken from 48 μs to 12 μs so that is less time fiddling with copying data into that buffer.
The actual transmission is done by the hardware, and it fires an interrupt when each byte has been sent, and commences sending the next byte. During the sending of the byte your code is free to continue doing its stuff.
There would be some overhead (I'm not sure the exact amount) for each byte to be pulled from the buffer and sent to the hardware.
I've heard the Serial library processes 16 bits (2 bytes) at a time.
That's nonsense. It would help if you specified what Arduino you have, however.
What happens if I try to send a single byte?
A single byte will get put into the buffer, which will take slightly less time, as I explained above.
If timing is very critical I would be tempted to suggest turning interrupts off, but of course then you can't transmit any bytes at all.
This sounds a lot like an XY problem to me. You are getting bogged down on how long it takes to transmit one byte, but haven't explained:
- What Arduino you have
- What baud rate you are using
- What your code is
- What is this highly timing-critical thing you are trying to achieve.
As for your question title:
how many bits are actually transmitted at once by UART
One bit would be transmitted "at once" - that's how serial communications work.
Would never calling a sleep function and using micros() OR the baudrate affect my time keeping?
A sleep function? What is the purpose of that here?
Unless you disable interrupts, or take other steps (like cancelling Timer 0) then micros will work as it basically returns the contents of the hardware timer (0) plus an overflow count. It would also be reasonably fast. Running the timer itself will cause the occasional interrupt (about every millisecond).
A different baudrate wouldn't affect timing much, except that the interrupts which occur when the sending buffer empties would happen more or less often.
I think micros() and actually transmission uses interrupts. So different baudrates may delay micros() call differently since both needs to disable interrupts?
micros() doesn't use interrupts directly (except as noted below), however the accumulation of timer overflows is done in an interrupt. Note that micros() briefly turns off interrupts in order to obtain the overflow count without it changing during the few clock cycles that it makes a copy of it.
See hardware/arduino/avr/cores/arduino/wiring.c
for the actual code in micros() and millis().
Note that because of the way Timer 0 is configured, micros() has a resolution of 4μs. In other words, you won't be able to tell the difference between a 2μs and 3μs interval.
In response to a comment, I'll just clarify that turning off interrupts just defers the processing of that interrupt until such time as they are turned on again.
-
Thanks! I have an Uno (in the title) rev3. My baudrate is 115200 to ensure all OS compatibility. About my concern of 16 bits being sent, you are right, I confused that Serial FIFO pointer being 16bits as mentioned here forum.arduino.cc/t/serial-write-time/233360/23. Finally, this is exactly what I wanted to know. I wanted to minimize the time it takes to write to Serial buffer so that I can "thread" without any operation taking more than ~50us. The communication is already very light and not frequent that I can assume Serial write will never block due to being filled fully.gunakkoc– gunakkoc2024年09月15日 13:16:29 +00:00Commented Sep 15, 2024 at 13:16
-
I've edited the question to make my point better.gunakkoc– gunakkoc2024年09月15日 22:50:18 +00:00Commented Sep 15, 2024 at 22:50
-
Regarding the Uno part, I glanced at the tags, where people usually put which Arduino they have. I added the Uno tag for you.2024年09月16日 01:45:13 +00:00Commented Sep 16, 2024 at 1:45
-
I've responded to your edited points.2024年09月16日 01:59:04 +00:00Commented Sep 16, 2024 at 1:59
-
I mentioned Running the timer itself will cause the occasional interrupt. I didn't say, or intend to imply, that turning off interrupts would drop an interrupt service, clearly it doesn't. However while interrupts are turned off they will not be serviced. For more information see my page about interrupts.2024年09月16日 07:59:36 +00:00Commented Sep 16, 2024 at 7:59
Explore related questions
See similar questions with these tags.