Add support for half-duplex emulation.
When using half-duplex communication (e.g. only a single wire for both Tx and Rx) a data packet needs to be transmitted entirely before attempting to switch into receiving mode. For legacy serial hardware, the tcdrain() probably works as advertised, and waits until the data has been transmitted. However for common usb-serial converters, the hardware doesn't provide any feedback to the driver, and the tcdrain() function can only wait until the data has been transmitted to the usb-serial chip. There is no guarantee that the data has actually been transmitted by the usb-serial chip. As a workaround, we wait at least the minimum amount of time required to transmit the data packet over a serial line, taking into account the current configuration.
This commit is contained in:
parent
3c07a3017f
commit
b6d24e72e2
@ -88,6 +88,8 @@ int serial_set_timeout (serial_t *device, long timeout /* milliseconds */);
|
||||
|
||||
int serial_set_queue_size (serial_t *device, unsigned int input, unsigned int output);
|
||||
|
||||
int serial_set_halfduplex (serial_t *device, int value);
|
||||
|
||||
int serial_read (serial_t *device, void* data, unsigned int size);
|
||||
int serial_write (serial_t *device, const void* data, unsigned int size);
|
||||
|
||||
|
||||
@ -66,6 +66,10 @@ struct serial_t {
|
||||
* serial port is closed.
|
||||
*/
|
||||
struct termios tty;
|
||||
/* Half-duplex settings */
|
||||
int halfduplex;
|
||||
unsigned int baudrate;
|
||||
unsigned int nbits;
|
||||
};
|
||||
|
||||
//
|
||||
@ -103,6 +107,11 @@ serial_open (serial_t **out, const char* name)
|
||||
// Default to blocking reads.
|
||||
device->timeout = -1;
|
||||
|
||||
// Default to full-duplex.
|
||||
device->halfduplex = 0;
|
||||
device->baudrate = 0;
|
||||
device->nbits = 0;
|
||||
|
||||
// Open the device in non-blocking mode, to return immediately
|
||||
// without waiting for the modem connection to complete.
|
||||
device->fd = open (name, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
@ -401,6 +410,9 @@ serial_configure (serial_t *device, int baudrate, int databits, int parity, int
|
||||
#endif
|
||||
}
|
||||
|
||||
device->baudrate = baudrate;
|
||||
device->nbits = 1 + databits + stopbits + (parity ? 1 : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -434,6 +446,18 @@ serial_set_queue_size (serial_t *device, unsigned int input, unsigned int output
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
serial_set_halfduplex (serial_t *device, int value)
|
||||
{
|
||||
if (device == NULL)
|
||||
return -1; // EINVAL (Invalid argument)
|
||||
|
||||
device->halfduplex = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
serial_read (serial_t *device, void *data, unsigned int size)
|
||||
{
|
||||
@ -512,6 +536,15 @@ serial_write (serial_t *device, const void *data, unsigned int size)
|
||||
if (device == NULL)
|
||||
return -1; // EINVAL (Invalid argument)
|
||||
|
||||
struct timeval tve, tvb;
|
||||
if (device->halfduplex) {
|
||||
// Get the current time.
|
||||
if (gettimeofday (&tvb, NULL) != 0) {
|
||||
TRACE ("gettimeofday");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int nbytes = 0;
|
||||
while (nbytes < size) {
|
||||
fd_set fds;
|
||||
@ -549,6 +582,33 @@ serial_write (serial_t *device, const void *data, unsigned int size)
|
||||
}
|
||||
}
|
||||
|
||||
if (device->halfduplex) {
|
||||
// Get the current time.
|
||||
if (gettimeofday (&tve, NULL) != 0) {
|
||||
TRACE ("gettimeofday");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate the elapsed time (microseconds).
|
||||
struct timeval tvt;
|
||||
timersub (&tve, &tvb, &tvt);
|
||||
unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec;
|
||||
|
||||
// Calculate the expected duration (microseconds). A 2 millisecond fudge
|
||||
// factor is added because it improves the success rate significantly.
|
||||
unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000;
|
||||
|
||||
// Wait for the remaining time.
|
||||
if (elapsed < expected) {
|
||||
unsigned long remaining = expected - elapsed;
|
||||
|
||||
// The remaining time is rounded up to the nearest millisecond to
|
||||
// match the Windows implementation. The higher resolution is
|
||||
// pointless anyway, since we already added a fudge factor above.
|
||||
serial_sleep ((remaining + 999) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,10 @@ struct serial_t {
|
||||
*/
|
||||
DCB dcb;
|
||||
COMMTIMEOUTS timeouts;
|
||||
/* Half-duplex settings */
|
||||
int halfduplex;
|
||||
unsigned int baudrate;
|
||||
unsigned int nbits;
|
||||
};
|
||||
|
||||
//
|
||||
@ -111,6 +115,11 @@ serial_open (serial_t **out, const char* name)
|
||||
return -1; // ERROR_OUTOFMEMORY (Not enough storage is available to complete this operation)
|
||||
}
|
||||
|
||||
// Default to full-duplex.
|
||||
device->halfduplex = 0;
|
||||
device->baudrate = 0;
|
||||
device->nbits = 0;
|
||||
|
||||
// Open the device.
|
||||
device->hFile = CreateFileA (devname,
|
||||
GENERIC_READ | GENERIC_WRITE, 0,
|
||||
@ -267,6 +276,9 @@ serial_configure (serial_t *device, int baudrate, int databits, int parity, int
|
||||
return -1;
|
||||
}
|
||||
|
||||
device->baudrate = baudrate;
|
||||
device->nbits = 1 + databits + stopbits + (parity ? 1 : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -338,6 +350,19 @@ serial_set_queue_size (serial_t *device, unsigned int input, unsigned int output
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
serial_set_halfduplex (serial_t *device, int value)
|
||||
{
|
||||
if (device == NULL)
|
||||
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
|
||||
|
||||
device->halfduplex = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
serial_read (serial_t *device, void* data, unsigned int size)
|
||||
{
|
||||
@ -360,12 +385,47 @@ serial_write (serial_t *device, const void* data, unsigned int size)
|
||||
if (device == NULL)
|
||||
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
|
||||
|
||||
LARGE_INTEGER begin, end, freq;
|
||||
if (device->halfduplex) {
|
||||
// Get the current time.
|
||||
if (!QueryPerformanceFrequency(&freq) ||
|
||||
!QueryPerformanceCounter(&begin)) {
|
||||
TRACE ("QueryPerformanceCounter");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD dwWritten = 0;
|
||||
if (!WriteFile (device->hFile, data, size, &dwWritten, NULL)) {
|
||||
TRACE ("WriteFile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (device->halfduplex) {
|
||||
// Get the current time.
|
||||
if (!QueryPerformanceCounter(&end)) {
|
||||
TRACE ("QueryPerformanceCounter");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate the elapsed time (microseconds).
|
||||
unsigned long elapsed = 1000000.0 * (end.QuadPart - begin.QuadPart) / freq.QuadPart + 0.5;
|
||||
|
||||
// Calculate the expected duration (microseconds). A 2 millisecond fudge
|
||||
// factor is added because it improves the success rate significantly.
|
||||
unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000;
|
||||
|
||||
// Wait for the remaining time.
|
||||
if (elapsed < expected) {
|
||||
unsigned long remaining = expected - elapsed;
|
||||
|
||||
// The remaining time is rounded up to the nearest millisecond
|
||||
// because the Windows Sleep() function doesn't have a higher
|
||||
// resolution.
|
||||
serial_sleep ((remaining + 999) / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return dwWritten;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user