[POLL (Closed)] Blocking behaviour of Serial.USB during TX

victor_pv
Sun Aug 20, 2017 4:37 pm
There is a very long discussion about SerialUSB TX behavior in different cores, and the posibility of losing bytes.
There is mainly 2 different ways to manage USB transmissions if the host is not processing data at the rate the MCU is sending it:
On one hand SerialUSB.write () can just discard packets and not block after a set timeout period, returning a value that indicates how many bytes could be fit in the TX buffer. The timeout period can be hard set, or could be modified to match a certain baud rate, so timeout only if the host is acknowledging packets at a slower rate.
On the other hand the SerialUSB.write()can block indifinitely until data can be queued for TX, and return only if it was successfully queued.

Please vote below, and add any extra comment you want to add in the thread.


martinayotte
Sun Aug 20, 2017 4:50 pm
I voted “other” simply because in the old libmaple F4, there is already a begining of solution I used since awhile :
https://github.com/rogerclarkmelbourne/ … .h#L66-L67
May it could simply be extented by providing an additional timeout to enabledBlockingTx() …
In other words, I would prefer an universal solution which cover all possible cases ! :ugeek:

stevestrong
Sun Aug 20, 2017 8:42 pm
I’m with Martin this time.
For applications where high Tx throughput is needed, it must be granted that the host is capable of that high speed.
Otherwise one should be able to select the desired behavior, blocking or not.

zmemw16
Sun Aug 20, 2017 9:00 pm
+1

RogerClark
Sun Aug 20, 2017 9:21 pm
There is definitely a problem with blocking completely, because if you run you board from a USB charger or a battery and forget there is a serial.print in some bit of code.

The program hangs..

The same potentially applies if the terminal is not open.

I chose the second option, I.e don’t block but return how much space is available, as it would allow the program to choose to block its self if necessary

However in hindsight, I should look to see what Serial.print returns in the rest of the Arduino world. Eg.on AVR and Due.


victor_pv
Mon Aug 21, 2017 12:57 am
Guys, since the option that was not included is the one apparently getting the most votes, I have added it as a separate option to track how much people goes for it.
Please vote again as the change cleared the voting.

But so far it seems the best option is to make it configurable.
I will look at teensy and due code to see what they do, and that could be default, and then configurable to act the opposite.


RogerClark
Mon Aug 21, 2017 1:27 am
Victor

I know I’ve mentioned this before, but ….

Can you also look at what happens in regard to DTR, beause AFIK our SerialUSB implementation does not currently behave the same way as the Due or the Teensy.
I think the difference is that we don’t put any data into the USB TX buffer is DTR is not set, (Host terminal is not active), but the other boards still put stuff into the TX buffer

Currently people have issues with Libmaple not working correctly with some terminal’s, who don’t set DTR by default.
i.e Normal Arduino boards work with those terminals but LibMaple does not.


victor_pv
Mon Aug 21, 2017 2:46 am
This is what the DUE core does:
SerialUSB TX behavior
https://github.com/arduino/ArduinoCore- … C.cpp#L264
/* only try to send bytes if the high-level CDC connection itself
is open (not just the pipe) – the OS should set lineState when the port
is opened and clear lineState when the port is closed.
bytes sent before the user opens the connection or after
the connection is closed are lost – just like with a UART. */

LineState contains 2 flags, DTR and RTS, besides baud rate and some other information.
The code checks if line rate != 0. In that case, it sends.
If lineState is 0, it doesn’t even buffer the data, just returns 0, and sets a flag.
If lineState !=0, then tries to send data, and returns a value indicating what how much it sent. I need to dig more in the code to see if it can successfully send part of what’s requested, but looks like it.

OK it seems like the low level driver will block until the ep is ready to take a packet. So the class is the one that will return 0 if the line is closed, but it the line is open, it blocks.
It doesn’t seem to do any kind of buffering like we do for TX, it goes straight to the endpoint buffer, which for bulk transmission and USB FS is max 64 bytes.
There is no timeout or anything like that. If the line is open, it will block, if the line is closed, it returns right away. But line open is not just having a host connected to USB, but rather having an application open the port.
I’m not sure what would happen if the line is setup, application open the port, then cable is removed, and from the comments in the code, whoever wrote the library doesn’t know either.
// TODO – ZE – check behavior on different OSes and test what happens if an
// open connection isn’t broken cleanly (cable is yanked out, host dies
// or locks up, or host virtual serial port hangs)

Even if we manage to detect the line open and close correctly, we may want to be able to not block for too long.

EDIT:
The RX behavior:
It will not ACK RX packets until it’s able to write them to the class buffer. So if the application is not reading, or not reading fast enough, it will block the host until there is space in the buffer. So on RX there shouldn’t be any drop, which I think is the most appropriate.


RogerClark
Mon Aug 21, 2017 3:11 am
Thanks

