checking for ring buffer full in Serial.write() and Serial.print()

1,062 views
Skip to first unread message

Bruce Allen

unread,
Dec 12, 2014, 9:47:02 AM12/12/14
to devel...@arduino.cc
Dear Arduino Devs,

As far as I can tell, the Serial library currently offers no public method to test if the transmit ring buffer is full. The relevant code fragment (from line 462 of HardwareSerial.cpp) is:

size_t HardwareSerial::write(uint8_t c) {
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail) ;

The consequence is that Serial.write() and Serial.print() will return immediately if there is sufficient space available in the ring buffer, but otherwise they will BLOCK until the ring buffer has emptied sufficiently. However there is no way for user code to test if space is available for a non-blocking write.

It would therefore be desirable to have a public method availble to test if the transmit ring buffer is empty, for example:

bool HardwareSerial::empty() {
return (_tx_buffer->head == _tx_buffer->tail);
}

or alternatively a method to return the space available in the transmit ring buffer:

size_t HardwareSerial::bytesunsent() {
int used = (SERIAL_BUFFER_SIZE + _tx_buffer->head - _tx_buffer->tail) % SERIAL_BUFFER_SIZE;
return SERIAL_BUFFER_SIZE-1-used; // Check that -1 is correct!
}

From what I can see, this is architecture-neutral and would have no side effects other than requiring a few lines of new documentation.

Has this been considered in the past and rejected for some reason? The "???" above clearly indicate that the author had concerns about blocking. My proposal would permit the programmer to guarantee non-blocking writes if desired.

Cheers,
Bruce

Victor Aprea

unread,
Dec 12, 2014, 10:44:22 AM12/12/14
to Arduino Developers
Bruce,

This has been discussed at some length earlier this year, See this thread:



... which introduced the availableForWrite method on HardwareSerial.

Should be in Arduino 1.6.0, not sure if it got back-ported into the 1.0.x branch.

Kind Regards,
Vic



--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Bruce Allen

unread,
Dec 14, 2014, 11:04:15 AM12/14/14
to devel...@arduino.cc
Hi Victor,

Thanks for the quick and helpful reply.

> and was addressed by this pull request https://github.com/arduino/Arduino/commit/e5c7cb9de174778521ff4be164c616caebc48801
> ... which introduced the availableForWrite method on HardwareSerial.

Yes, this is exactly what I was hoping for, it makes it possible to program non-blocking writes if desired!

Cheers,
Bruce

Federico Fissore

unread,
Dec 16, 2014, 4:38:35 AM12/16/14
to devel...@arduino.cc
Hi

Victor Aprea ha scrito il 12/12/2014 alle 16:43:
>
> Should be in Arduino 1.6.0, not sure if it got back-ported into the
> 1.0.x branch.


I confirm it's available since 1.5.8. It's not been backported to 1.0.x

Regards

Federico

Bruce Allen

unread,
Dec 18, 2014, 5:26:31 AM12/18/14
to devel...@arduino.cc
Hi Federico,

>> Should be in Arduino 1.6.0, not sure if it got back-ported into the
>> 1.0.x branch.

> I confirm it's available since 1.5.8. It's not been backported to 1.0.x

Thank you! It's not in 1.5.8, so you must mean version > 1.5.8, rather than version >= 1.5.8.

I have a related question for you and Victor, since you're familiar with the Serial code. Or perhaps someone else here will know this.

My question concerns a real-time control (on a Due) which uses (the on-chip) Timer3 to generate interrupts every 20 microseconds. The interrupt handler() takes 11 to 14 microseconds to execute, so on average the code in loop() has 6 to 9 microseconds to run before a new interrupt arrives. The code in loop() is calling Serial.write() and Serial.print() functions.

My question is, will Serial.write()/Serial.print() block or suspend the handler()? And if so, is there a way for usercode to alter that behavior?

I would like the Serial output code to do all its processing in the 6-9 microsecond "nibbles" that are available when handler() is not executing. It should never mask/block/suspend the execution of handler(). Is this how the current implementation behaves?

Cheers,
Bruce

Federico Fissore

