2

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.

dda
  • 1,595
  • 1
  • 12
  • 17
gunakkoc
  • 123
  • 5

2 Answers2

7

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.

Nick Gammon
  • 38,901
  • 13
  • 69
  • 125
3

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:

  1. Disable the "USART, Data Register Empty" interrupt, which is the interrupt used by the Arduino core for sending the bytes to the port.

  2. 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.

  3. 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 }

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81