victor_pv
Mon Aug 21, 2017 12:57 pm
Updated the previous post to add RX behavior in the DUE.

aster
Fri Sep 01, 2017 10:04 pm
maybe:
Configurable at runtime (blocking or non-blocking with small timeout)
+
if not set at runtime use a default configuration like: It should not block and return inmediately if there is no space in the buffer (return value indicates number of bytes queued for TX).

victor_pv
Sat Sep 02, 2017 3:04 pm
Looks like so far is going in the direction of having a runtime option to make it block or not block, the return value should match the number of bytes successfully queue, and when non-blocking mode either return inmediately, or with a short timeout depending on speed requested.

Any more votes or different opinions on the expected behaviour?


fpiSTM
Sat Sep 09, 2017 12:57 pm
In fact, I ask myself the same question on the USB behaviour for the STM core.
So, I have the same trouble than you and wait the end of the poll :mrgreen:

victor_pv
Mon Sep 11, 2017 1:14 am
From everyone’s comments so far, and the poll, I think the best expected behaviour is:

-If USB is disconnected, just drop and return with no timeout, and not saving to buffer either. Return indicates 0 bytes were sent. This is how Arduino DUE works.
-If USB is connected, 2 possibly outcomes:

  • by default try to queue to the buffer, and wait for a timeout period. Such timeout simulating the rate at which the port was opened (115200 if nothing was provided). If the bytes can’t be sent at that pace, return indicating how many were queued. If the sketch cares to check the return fine, if not, then data is just lost. This is more or less how a USART port would work. If puts the data in the output line, but doesn’t care if anyone listen.
  • At runtime you can set the timeout to max (let’s say 0xFFFF), or bps rate to 0, and if so, the sending function will block indefinitely waiting until it can send the data. I think that still if the port gets disconnected from the host after the sketch is running, the TX function should just return, dont see a need to block in this case unless someone can explain it to me.

Does that fit everyone needs?


stevestrong
Mon Sep 11, 2017 8:29 am
Victor, I would not use timeouts at all, just return the nr of bytes successfully sent, because if systick is not used, then the timeout won’t work.

danieleff
Mon Sep 11, 2017 8:47 am
[victor_pv – Mon Sep 11, 2017 1:14 am] –
Does that fit everyone needs?

I am ok with that.

BTW my experiments with windows10/usbser.sys driver show that it has a 16K buffer. So if the host PC program opens the COM port, but does not read anything at all, the STM32 USB will be able to send 16K data instantly anyway, bypassing your device timeout.


victor_pv
Mon Sep 11, 2017 1:48 pm
[stevestrong – Mon Sep 11, 2017 8:29 am] –
Victor, I would not use timeouts at all, just return the nr of bytes successfully sent, because if systick is not used, then the timeout won’t work.

The possible problem with that is if you do a lot of Serial.print in a row, filling up the buffer. I don’t think anyone checks return values when they are trying to print information, but at the same time they expect that to be sent, and not lose a line here or there.
As I said if we allow to change it at runtime, we can make it that if timeout is 0, it doesn’t even look at systick, just runs once trying to send the data and return what’s successfull. That would allow for not using systick.

Let’s try to agree on how to manage the possibilities. I suggest using begin() since it’s already there. Imagine the following case (and add or modify to it until we all like it).
Serial.begin(); //No timeout, and no blocking. No guarantee of delivery. We can include some repeat loop in case the sketch is sending data at very fast pace. currently this results in a set timeout of 50ms in the libmaple F1.

Serial.begin(115200); // Use a timeout to approximate 115200 speed.

Serial.begin(0); // Block until it can write data to buffer (no timeout, just blocking), except if the port is totally disconnected (which you should have checked for with !Serial)


fredbox
Mon Sep 11, 2017 4:04 pm
-If USB is disconnected, just drop and return with no timeout, and not saving to buffer either. Return indicates 0 bytes were sent. This is how Arduino DUE works.
+1

stevestrong
Mon Sep 11, 2017 4:09 pm
Just a dummy idea: what about
Serial.begin(<baudrate>, bool blocking = 1 or 0); // 0 per default
basically use second parameter to specify the blocking/non-blocking option.

BTW, libmaple F4 USB serial has these 2 functions:
void usbEnableBlockingTx(void);
void usbDisableBlockingTx(void);


martinayotte
Mon Sep 11, 2017 4:11 pm
Right !
That is what I’ve said in the other thread several weeks ago.
usbEnableBlockingTx() could have a new argument for the timeout value, and 0 or -1 can indicate blocking forever…

victor_pv
Mon Sep 11, 2017 6:08 pm
So if we use this method:

Serial.begin(<baudrate>, bool blocking = 1 or 0); // 0 per default


stevestrong
Tue Sep 12, 2017 8:26 am
As I don’t prefer timeout, the
begin(115200, 1);

Leave a Reply

Your email address will not be published. Required fields are marked *