Initial import.
This commit is contained in:
commit
3f33f82bc2
82
serial.h
Normal file
82
serial.h
Normal file
@ -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 */
|
||||
678
serial_posix.c
Normal file
678
serial_posix.c
Normal file
@ -0,0 +1,678 @@
|
||||
#define _POSIX_C_SOURCE 199309
|
||||
|
||||
#include <stdlib.h> // malloc, free
|
||||
#include <string.h> // strerror
|
||||
#include <errno.h> // errno
|
||||
#include <unistd.h> // open, close, read, write, isatty, usleep
|
||||
#include <fcntl.h> // fcntl
|
||||
#include <termios.h> // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak
|
||||
#include <poll.h> // poll
|
||||
#include <sys/ioctl.h> // ioctl
|
||||
#include <sys/time.h> // gettimeofday
|
||||
#include <time.h> // nanosleep
|
||||
|
||||
#include "serial.h"
|
||||
|
||||
#include <stdio.h> // 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;
|
||||
}
|
||||
486
serial_win32.c
Normal file
486
serial_win32.c
Normal file
@ -0,0 +1,486 @@
|
||||
#include <stddef.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "serial.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#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 ();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user