diff --git a/src/serial.h b/src/serial.h index 7359415..54b9982 100644 --- a/src/serial.h +++ b/src/serial.h @@ -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); diff --git a/src/serial_posix.c b/src/serial_posix.c index cdaa882..50f09e1 100644 --- a/src/serial_posix.c +++ b/src/serial_posix.c @@ -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; } diff --git a/src/serial_win32.c b/src/serial_win32.c index 299b130..d3b9d32 100644 --- a/src/serial_win32.c +++ b/src/serial_win32.c @@ -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; }