unread,
Dec 18, 2014, 6:49:05 AM12/18/14
to devel...@arduino.cc
Bruce Allen ha scrito il 18/12/2014 alle 11:26:
> Hi Federico,
>
>>> Should be in Arduino 1.6.0, not sure if it got back-ported into the
>>> 1.0.x branch.
>
>> I confirm it's available since 1.5.8. It's not been backported to 1.0.x
>
> Thank you! It's not in 1.5.8, so you must mean version > 1.5.8, rather than version >= 1.5.8.


Strange. When I "git log 1.5.8" I see commit
e5c7cb9de174778521ff4be164c616caebc48801. Since 1.5.8 has been released
the 1st of Oct and the commit has been merged the 19th of July, I expect
it to be there. If it's not, it may be a regression.


>
> My question is, will Serial.write()/Serial.print() block or suspend the handler()? And if so, is there a way for usercode to alter that behavior?
>

When you write to a hardware serial, you're filling its buffer. That
buffer is emptied by the hardware serial, regardless of someone actually
reading it or not. If you send in to the buffer more that what it can
handle, the print will block until the needed bytes are read/discarded
by the hardware serial.
Hovewer, on the SAM3X of the Due the hardware serial doesn't implement a
write buffer: it's like having a buffer of 1 byte. Hence, your print
will always block.

Regards

Federico

Bruce Allen

unread,
Dec 18, 2014, 7:36:43 AM12/18/14
to devel...@arduino.cc
Hi Federico,

Thank you for the quick follow-up!

>>>> Should be in Arduino 1.6.0, not sure if it got back-ported into the
>>>> 1.0.x branch.
>>
>>> I confirm it's available since 1.5.8. It's not been backported to 1.0.x
>>
>> Thank you! It's not in 1.5.8, so you must mean version > 1.5.8, rather than version >= 1.5.8.
>
> Strange. When I "git log 1.5.8" I see commit e5c7cb9de174778521ff4be164c616caebc48801. Since 1.5.8 has been released the 1st of Oct and the commit has been merged the 19th of July, I expect it to be there. If it's not, it may be a regression.

I looked more closely. Indeed, availableForWrite() is defined in HardwareSerial.h/HardwareSerial.cpp. But when I try to compile code with 1.5.8 containing

Serial.availableForWrite();

I get the following error message:

HeidenhainDue002.ino:166:15: error: 'class UARTClass' has no member named 'availableForWrite'

Am I doing something stupid? I am an experienced C and linux programmer, but new to Arduino.


>> My question is, will Serial.write()/Serial.print() block or suspend the handler()? And if so, is there a way for usercode to alter that behavior?
>
> When you write to a hardware serial, you're filling its buffer. That buffer is emptied by the hardware serial, regardless of someone actually reading it or not.

OK, understood.

> If you send in to the buffer more that what it can handle, the print will block until the needed bytes are read/discarded by the hardware serial.

I understand that the library routine will block, via a busy loop "while (buffer_full()) ;". But my question is, does the library routine block or mask interrupts? Or can the library code be interrupted for the ISR handler()?

> Hovewer, on the SAM3X of the Due the hardware serial doesn't implement a write buffer: it's like having a buffer of 1 byte. Hence, your print will always block.

I hope you mean "block" in the busy-loop sense, not "block" in the sense of "interrupts are disabled there". Is that right?

Cheers,
Bruce
>

c.ma...@bug.st

unread,
Dec 18, 2014, 7:46:00 AM12/18/14
to devel...@arduino.cc
Il 18/12/2014 13:36, Bruce Allen ha scritto:
> I looked more closely. Indeed, availableForWrite() is defined in HardwareSerial.h/HardwareSerial.cpp. But when I try to compile code with 1.5.8 containing
>
> Serial.availableForWrite();
>
> I get the following error message:
>
> HeidenhainDue002.ino:166:15: error: 'class UARTClass' has no member named 'availableForWrite'
>
> Am I doing something stupid? I am an experienced C and linux programmer, but new to Arduino.

Indeed the function is missing on the SAM core, I'll add that for the
final 1.6.0 to grant compatibility across cores.

BTW since the SAM3X HardwareSerial doesn't implement a write buffer for
serial this function should be an empty stub that always returns 1.

>> If you send in to the buffer more that what it can handle, the print will block until the needed bytes are read/discarded by the hardware serial.
>
> I understand that the library routine will block, via a busy loop "while (buffer_full()) ;". But my question is, does the library routine block or mask interrupts? Or can the library code be interrupted for the ISR handler()?

