jcurl / RJCP.DLL.SerialPortStream

SerialPortStream is an independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. Default branch is 2.x and now has support for Mono with help of a C library.
Microsoft Public License
614 stars 196 forks source link

Method to wait until all data is physical sent by the driver #119

Closed ghost closed 2 years ago

ghost commented 3 years ago

Hello, This post might be duplicated with #99 . I'm implementing a RS485 protocol for long distance communication. I have a master and multiple slaves and I use the RtsEnable pinout from my FT232RL USB cable to set the MAX485 into Write or Read mode. So the flow is something like: I put master in Write mode, I write X bytes, then I put master in Read mode waiting for slave to answer. Everything works fine but I just realized that if I write more bytes (let's say 64 bytes on a 9600 BAUD), even if I call .Flush, the Flush seems to only copy the bytes from local SerialPortStream class memory buffer to the OS driver buffer. At 9600 BAUD we have 1 byte sent every 1 ms (10 bits per byte). So I have to sleep another 64 ms to make sure that the bytes are actually sent from OS driver on the physical wire. But the thing is that in some languages (I use C#) sleep is not very accurate. Even SpinWait is slightly inaccurate these days with variable CPU frequency.
I was thinking that maybe you can implement a method which will wait for the bytes to be physically sent over the wire from the OS driver buffer (probably tcdrain from #99 post). Thanks a lot for this library!

jcurl commented 3 years ago

Thanks for the report. Flushing the driver has a few problems, which is why the original design doesn't do this. I could make a new API as opt-in.

The biggest problem when flushing at hardware level is when hardware flow control is active. I've seen quite a few drivers just hang, ignore timeouts completely and unblockable thus leading to deadlocks that I can't recover from.

If I create a new API, this will be a big caveat, that timeouts may be ignored.

ghost commented 3 years ago

Meanwhile I've subscribed with reflection to TX_EMPTY event from your library but it doesn't help :). I've ended up with a solution to calculate the amount of time needed for a byte (10 bits) to be transferred on the wire based on current BAUD rate, and I did a Stopwatch start and then a busy while(stopwatch.Elapsed.TotalMilliseconds < X)... This was the most accurate wait I could do in C# because as I mentioned before Thread.Sleep, Task.Delay, Thread.SpinWait are not accurate enough.

jcurl commented 3 years ago

I've implemented a new API called Flush(bool drain). I would appreciate it if you would test it (Windows only for now) and tell me what you think. It's attached. SerialPortStream.2.4.0-beta.20210604.1.zip

ghost commented 3 years ago

Thank you very much! I will try to test it out next days in my RS485 communication. I will let you know the results.

ghost commented 3 years ago

I have tested and it doesn't seem to work properly... SerialPortStream sps = new SerialPortStream("COM13", 1200); sps.Open(); Stopwatch sw = Stopwatch.StartNew(); sps.Write(Guid.NewGuid().ToByteArray()); sps.Flush(true); sw.Stop(); The stopwatch shows like 0.01 ms so basically only the time needed to copy the binary data from one buffer to another. I have modified your code trying to create the COM handler with NativeMethods.FileAttributes.FILE_FLAG_NO_BUFFERING | NativeMethods.FileAttributes.FILE_FLAG_WRITE_THROUGH but that doesn't help either. I found this http://www.egmont.com.pl/addi-data/instrukcje/standard_driver.pdf (page 6) and I want to try it out when I will have some available time.

jcurl commented 3 years ago

Hmm, then MS Docs don't work as advertised. Looking here PurgeComm, it says

If a thread uses PurgeComm to flush an output buffer, the deleted characters are not transmitted. To empty the output buffer while ensuring that the contents are transmitted, call the FlushFileBuffers function (a synchronous operation). Note, however, that FlushFileBuffers is subject to flow control but not to write time-outs, and it will not return until all pending write operations have been transmitted.

So I use FlushFileBuffers.

This could indicate a problem in the driver itself? But I've seen FTDI implement pretty good drivers.

jcurl commented 3 years ago

Have you been able to investigate further? I looked at the PDF you recommended. My implementation is already using an I/O thread that uses overlapped I/O. The flush implementation first waits for the software buffer to be empty (the same implementation for Flush(false)), and then it secondly waits for the return of FlushFileBuffers.

Are you able to try with different serial devices, or provide sample code you use to test, that I might be able to translate for testing with a normal RS232?

jcurl commented 2 years ago

Closing, as there hasn't been any activity. Unfortunately, my changes made as per MSDN documentation didn't work as expected.