From 3f33f82bc28000aab08d54a1c260afd9d3490e78 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 29 Oct 2007 09:22:19 +0000 Subject: [PATCH] Initial import. --- serial.h | 82 ++++++ serial_posix.c | 678 +++++++++++++++++++++++++++++++++++++++++++++++++ serial_win32.c | 486 +++++++++++++++++++++++++++++++++++ 3 files changed, 1246 insertions(+) create mode 100644 serial.h create mode 100644 serial_posix.c create mode 100644 serial_win32.c diff --git a/serial.h b/serial.h new file mode 100644 index 0000000..ecf1b67 --- /dev/null +++ b/serial.h @@ -0,0 +1,82 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct serial serial; + +enum parity_t { + SERIAL_PARITY_NONE, + SERIAL_PARITY_EVEN, + SERIAL_PARITY_ODD +}; + +enum flowcontrol_t { + SERIAL_FLOWCONTROL_NONE, + SERIAL_FLOWCONTROL_HARDWARE, + SERIAL_FLOWCONTROL_SOFTWARE +}; + +enum queue_t { + SERIAL_QUEUE_INPUT = 0x01, + SERIAL_QUEUE_OUTPUT = 0x02, + SERIAL_QUEUE_BOTH = SERIAL_QUEUE_INPUT | SERIAL_QUEUE_OUTPUT +}; + +int serial_errcode (); +const char* serial_errmsg (); + +int serial_open (serial **device, const char* name); + +int serial_close (serial *device); + +int serial_configure (serial *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol); + +// +// Available read modes: +// +// * Blocking (timeout < 0): +// +// The read function is blocked until all the requested bytes have +// been received. If the requested number of bytes does not arrive, +// the function will block forever. +// +// * Non-blocking (timeout == 0): +// +// The read function returns immediately with the bytes that have already +// been received, even if no bytes have been received. +// +// * Timeout (timeout > 0): +// +// The read function is blocked until all the requested bytes have +// been received. If the requested number of bytes does not arrive +// within the specified amount of time, the function will return +// with the bytes that have already been received. +// + +int serial_set_timeout (serial *device, long timeout /* milliseconds */); + +int serial_read (serial *device, void* data, unsigned int size); +int serial_write (serial *device, const void* data, unsigned int size); + +int serial_flush (serial *device, int queue); +int serial_drain (serial *device); + +int serial_send_break (serial *device); + +int serial_set_dtr (serial *device, int level); +int serial_set_rts (serial *device, int level); + +int serial_get_received (serial *device); +int serial_get_transmitted (serial *device); + +int serial_sleep (unsigned long timeout /* milliseconds */); + +int serial_timer (); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* SERIAL_H */ diff --git a/serial_posix.c b/serial_posix.c new file mode 100644 index 0000000..a58e0ca --- /dev/null +++ b/serial_posix.c @@ -0,0 +1,678 @@ +#define _POSIX_C_SOURCE 199309 + +#include // malloc, free +#include // strerror +#include // errno +#include // open, close, read, write, isatty, usleep +#include // fcntl +#include // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak +#include // poll +#include // ioctl +#include // gettimeofday +#include // nanosleep + +#include "serial.h" + +#include // perror +#define TRACE(expr) \ +{ \ + int error = errno; \ + fprintf (stderr, "TRACE %s:%d: ", __FILE__, __LINE__); \ + perror (expr); \ + errno = error; \ +} + +struct serial { + /* + * The file descriptor corresponding to the serial port. + */ + int fd; + int timeout; + /* + * Serial port settings are saved into this variable immediately + * after the port is opened. These settings are restored when the + * serial port is closed. + */ + struct termios tty; +}; + +// +// Error reporting. +// + +int serial_errcode () +{ + return errno; +} + + +const char* serial_errmsg () +{ + return strerror (errno); +} + +// +// Open the serial port. +// + +int +serial_open (serial** out, const char* name) +{ + if (out == NULL) + return -1; // EINVAL (Invalid argument) + + // Allocate memory. + struct serial *device = malloc (sizeof (struct serial)); + if (device == NULL) { + TRACE ("malloc"); + return -1; // ENOMEM (Not enough space) + } + + // Default to blocking reads. + device->timeout = -1; + + // 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); + if (device->fd == -1) { + TRACE ("open"); + free (device); + return -1; // Error during open call. + } + + // Restore blocking mode after opening the device. The CLOCAL bit + // of the termios struct will be set to ignore modem control lines. + int flags = fcntl (device->fd, F_GETFL, 0); + if (fcntl (device->fd, F_SETFL, flags & ~O_NONBLOCK) != 0) { + TRACE ("fcntl"); + close (device->fd); + free (device); + return -1; // Failed to restore blocking mode. + } + + // Retrieve the current terminal attributes, to + // be able to restore them when closing the device. + // It is also used to check if the obtained + // file descriptor represents a terminal device. + if (tcgetattr (device->fd, &device->tty) != 0) { + TRACE ("tcgetattr"); + close (device->fd); + free (device); + return -1; + } + + *out = device; + + return 0; +} + +// +// Close the serial port. +// + +int +serial_close (serial* device) +{ + if (device == NULL) + return 0; + + // Restore the initial terminal attributes. + if (tcsetattr (device->fd, TCSANOW, &device->tty) != 0) { + TRACE ("tcsetattr"); + close (device->fd); + free (device); + return -1; + } + + // Close the device. + if (close (device->fd) != 0) { + TRACE ("close"); + free (device); + return -1; + } + + // Free memory. + free (device); + + return 0; +} + +// +// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). +// + +int +serial_configure (serial *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + // Retrieve the current settings. + struct termios tty = {0}; + if (tcgetattr (device->fd, &tty) != 0) { + TRACE ("tcgetattr"); + return -1; + } + + // Setup raw input/output mode without echo. + tty.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL); + tty.c_oflag &= ~(OPOST); + tty.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN); + + // Enable the receiver (CREAD) and ignore modem control lines (CLOCAL). + tty.c_cflag |= (CLOCAL | CREAD); + + // VMIN is the minimum number of characters for non-canonical read + // and VTIME is the timeout in deciseconds for non-canonical read. + // Setting both of these parameters to zero implies that a read + // will return immediately, only giving the currently available + // characters (non-blocking read behaviour). + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + + // Set the baud rate. + speed_t baud = 0; + switch (baudrate) { + case 0: baud = B0; break; + case 50: baud = B50; break; + case 75: baud = B75; break; + case 110: baud = B110; break; + case 134: baud = B134; break; + case 150: baud = B150; break; + case 200: baud = B200; break; + case 300: baud = B300; break; + case 600: baud = B600; break; + case 1200: baud = B1200; break; + case 1800: baud = B1800; break; + case 2400: baud = B2400; break; + case 4800: baud = B4800; break; + case 9600: baud = B9600; break; + case 19200: baud = B19200; break; + case 38400: baud = B38400; break; +#ifdef B57600 + case 57600: baud = B57600; break; +#endif +#ifdef B115200 + case 115200: baud = B115200; break; +#endif +#ifdef B230400 + case 230400: baud = B230400; break; +#endif + default: + return -1; + } + if (cfsetispeed (&tty, baud) != 0 || + cfsetospeed (&tty, baud) != 0) { + TRACE ("cfsetispeed/cfsetospeed"); + return -1; + } + + // Set the character size. + tty.c_cflag &= ~CSIZE; + switch (databits) { + case 5: + tty.c_cflag |= CS5; + break; + case 6: + tty.c_cflag |= CS6; + break; + case 7: + tty.c_cflag |= CS7; + break; + case 8: + tty.c_cflag |= CS8; + break; + default: + return -1; + } + + // Set the parity type. + tty.c_cflag &= ~(PARENB | PARODD); + tty.c_iflag &= ~(IGNPAR | PARMRK | INPCK); + switch (parity) { + case SERIAL_PARITY_NONE: // No parity + tty.c_iflag |= IGNPAR; + break; + case SERIAL_PARITY_EVEN: // Even parity + tty.c_cflag |= PARENB; + tty.c_iflag |= INPCK; + break; + case SERIAL_PARITY_ODD: // Odd parity + tty.c_cflag |= (PARENB | PARODD); + tty.c_iflag |= INPCK; + break; + default: + return -1; + } + + // Set the number of stop bits. + switch (stopbits) { + case 1: // One stopbit + tty.c_cflag &= ~CSTOPB; + break; + case 2: // Two stopbits + tty.c_cflag |= CSTOPB; + break; + default: + return -1; + } + + // Set the flow control. + switch (flowcontrol) { + case SERIAL_FLOWCONTROL_NONE: // No flow control. + #ifdef CRTSCTS + tty.c_cflag &= ~CRTSCTS; + #endif + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. + #ifdef CRTSCTS + tty.c_cflag |= CRTSCTS; + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + #else + return -1; // Hardware flow control is unsupported. + #endif + case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. + #ifdef CRTSCTS + tty.c_cflag &= ~CRTSCTS; + #endif + tty.c_iflag |= (IXON | IXOFF); + break; + default: + return -1; + } + + // Flush the input and output buffers. + if (tcflush (device->fd, TCIOFLUSH) != 0) { + TRACE ("tcflush"); + return -1; + } + + // Apply the new settings. + if (tcsetattr (device->fd, TCSANOW, &tty) != 0) { + TRACE ("tcsetattr"); + return -1; + } + + // tcsetattr() returns success if any of the requested changes could be + // successfully carried out. Therefore, when making multiple changes + // it may be necessary to follow this call with a further call to + // tcgetattr() to check that all changes have been performed successfully. + + struct termios active = {0}; + if (tcgetattr (device->fd, &active) != 0) { + TRACE ("tcgetattr"); + return -1; + } + if (memcmp (&tty, &active, sizeof (struct termios) != 0)) { + TRACE ("memcmp"); + return -1; + } + + return 0; +} + +// +// Configure the serial port (timeouts). +// + +int +serial_set_timeout (serial *device, long timeout) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + device->timeout = timeout; + + return 0; +} + + +struct timeouts_t { + int interval; + int total; + int end; +}; + + +static void +timeouts_init_read (serial* device, struct timeouts_t* timeouts, unsigned int count) +{ + timeouts->interval = -1; + timeouts->total = (device->timeout >= 0 ? device->timeout : -1); + if (timeouts->total > 0) + timeouts->end = serial_timer () + timeouts->total; +} + + +static void +timeouts_init_write (serial* device, struct timeouts_t* timeouts, unsigned int count) +{ + timeouts->interval = -1; + timeouts->total = -1; + if (timeouts->total > 0) + timeouts->end = serial_timer () + timeouts->total; +} + + +static int +timeouts_next (const struct timeouts_t* timeouts, unsigned int already) +{ + // Default timeout (INFINITE) + int result = -1; + + // Calculate the remaining total timeout. + if (timeouts->total != -1) { + if (timeouts->total > 0) { + result = timeouts->end - serial_timer (); + if (result < 0) + result = 0; + } else { + result = 0; + } + } + + // Adjust with the interval timeout. + if (already && timeouts->interval != -1) { + if (result == -1 || result > timeouts->interval) + result = timeouts->interval; + } + + // Return timeout value. + return result; +} + + +static int +posix_wait (int fd, const struct timeouts_t* timeouts, int input, unsigned int already) +{ + int rc = 0; + do { + // Calculate the remaining timeout. + int timeout = timeouts_next (timeouts, already); + + // Wait until the file descriptor is ready for reading/writing, or + // the timeout expires. A file descriptor is considered ready for + // reading/writing when a call to an input/output function with + // O_NONBLOCK clear would not block, whether or not the function + // would transfer data successfully. +/* + fd_set fds; + FD_ZERO (&fds); + FD_SET (fd, &fds); + if (timeout >= 0) { + struct timeval tv; + tv.tv_sec = (timeout / 1000); + tv.tv_usec = (timeout % 1000) * 1000; + if (input) + rc = select (fd + 1, &fds, NULL, NULL, &tv); + else + rc = select (fd + 1, NULL, &fds, NULL, &tv); + } else { + if (input) + rc = select (fd + 1, &fds, NULL, NULL, NULL); + else + rc = select (fd + 1, NULL, &fds, NULL, NULL); + } +*/ + struct pollfd pfd = {0}; + pfd.fd = fd; + pfd.events = (input ? POLLIN : POLLOUT); + rc = poll (&pfd, 1, timeout); + } while (rc < 0 && errno == EINTR); + + return rc; +} + + +static int +posix_read (int fd, void* buffer, unsigned int count) +{ + int rc = 0; + do { + rc = read (fd, buffer, count); + } while (rc == -1 && errno == EINTR); + + return rc; +} + + +static int +posix_write (int fd, const void* buffer, unsigned int count) +{ + int rc = 0; + do { + rc = write (fd, buffer, count); + } while (rc == -1 && errno == EINTR); + + return rc; +} + + +int +serial_read (serial* device, void* data, unsigned int size) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + // Initialize the timeout calculation. + struct timeouts_t timeouts = {0}; + timeouts_init_read (device, &timeouts, size); + + unsigned int nbytes = 0; + while (nbytes < size) { + // Wait until the file descriptor is ready for reading, or the timeout expires. + int rc = posix_wait (device->fd, &timeouts, 1, nbytes); + if (rc < 0) { + TRACE ("posix_wait"); + return -1; // Error during select/poll call. + } else if (rc == 0) + break; // Timeout. + + // Attempt to read data from the file descriptor. + int n = posix_read (device->fd, data + nbytes, size - nbytes); + if (n < 0) { + TRACE ("posix_read"); + return -1; // Error during read call. + } else if (n == 0) { + break; // EOF reached. + } else { + nbytes += n; // Success. + } + } + + return nbytes; +} + + +int +serial_write (serial* device, const void* data, unsigned int size) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + unsigned int nbytes = 0; + while (nbytes < size) { + // Attempt to write data to the file descriptor. + int n = posix_write (device->fd, data + nbytes, size - nbytes); + if (n < 0) { + TRACE ("posix_write"); + return -1; // Error during write call. + } + + nbytes += n; + } + + return nbytes; +} + + +int +serial_flush (serial *device, int queue) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + int flags = 0; + + switch (queue) { + case SERIAL_QUEUE_INPUT: + flags = TCIFLUSH; + case SERIAL_QUEUE_OUTPUT: + flags = TCOFLUSH; + default: + flags = TCIOFLUSH; + } + + if (tcflush (device->fd, flags) != 0) { + TRACE ("tcflush"); + return -1; + } + + return 0; +} + + +int +serial_drain (serial *device) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + int rc = 0; + do { + rc = tcdrain (device->fd); + } while (rc == -1 && errno == EINTR); + + if (rc != 0) { + TRACE ("tcdrain"); + return -1; + } + + return 0; +} + + +int +serial_send_break (serial *device) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + if (tcsendbreak (device->fd, 0) != 0) { + TRACE ("tcsendbreak"); + return -1; + } + + return 0; +} + + +static int +serial_set_status (int fd, int value, int level) +{ + int bits; + if (ioctl (fd, TIOCMGET, &bits)) { + TRACE ("ioctl"); + return -1; + } + + if (level) + bits |= value; + else + bits &= value; + + if (ioctl (fd, TIOCMSET, &bits)) { + TRACE ("ioctl"); + return -1; + } + + return 0; +} + + +int +serial_set_dtr (serial *device, int level) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + return serial_set_status (device->fd, TIOCM_DTR, level); +} + + +int +serial_set_rts (serial *device, int level) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + return serial_set_status (device->fd, TIOCM_RTS, level); +} + + +int +serial_get_received (serial *device) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + int bytes = 0; + if (ioctl (device->fd, TIOCINQ, &bytes) != 0) { + TRACE ("ioctl"); + return -1; + } + + return bytes; +} + + +int +serial_get_transmitted (serial *device) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + int bytes = 0; + if (ioctl (device->fd, TIOCOUTQ, &bytes) != 0) { + TRACE ("ioctl"); + return -1; + } + + return bytes; +} + + +int +serial_sleep (unsigned long timeout) +{ + struct timespec ts = {0}; + ts.tv_sec = (timeout / 1000); + ts.tv_nsec = (timeout % 1000) * 1000000; + + int rc = 0; + do { + rc = nanosleep (&ts, &ts); + } while (rc == -1 && errno == EINTR); + + if (rc != 0) { + TRACE ("nanosleep"); + return -1; + } + + return 0; +} + + +int +serial_timer () +{ + struct timeval tv = {0}; + if (gettimeofday (&tv, NULL) != 0) { + TRACE ("gettimeofday"); + return 0; + } + + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} diff --git a/serial_win32.c b/serial_win32.c new file mode 100644 index 0000000..3439f47 --- /dev/null +++ b/serial_win32.c @@ -0,0 +1,486 @@ +#include +#include + +#include "serial.h" + +#include +#define TRACE(expr) \ +{ \ + DWORD error = GetLastError (); \ + fprintf (stderr, "TRACE %s:%d: ", __FILE__, __LINE__); \ + if (expr) \ + fprintf (stderr, "%s: %s\n", expr, serial_errmsg ()); \ + else \ + fprintf (stderr, "%s\n", serial_errmsg ()); \ + SetLastError (error); \ +} + +struct serial { + /* + * The file descriptor corresponding to the serial port. + */ + HANDLE hFile; + /* + * Serial port settings are saved into this variables immediately + * after the port is opened. These settings are restored when the + * serial port is closed. + */ + DCB dcb; + COMMTIMEOUTS timeouts; +}; + +// +// Error reporting. +// + +int serial_errcode () +{ + return GetLastError (); +} + + +const char* serial_errmsg () +{ + static char buffer[256] = {0}; + unsigned int size = sizeof (buffer) / sizeof (char); + + DWORD errcode = GetLastError (); + DWORD rc = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errcode, 0, buffer, size, NULL); + // Remove certain characters ('\r', '\n' and '.') + // at the end of the error message. + while (rc > 0 && ( + buffer[rc-1] == '\n' || + buffer[rc-1] == '\r' || + buffer[rc-1] == '.')) { + buffer[rc-1] = '\0'; + rc--; + } + if (rc) { + return buffer; + } else { + return NULL; + } +} + +// +// Open the serial port. +// + +int +serial_open (serial** out, const char* name) +{ + if (out == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + // Allocate memory. + struct serial *device = malloc (sizeof (struct serial)); + if (device == NULL) { + TRACE ("malloc"); + return -1; // ERROR_OUTOFMEMORY (Not enough storage is available to complete this operation) + } + + // Open the device. + device->hFile = CreateFile (name, + GENERIC_READ | GENERIC_WRITE, 0, + NULL, // No security attributes. + OPEN_EXISTING, + 0, // Non-overlapped I/O. + NULL); + if (device->hFile == INVALID_HANDLE_VALUE) { + TRACE ("CreateFile"); + free (device); + return -1; + } + + // Retrieve the current communication settings and timeouts, + // to be able to restore them when closing the device. + // It is also used to check if the obtained handle + // represents a serial device. + if (!GetCommState (device->hFile, &device->dcb) || + !GetCommTimeouts (device->hFile, &device->timeouts)) { + TRACE ("GetCommState/GetCommTimeouts"); + CloseHandle (device->hFile); + free (device); + return -1; + } + + *out = device; + + return 0; +} + +// +// Close the serial port. +// + +int +serial_close (serial* device) +{ + if (device == NULL) + return 0; + + // Restore the initial communication settings and timeouts. + if (!SetCommState (device->hFile, &device->dcb) || + !SetCommTimeouts (device->hFile, &device->timeouts)) { + TRACE ("SetCommState/SetCommTimeouts"); + CloseHandle (device->hFile); + free (device); + return -1; + } + + // Close the device. + if (!CloseHandle (device->hFile)) { + TRACE ("CloseHandle"); + free (device); + return -1; + } + + // Free memory. + free (device); + + return 0; +} + +// +// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). +// + +int +serial_configure (serial *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + // Retrieve the current settings. + DCB dcb = {0}; + if (!GetCommState (device->hFile, &dcb)) { + TRACE ("GetCommState"); + return -1; + } + + dcb.fBinary = TRUE; // Enable Binary Transmission + + // Baudrate. + switch (baudrate) { + case 110: dcb.BaudRate = CBR_110; break; + case 300: dcb.BaudRate = CBR_300; break; + case 600: dcb.BaudRate = CBR_600; break; + case 1200: dcb.BaudRate = CBR_1200; break; + case 2400: dcb.BaudRate = CBR_2400; break; + case 4800: dcb.BaudRate = CBR_4800; break; + case 9600: dcb.BaudRate = CBR_9600; break; + case 14400: dcb.BaudRate = CBR_14400; break; + case 19200: dcb.BaudRate = CBR_19200; break; + case 38400: dcb.BaudRate = CBR_38400; break; + case 57600: dcb.BaudRate = CBR_57600; break; + case 115200: dcb.BaudRate = CBR_115200; break; + case 128000: dcb.BaudRate = CBR_128000; break; + case 256000: dcb.BaudRate = CBR_256000; break; + default: + return -1; + } + + // Character size. + if (databits >= 5 && databits <= 8) + dcb.ByteSize = databits; + else + return -1; + + // Parity checking. + switch (parity) { + case SERIAL_PARITY_NONE: // No parity + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + break; + case SERIAL_PARITY_EVEN: // Even parity + dcb.Parity = EVENPARITY; + dcb.fParity = TRUE; + break; + case SERIAL_PARITY_ODD: // Odd parity + dcb.Parity = ODDPARITY; + dcb.fParity = TRUE; + break; + default: + return -1; + } + // Stopbits. + switch (stopbits) { + case 1: // One stopbit + dcb.StopBits = ONESTOPBIT; + break; + case 2: // Two stopbits + dcb.StopBits = TWOSTOPBITS; + break; + default: + return -1; + } + + // Flow control. + switch (flowcontrol) { + case SERIAL_FLOWCONTROL_NONE: // No flow control. + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + break; + case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = TRUE; + dcb.fOutxDsrFlow = TRUE; + dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; + dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + break; + case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. + dcb.fInX = TRUE; + dcb.fOutX = TRUE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + break; + default: + return -1; + } + + // Flush the input and output buffers. + if (!PurgeComm (device->hFile, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR)) { + TRACE ("PurgeComm"); + return -1; + } + + // Apply the new settings. + if (!SetCommState (device->hFile, &dcb)) { + TRACE ("SetCommState"); + return -1; + } + + return 0; +} + +// +// Configure the serial port (timeouts). +// + +int +serial_set_timeout (serial* device, long timeout) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + // Retrieve the current timeouts. + COMMTIMEOUTS timeouts = {0}; + if (!GetCommTimeouts (device->hFile, &timeouts)) { + TRACE ("GetCommTimeouts"); + return -1; + } + + // Update the settings. + if (timeout < 0) { + // Blocking mode. + timeouts.ReadIntervalTimeout = 0; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + } else if (timeout == 0) { + // Non-blocking mode. + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + } else { + // Standard timeout mode. + timeouts.ReadIntervalTimeout = 0; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = timeout; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + } + + // Activate the new timeouts. + if (!SetCommTimeouts (device->hFile, &timeouts)) { + TRACE ("SetCommTimeouts"); + return -1; + } + + return 0; +} + + +int +serial_read (serial* device, void* data, unsigned int size) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + DWORD dwRead = 0; + if (!ReadFile (device->hFile, data, size, &dwRead, NULL)) { + TRACE ("ReadFile"); + return -1; + } + + return dwRead; +} + + +int +serial_write (serial* device, const void* data, unsigned int size) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + DWORD dwWritten = 0; + if (!WriteFile (device->hFile, data, size, &dwWritten, NULL)) { + TRACE ("WriteFile"); + return -1; + } + + return dwWritten; +} + + +int +serial_flush (serial* device, int queue) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + DWORD flags = 0; + + switch (queue) { + case SERIAL_QUEUE_INPUT: + flags = PURGE_RXABORT | PURGE_RXCLEAR; + case SERIAL_QUEUE_OUTPUT: + flags = PURGE_TXABORT | PURGE_TXCLEAR; + default: + flags = PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR; + } + + if (!PurgeComm (device->hFile, flags)) { + TRACE ("PurgeComm"); + return -1; + } + + return 0; +} + + +int +serial_drain (serial* device) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + return 0; +} + + +int +serial_send_break (serial* device) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + if (!SetCommBreak (device->hFile)) { + TRACE ("SetCommBreak"); + return -1; + } + + Sleep (250); + + if (!ClearCommBreak (device->hFile)) { + TRACE ("ClearCommBreak"); + return -1; + } + + return 0; +} + + +int +serial_set_dtr (serial* device, int level) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + int status = (level ? SETDTR : CLRDTR); + + if (!EscapeCommFunction (device->hFile, status)) { + TRACE ("EscapeCommFunction"); + return -1; + } + + return 0; +} + + +int +serial_set_rts (serial* device, int level) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + int status = (level ? SETRTS : CLRRTS); + + if (!EscapeCommFunction (device->hFile, status)) { + TRACE ("EscapeCommFunction"); + return -1; + } + + return 0; +} + + +int +serial_get_received (serial* device) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + COMSTAT stats = {0}; + + if (!ClearCommError (device->hFile, NULL, &stats)) { + TRACE ("ClearCommError"); + return -1; + } + + return stats.cbInQue; +} + + +int +serial_get_transmitted (serial* device) +{ + if (device == NULL) + return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect) + + COMSTAT stats = {0}; + + if (!ClearCommError (device->hFile, NULL, &stats)) { + TRACE ("ClearCommError"); + return -1; + } + + return stats.cbOutQue; +} + + +int +serial_sleep (unsigned long timeout) +{ + Sleep (timeout); + + return 0; +} + + +int +serial_timer () +{ + return GetTickCount (); +}