ISRs are NOT disabled, the write loop is really a busy loop like the one
you posted:

size_t USARTClass::write( const uint8_t uc_data )
{
// Check if the transmitter is ready
while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
;

// Send character
_pUsart->US_THR = uc_data;
return 1;
}

as you can see, it's a check followed by a write, no ISR masking is
happening.

C


Matthijs Kooijman

unread,
Dec 18, 2014, 8:08:33 AM12/18/14
to devel...@arduino.cc
Hey Cristian,

> BTW since the SAM3X HardwareSerial doesn't implement a write buffer
> for serial this function should be an empty stub that always returns
> 1.
Shouldn't it return 0? IIUC, you can write 0 bytes without blocking,
right? Or is there a 1-byte hardware buffer that can be filled without
blocking? If so, 0 or 1 should be returned depending on wether that
buffer is free?

Gr.

Matthijs
signature.asc

Bruce Allen

unread,
Dec 18, 2014, 8:11:06 AM12/18/14
to devel...@arduino.cc
Hi Christian,

Thank you for this very helpful reply.

>> Serial.availableForWrite();
>>
>> I get the following error message:
>> HeidenhainDue002.ino:166:15: error: 'class UARTClass' has no member named 'availableForWrite'
>
> Indeed the function is missing on the SAM core, I'll add that for the final 1.6.0 to grant compatibility across cores.

Makes sense.

> BTW since the SAM3X HardwareSerial doesn't implement a write buffer for serial this function should be an empty stub that always returns 1.

