-
Notifications
You must be signed in to change notification settings - Fork 199
Driver Compatibility with SerialPortStream
This section is intended to capture some of the quirks and behaviours of serial drivers. The SerialPortStream implementation is very reliant on how the Operating System behaves and how individual drivers behave.
When designing your software to talk over the serial port, do not rely on the correctness of error checking (e.g. Parity). Instead, use packet based protocols with your own CRC checks (or use existing protocols for your design, such as HDLC).
When testing hardware flow control on multiple drivers, I've observed issues with flow control not releasing, or working as expected. See below for more details.
I recommend always using the most recent version of the driver. Some USB serial port adapters come with outdated drivers that often cause Blue Screen crashes.
Issue? NO. Workaround in code available.
Data corruption can occur when using synchronous settings with the PL2303 drivers (observed with the first implementation in 2010). The code block below should block until data arrives and if data is already in the buffer, it should return immediately. However, using loop back devices showed the data to be corrupted (it appeared interleaved) indicating an issue in the driver.
// Non-asynchronous behaviour timeouts.ReadIntervalTimeout = -1; timeouts.ReadTotalTimeoutConstant = 0; timeouts.ReadTotalTimeoutMultiplier = 0;
The solution is to use read interval time outs. This block hasn't been tested with the newer PL2303RA chip sets that have working drivers for Windows 8 and later, as the workaround to have different settings is also compatible with all current drivers.
timeouts.ReadIntervalTimeout = 10; timeouts.ReadTotalTimeoutConstant = 100; timeouts.ReadTotalTimeoutMultiplier = 0;
Issue? NO. Workaround in code available.
It's my experience that a Prolific PL2303H driver will not send an arbitrary size block of data in one write operation. My tests show that it accepts large blocks, but finishes the overlapped I/O after 12032 or 11776 bytes. This makes it more important that the SerialPortStream buffer the write data and automatically resends the data when finished.
Note, low level serial timeouts used are:
WriteTotalTimeoutMultipler = 0 WriteTotalTimeoutConstant = 0
If WriteTotalTimeoutConstant is 500ms, then blocks of 6144 or 5888 bytes are sent (at a baud of 115200, 6144 bytes is 533ms, 5888 bytes is 511ms). This kind of behaviour doesn't seem to be a problem, one just needs to be aware that generally a WriteFile() won't write all data (which is why the SerialPortStream will buffer the data for you).
Issue? NO. Workaround in code available.
In a simple test where 128KB of random data is sent from a Prolific adapter, the WriteFile() events occur generally before the WaitCommEvent() with the flag TX_EMPTY. I would have expected this to be generated only once, as observed by the FTDI driver.
Issue? YES.
When enabling Hardware Flow Control for the PL2303H when reading from the serial port, we observe that it doesn't release the CTS line as expected.
The test setup was to implement an artificial delay in the serial thread loop (of 1000ms), to simulate a badly behaving process. While the serial thread isn't able to receive data, it is expected that the driver set the CTS line when too much data has arrived, and release the CTS line when data has been read from the buffer.
An external program, such as ZOC or TeraTerm was used to send data via an FTDI based serial port, or a physical 16550A UART (on an older computer).
The following PL2303 drivers were tested and all have problems:
- Prolific Driver Win 7 x86; Date 26.07.2012; Version 3.4.36.247
- Prolific Driver Win 7 x86; Date 31.07.2007; Version 3.2.0.0
- Prolific Driver Win 7 x86; Date 03.08.2005; Version 2.0.0.19 (unsigned)
- Hardware: SiteCom CN-116
The sender doesn't send any data, because it sees the PL2303H indicates to not send data. If the sender or the receiver (or both) have hardware flow control disabled, then data is sent (with the possibility of data overflow of course).
If the roles are reversed, so that data is received with hardware flow control enabled, but the PL2303 is sending data (instead of receiving), small chunks of data (4096 bytes, as configured to the drivers for the driver input buffer - independent of the SerialPortStream receive buffer) are sent, quickly for a short period of time, then pauses, repeating every second. This is the expected behaviour.
There are no known workarounds with the PL2303 driver that I could find, other than to not use a PL2303 for receiving data, or by turning off hardware flow control completely.
- Tried to perform a read regardless of the EV_RXCHAR event, to test if data is really present. No data is read, indicating that the sending application doesn't send the data and that EV_RXCHAR is behaving as expected.
- Getting the DCB and rewriting the DCB doesn't help.
- Trying to write the RTS parameter explicitly with the code below, resulted in error 87 (ERROR_INVALID_PARAMETER)
- After fixing reading errors with the PL2303, retested and the issue with Hardware Flow control remains
Native.DCB m_Dcb = new Native.DCB(); Native.GetCommState(m_ComPortHandle, ref m_Dcb); if ((m_Dcb.Flags & Native.DcbFlags.RtsControlMask) == Native.DcbFlags.RtsControlHandshake) { if (!Native.EscapeCommFunction(m_ComPortHandle, Native.ExtendedFunctions.SETRTS)) { Console.WriteLine("Couldn't set RTS: {0}", Marshal.GetLastWin32Error()); } }
The FTDI chipset is by far one of the most reliable and best working chip sets and drivers that I've encountered.
Issue? YES. Ensure you have sufficient time outs in your code.
The FTDI driver interprets the EOF character (set to default 0x1A). When reading data, it may be that a timeout of 100ms is too short. When using a test case to sending random data, with a buffer timeout of 100ms from a PL2303 chipset to a FTDI chipset (for reading), on reception of the 0x1A character, a small pause in reading is observed, so that no data is received, unless a second read is made, or a timeout of 180ms or more is used.
In particular, the small loop:
// Receive sent data int rcv = 0; byte[] rcvbuf = new byte[150000]; while (rcv < rcvbuf.Length) { int b = dst.Read(rcvbuf, rcv, rcvbuf.Length - rcv); if (b == 0) break; rcv += b; }
The last byte to be received, when dst.ReadTimeout is only 100ms, is the character 0x1A. It was observed, that by changing the DCB EOF character, the behaviour changes, so that the last byte is the same as the DCB EOF character. According to the documentation by FTDI D2XX Programmer's Guide, the EOF character should be ignored when the fBinary flag is used, as this is what is documented by MSDN DCB structure, any other flag other than fBinary being 1 is not supported).
In any case, this is simple to work around, simply increase the timeouts.
For Windows, com0com is the software driver that is used often to test SerialPortStream from Windows XP to Windows 10 (1607). The Windows SerialPort class will only work with COMx interfaces, not names such as CNCA0. This implementation will work with CNCA0, etc.
Issue? YES. Ensure that your applications are properly configured.
This driver is based on bytes, not individual bits, so some of the test cases will fail with this driver as we send 8-bit data to be received using a configuration of 7-bit data with parity. Naturally this will work with real serial ports as the data is indistinguishable.
Issue? NO. Resolved in SerialPortStream 2.0.3
See Issue #12 on GitHUB. When opening the serial port, it would clear the break state. On this particular driver, it caused the current thread to hang for 30s. The workaround implemented was not to set the Comm state on open (or on changes to any other property).
Behaviour can still be reproduced for those drivers when setting the Break state property direct.
Using version 6.7.0.0 (best version till now), which is found on Windows 7 x86 automatically using Windows Update.
Modem signals test cases wouldn't work as expected. We know that the hardware supports this because tests on Linux showed correct behaviour. The following test cases failed, indicating that querying the pins and waiting for the driver to raise events, didn't work as expected.
- WaitForDsrChangedEvent, WaitForCtsChangedEvent fail
- ModemSignals, ModemSignalsWithSleep10 fail
- CloseWhenBlocked, CloseWhenFlushBlocked, DisposedWhenBlocked, DisposedWhenFlushBlocked fail, as closing requires 30 seconds (similar behaviour observed under Linux)
- ClosedWhenBlockedResetHandshake passes, which shows that we can turn off flow control and the test case passes as expected, avoiding the 30 second timeout.
- Flush fails due to too early an event indicating TX-BUFFER EMPTY
- WaitForDsrChangedEvent, WaitForCtsChangedEvent fail as the driver doesn't raise events
- ModemSignals, ModemSignalsWithSleep10 fail as the driver doesn't report the correct signal state
Not all serial port drivers support a break state.
- Issue #2 describes a u-Blox Cellular Modem that raises exceptions.
You should also refer to the document dll/serialunix/README.linux for more information. Some of it is repeated here.
This appears to be the only driver that I've tested that works flawlessly, including receiving invalid data, and properly handling parity errors on receive.
Chipset | Affected |
---|---|
PL2303H | YES |
PL2303RA | YES |
FTDI | YES |
16550A | NO |
CP210x UART Bridge | YES |
On receiving data that has parity errors, the affected chip sets receive invalid data, not just for the byte that has the parity error, but a block of bytes.
The test cases are
libnserial/comptest SerialParityTest.Parity7O1ReceiveError SerialParityTest.Parity7E1ReceiveError
The case sets byte offset 0x45 as having the wrong parity. Even Parity should be sent on the wire as 0xC5. Odd Parity should be sent on the wire as 0x45.
For PL2303H, PL2303RA, FTDI, bytes 0x3A to 0x45 were considered incorrect, even though only bytes 0x45 was the only incorrectly sent byte.
0000030: 3031 3233 3435 3637 3839 ff00 3aff 003b 0123456789..:..; 0000040: ff00 3cff 003d ff00 3eff 003f ff00 40ff ..<..=..>..?..@. 0000050: 0041 ff00 42ff 0043 ff00 44ff 0045 4647 .A..B..C..D..EFG
And in this case 0x44 was considered incorrect.
0000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>? 0000040: 4041 4243 ff00 4445 4647 4849 4a4b 4c4d @ABC..DEFGHIJKLM 0000050: 4e4f 5051 5253 5455 5657 5859 5a5b 5c5d NOPQRSTUVWXYZ[\]
For the CP210x UART Bridge, the results are a little different. There is no parity detection, it appears as if the receiver is simply set to 7-bit data and parity errors are ignored.
Parity7E1 sent: (sent 0x45 instead of 0xC5)
00000040: c041 42c3 4445 c647 48c9 ca4b cc4d 4ecf .AB.DE.GH..K.MN.
Parity7O1 sent: (sent 0xC5 instead of 0x45)
00000040: 40c1 c243 c4c5 46c7 c849 4acb 4ccd ce4f @..C..F..IJ.L..O
and received with no parity error detection at all:
00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO
Chipset | Affected |
---|---|
PL2303H | YES |
PL2303RA | YES |
FTDI | YES |
CP210x UART Bridge | YES |
16550A | NO |
If you close the serial port while send blocked (e.g. due to flow control being active and the remote side has the RTS signal disabled), calling the serial_close() method may take an excessive amount of time. Times are typically 30 seconds and blocks in the Linux close() system call.
As soon as the process is no longer send blocked, the close follows immediately.
Chipset | Affected |
---|---|
PL2303H | YES |
PL2303RA | YES |
FTDI | YES |
16550A | YES |
CP210x UART Bridge | YES |
You should not change the properties of the serial port (baudrate, flow control, etc.) with anything that might call the tcsetattr() Linux system call. Doing so will block the thread forever, until data can be sent again.
Chipset | Affected |
---|---|
PL2303H | YES |
PL2303RA | NO |
FTDI | YES |
16550A | NO |
CP210x UART Bridge | NO |
In some cases, when ending a program and restarting it, unexpected data can be read which never appeared on the wire. For more details, see the notes in: libnserial/NOTES.bug-serialportclose.txt
The test program to recreate this (raw, without using the library) is given by: libnserial/comptest/kernelbug.c
On opening the serial port, an initial read could return a variable number of zero bytes before all other data, but would only occur when the first "real" data arrives over the wire. So it is not possible to previously flush the serial port using operating system commands, or to continuously read.
Tested was
- Ubuntu 14.04.3 with kernel:
- Linux leon-ubuntu 3.19.0-49-generic #55~14.04.1-Ubuntu SMP Fri Jan 22 11:23:34 UTC 2016 i686 i686 i686 GNU/Linux
- Ubuntu 16.04
- Linux leon-ubuntu 4.4.0-15-generic #31-Ubuntu SMP Fri Mar 18 19:08:31 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
- Linux leon-ubuntu 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:36:54 UTC 2017 i686 i686 i686 GNU/Linux
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1542862