(:-(.

>>> If you send in to the buffer more that what it can handle, the print will block until the needed bytes are read/discarded by the hardware serial.
>>
>> I understand that the library routine will block, via a busy loop "while (buffer_full()) ;". But my question is, does the library routine block or mask interrupts? Or can the library code be interrupted for the ISR handler()?
>
> ISRs are NOT disabled, the write loop is really a busy loop like the one you posted:
>
> size_t USARTClass::write( const uint8_t uc_data ) {
> // Check if the transmitter is ready
> while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY) ;

I see. I found the relevant code in /Java/hardware/arduino/sam/cores/arduino/USARTClass.cpp, I was looking under /Java/hardware/arduino/avr.

> as you can see, it's a check followed by a write, no ISR masking is happening.

Yes, I see.

In browsing the code, I see that there is an interrupt handler present. Is that ISR only used for Serial receive, not for Serial send? That would be good news for me, since my app only writes to the Serial port. Then I wouldn't have to worry about the serial library's ISR blocking my app's ISR.

Cheers,
Bruce

Bruce Allen

unread,
Dec 18, 2014, 8:35:25 AM12/18/14
to devel...@arduino.cc
Hi Matthijs,
Mmm, good point. The idea of availableForWrite() is to tell the programmer how many bytes can be written 'fast', meaning without busy-loop blocking. So in this sense you are right, availableForWrite() should return 0. On the other hand, typical interrupt-driven code from users will probably look like this:

loop () {
char output[64];
read some ADC or Digital values;
x = ....

...
sprintf(output, "Current value of x = %d\n", x);
if (Serial.availableForWrite()>=strlen(output)) Serial.print(output);
}

That code will break if availableForWrite() always returns zero. But you are correct: it is the right result.

The clean solution: add a function that returns the length of the write buffer. For SAM3X, that would be 0.

Dirtier "solution": make availableForWrite() return -1 if there is no write buffer available. The code above will still break, but in debugging it, the app programmer will RTFM and discover that there is no write buffer. But code writing one byte with if (Serial.availableForWrite()) {...} will "work as expected".

I'm too new here to have a vote, but if I did have one, it would be for an "int writeBufferLength()" function. Then make the availableForWrite() stub return 0, which as you say, is the right answer.

For me the solution is to read the data and do processing in an interrupt handler, so Serial.write() can block as much as it likes. The timer will keep interrupting Serial.write() as needed so that work can go on in another parallel thread.

Cheers,
Bruce

c.ma...@bug.st

unread,
Dec 18, 2014, 8:39:45 AM12/18/14
to devel...@arduino.cc
Il 18/12/2014 14:08, Matthijs Kooijman ha scritto:
> Shouldn't it return 0? IIUC, you can write 0 bytes without blocking,
> right? Or is there a 1-byte hardware buffer that can be filled without
> blocking? If so, 0 or 1 should be returned depending on wether that
> buffer is free?

Yes, actually it has a 1-byte buffer (i.e. the latch register of the
UART device), so the correct answer is: return 1 if the UART is not
already transmitting, otherwise 0.

Something along the line of:

return (_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY ? 1 : 0;

C

Andrew Kroll

unread,
Dec 18, 2014, 8:44:10 AM12/18/14
to devel...@arduino.cc
Use a software buffer and an ISR. Can't go wrong that way, really... What would be nice is the ability to return not just 'how many can I write' but if the tx buffer is empty. This way, if the tx buffer is empty the 'how many can I write' tells you how many you can write in one shot.

Bonus points if you give us the ability to specify what size buffer we get upon issuing Serial.begin()....




C

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.


--
Visit my github for awesome Arduino code @ https://github.com/xxxajk

Bruce Allen

unread,
Dec 18, 2014, 8:56:35 AM12/18/14
to devel...@arduino.cc
Hi Christian,

> Yes, actually it has a 1-byte buffer (i.e. the latch register of the UART device), so the correct answer is: return 1 if the UART is not already transmitting, otherwise 0.
>
> Something along the line of:
>
> return (_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY ? 1 : 0;

That's a good solution. I would still urge you to add an int writeBufferLength() method so that the programmer can see what's potentially available.

Cheers,
Bruce

Andrew Kroll

unread,
Dec 18, 2014, 9:01:26 AM12/18/14
to devel...@arduino.cc
An empty indicator and available to write counts would do that.
 (as I said)

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

c.ma...@bug.st

unread,
Dec 18, 2014, 9:07:08 AM12/18/14
to devel...@arduino.cc
Il 18/12/2014 14:35, Bruce Allen ha scritto:
> That code will break if availableForWrite() always returns zero. But you are correct: it is the right result.

We already discussed that in this very long thread:

https://groups.google.com/a/arduino.cc/d/msg/developers/ls3hkviFYM4/sSZL4IqzFoUJ

to sum up:

- there is a general interest to add a generic
Stream::availableForWrite() method as a "fallback" method for the
Stream-derived classes that doesn't provide their own availableForWrite.

- there are some concerns about what's the correct result for such
"default" method, 0 or 1 or -1, I'm currently convinced that 0 is the
safest when we "don't know"

- to get the max buffer size you can call availableForWrite() before
doing any write (there is still the possibility that the transmit buffer
may be not available immediately but this is a very corner case)

C

Matthijs Kooijman

unread,
Dec 18, 2014, 9:09:22 AM12/18/14
to devel...@arduino.cc
Hey Andrew,

> > That's a good solution. I would still urge you to add an int
> > writeBufferLength() method so that the programmer can see what's
> > potentially available.
> An empty indicator and available to write counts would do that.
> (as I said)

Only partially - You can only find out the buffer size when it's empty.
If not, you'd have to block waiting for the buffer to be empty, which is
a bit of a limited API.

A better API would be as Bruce proposed: writeBufferLength(), which can
be combined with the availableForWrite() method to also deduce "empty"
status, so would be a superset of the "empty indicator" you propose.

Also, while we're discussing this - having a readBufferLength() would
also be useful, e.g for implementing hardware flow control.

Gr.

Matthijs
signature.asc

Paul Stoffregen

unread,
Dec 18, 2014, 9:09:56 AM12/18/14
to devel...@arduino.cc
On 12/18/2014 06:01 AM, Andrew Kroll wrote:
An empty indicator and available to write counts would do that.
 (as I said)

Couldn't you achieve the same by calling Serial.availableForWrite() right after Serial.begin(), and store the result.  Then just compare the result from availableForWrite to the known size.

That may be slightly less efficient, but so is the memory overhead of adding another virtual function to the API.


Bruce Allen

unread,
Dec 18, 2014, 9:12:59 AM12/18/14
to devel...@arduino.cc
Hi Christian,

> - to get the max buffer size you can call availableForWrite() before doing any write (there is still the possibility that the transmit buffer may be not available immediately but this is a very corner case)

Yes, you're right.

Note: if flush() is guaranteed to block until the transmit buffer is empty, then a programmer can eliminate the corner case.

> - there are some concerns about what's the correct result for such "default" method, 0 or 1 or -1, I'm currently convinced that 0 is the safest when we "don't know"

In this case, I agree that there is a one byte buffer, so your proposed

return (_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY ? 1 : 0;

seems correct: if 1, it means you can write one byte with a guarantee that it will not block in a busy loop.

Cheers,
Bruce

Andrew Kroll

unread,
Dec 18, 2014, 9:14:57 AM12/18/14
to devel...@arduino.cc
-1 could indicate there is no buffer allocated :-)

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Bruce Allen

unread,
Dec 18, 2014, 10:41:40 AM12/18/14
to devel...@arduino.cc
Hi Andrew,

> -1 could indicate there is no buffer allocated :-)


That was my original proposal, but Christian is right that there is a one-byte register available. If the register is free to write, then it constitutes a one-byte buffer. So Christian's proposal makes more sense than the "return -1" option.

Cheers,
Bruce

Andrew Kroll

unread,
Dec 18, 2014, 11:19:37 AM12/18/14
to devel...@arduino.cc
Unless the register won't be ready to write because the UART was never initialized...



Cheers,
        Bruce

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Bruce Allen

unread,
Dec 19, 2014, 4:37:01 AM12/19/14
to devel...@arduino.cc
Dear Arduino Devs,

Earlier in this thread, I asked the following question, but didn't get a response.

> In browsing the code, I see that there is an interrupt handler present. Is that ISR only used for Serial receive, not for Serial send? That would be good news for me, since my app only writes to the Serial port. Then I wouldn't have to worry about the serial library's ISR blocking my app's ISR.

I spent some time browsing USARTClass.cpp and the SAM3X "data sheet" and thought that I would post the answer here, for the record. The code first disables all USART interrupts:

_pUsart->US_IDR = 0xFFFFFFFF;

Then it enables interrupts for receive ready, receive overflow, and (receive) framing errors:

_pUsart->US_IER = US_IER_RXRDY | US_IER_OVRE | US_IER_FRAME;

(FWIW, the only thing that the corresponding interrupt handler does is to clear the register bits that flag the errors.)

So ... if Serial is only used to transmit (output) data, it will not generate any interrupts.

Cheers,
Bruce


Paul Stoffregen

unread,
Dec 19, 2014, 5:51:51 AM12/19/14
to devel...@arduino.cc
On 12/19/2014 01:37 AM, Bruce Allen wrote:
> Dear Arduino Devs,
>
> Earlier in this thread, I asked the following question, but didn't get a response.

This mail list is intended for discussing development of Arduino's software.

While your question is regarding technical details in Arduino Due's core
library, there's a fine line here between contributing to development of
Arduino vs requesting technical support specifically for your project
(which belong on the forum).

>> In browsing the code, I see that there is an interrupt handler present. Is that ISR only used for Serial receive, not for Serial send? That would be good news for me, since my app only writes to the Serial port. Then I wouldn't have to worry about the serial library's ISR blocking my app's ISR.

Despite the tech support nature of your question, I believe Cristian did
indeed answer it directly.


> ISRs are NOT disabled, the write loop is really a busy loop like the
> one you posted:
>
> size_t USARTClass::write( const uint8_t uc_data )
> {
> // Check if the transmitter is ready
> while ((_pUsart->US_CSR & US_CSR_TXRDY) != US_CSR_TXRDY)
> ;
>
> // Send character
> _pUsart->US_THR = uc_data;
> return 1;
> }
>
> as you can see, it's a check followed by a write, no ISR masking is
> happening.


We get a lot of chatter and noise on this mail list. Extremely
experienced but non-contributing programmers are the worst, especially
when they lack understanding of Arduino's focus on simplicity and novice
usability!

I am personally aware of at least a few people who are significant
contributors to the Arduino community, who've unsubscripted or filtered
this list to an unread folder, simply because of the high noise to
signal ratio.

I'm also considering an ignore filter or simply unsubscribing.


Andrew Kroll

unread,
Dec 19, 2014, 10:16:33 AM12/19/14
to devel...@arduino.cc
If you want any sort of immediate help with a project, I suggest we tell the noobs to grab their favorite irc client (or to just use chatzilla) and go on to irc.freenode.net. They have a very active arduino community there.
Channels include english and french...
#arduino
and
#arduino-fr


--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+unsubscribe@arduino.cc.
Reply all
Reply to author
Forward
0 new messages