From 74510d4b59f755dc2db687e0e321eed25181cc37 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 27 Apr 2013 23:35:45 +0100 Subject: [PATCH] Initial version. Builds for Linux and Windows. --- serialport.c | 568 +++++++++++++++++++++++++++++++++++++++++++++++++++ serialport.h | 76 +++++++ 2 files changed, 644 insertions(+) create mode 100644 serialport.c create mode 100644 serialport.h diff --git a/serialport.c b/serialport.c new file mode 100644 index 0000000..c89f44e --- /dev/null +++ b/serialport.c @@ -0,0 +1,568 @@ +/* + * This file is part of the libserialport project. + * + * Copyright (C) 2010-2012 Bert Vermeulen + * Copyright (C) 2010-2012 Uwe Hermann + * Copyright (C) 2013 Martin Ling + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif +#include +#include + +#include "serialport.h" + +static int sp_validate_port(struct sp_port *port) +{ + if (port == NULL) + return 0; +#ifdef _WIN32 + if (port->hdl == INVALID_HANDLE_VALUE) + return 0; +#else + if (port->fd < 0) + return 0; +#endif + return 1; +} + +#define CHECK_PORT() do { if (!sp_validate_port(port)) return SP_ERR_ARG; } while (0) + +/** + * Open the specified serial port. + * + * @param port Pointer to empty port structure allocated by caller. + * @param portname Name of port to open. + * @param flags Flags to use when opening the serial port. Possible flags + * are: SP_MODE_RDWR, SP_MODE_RDONLY, SP_MODE_NONBLOCK. + * + * @return SP_OK on success, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid port or name is passed. + */ +int sp_open(struct sp_port *port, char *portname, int flags) +{ + if (!port) + return SP_ERR_ARG; + + if (!portname) + return SP_ERR_ARG; + + port->name = portname; + +#ifdef _WIN32 + DWORD desired_access = 0, flags_and_attributes = 0; + /* Map 'flags' to the OS-specific settings. */ + desired_access |= GENERIC_READ; + flags_and_attributes = FILE_ATTRIBUTE_NORMAL; + if (flags & SP_MODE_RDWR) + desired_access |= GENERIC_WRITE; + if (flags & SP_MODE_NONBLOCK) + flags_and_attributes |= FILE_FLAG_OVERLAPPED; + + port->hdl = CreateFile(port->name, desired_access, 0, 0, + OPEN_EXISTING, flags_and_attributes, 0); + if (port->hdl == INVALID_HANDLE_VALUE) + return SP_ERR_FAIL; +#else + int flags_local = 0; + /* Map 'flags' to the OS-specific settings. */ + if (flags & SP_MODE_RDWR) + flags_local |= O_RDWR; + if (flags & SP_MODE_RDONLY) + flags_local |= O_RDONLY; + if (flags & SP_MODE_NONBLOCK) + flags_local |= O_NONBLOCK; + + if ((port->fd = open(port->name, flags_local)) < 0) + return SP_ERR_FAIL; +#endif + + return SP_OK; +} + +/** + * Close the specified serial port. + * + * @param port Pointer to port structure. + * + * @return SP_OK on success, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid port is passed. + */ +int sp_close(struct sp_port *port) +{ + CHECK_PORT(); + +#ifdef _WIN32 + /* Returns non-zero upon success, 0 upon failure. */ + if (CloseHandle(port->hdl) == 0) + return SP_ERR_FAIL; +#else + /* Returns 0 upon success, -1 upon failure. */ + if (close(port->fd) == -1) + return SP_ERR_FAIL; +#endif + + return SP_OK; +} + +/** + * Flush serial port buffers. + * + * @param port Pointer to port structure. + * + * @return SP_OK on success, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid port is passed. + */ +int sp_flush(struct sp_port *port) +{ + CHECK_PORT(); + +#ifdef _WIN32 + /* Returns non-zero upon success, 0 upon failure. */ + if (PurgeComm(port->hdl, PURGE_RXCLEAR | PURGE_TXCLEAR) == 0) + return SP_ERR_FAIL; +#else + /* Returns 0 upon success, -1 upon failure. */ + if (tcflush(port->fd, TCIOFLUSH) < 0) + return SP_ERR_FAIL; +#endif + return SP_OK; +} + +/** + * Write a number of bytes to the specified serial port. + * + * @param port Pointer to port structure. + * @param buf Buffer containing the bytes to write. + * @param count Number of bytes to write. + * + * @return The number of bytes written, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid port is passed. + */ +int sp_write(struct sp_port *port, const void *buf, size_t count) +{ + CHECK_PORT(); + + if (!buf) + return SP_ERR_ARG; + +#ifdef _WIN32 + DWORD written = 0; + /* Returns non-zero upon success, 0 upon failure. */ + if (WriteFile(port->hdl, buf, count, &written, NULL) == 0) + return SP_ERR_FAIL; + return written; +#else + /* Returns the number of bytes written, or -1 upon failure. */ + ssize_t written = write(port->fd, buf, count); + if (written < 0) + return SP_ERR_FAIL; + else + return written;; +#endif +} + +/** + * Read a number of bytes from the specified serial port. + * + * @param port Pointer to port structure. + * @param buf Buffer where to store the bytes that are read. + * @param count The number of bytes to read. + * + * @return The number of bytes read, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid port is passed. + */ +int sp_read(struct sp_port *port, void *buf, size_t count) +{ + CHECK_PORT(); + + if (!buf) + return SP_ERR_ARG; + +#ifdef _WIN32 + DWORD bytes_read = 0; + /* Returns non-zero upon success, 0 upon failure. */ + if (ReadFile(port->hdl, buf, count, &bytes_read, NULL) == 0) + return SP_ERR_FAIL; + return bytes_read; +#else + ssize_t bytes_read; + /* Returns the number of bytes read, or -1 upon failure. */ + if ((bytes_read = read(port->fd, buf, count)) < 0) + return SP_ERR_FAIL; + return bytes_read; +#endif +} + +/** + * Set serial parameters for the specified serial port. + * + * @param port Pointer to port structure. + * @param baudrate The baudrate to set. + * @param bits The number of data bits to use. + * @param parity The parity setting to use (0 = none, 1 = even, 2 = odd). + * @param stopbits The number of stop bits to use (1 or 2). + * @param flowcontrol The flow control settings to use (0 = none, 1 = RTS/CTS, + * 2 = XON/XOFF). + * + * @return The number of bytes read, SP_ERR_FAIL on failure, + * or SP_ERR_ARG if an invalid argument is passed. + */ +int sp_set_params(struct sp_port *port, int baudrate, + int bits, int parity, int stopbits, + int flowcontrol, int rts, int dtr) +{ + CHECK_PORT(); + +#ifdef _WIN32 + DCB dcb; + + if (!GetCommState(port->hdl, &dcb)) + return SP_ERR_FAIL; + + switch (baudrate) { + /* + * The baudrates 50/75/134/150/200/1800/230400/460800 do not seem to + * have documented CBR_* macros. + */ + 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; /* Not available on Unix? */ + 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; /* Not available on Unix? */ + break; + case 256000: + dcb.BaudRate = CBR_256000; /* Not available on Unix? */ + break; + default: + return SP_ERR_ARG; + } + + switch (stopbits) { + /* Note: There's also ONE5STOPBITS == 1.5 (unneeded so far). */ + case 1: + dcb.StopBits = ONESTOPBIT; + break; + case 2: + dcb.StopBits = TWOSTOPBITS; + break; + default: + return SP_ERR_ARG; + } + + switch (parity) { + /* Note: There's also SPACEPARITY, MARKPARITY (unneeded so far). */ + case SP_PARITY_NONE: + dcb.Parity = NOPARITY; + break; + case SP_PARITY_EVEN: + dcb.Parity = EVENPARITY; + break; + case SP_PARITY_ODD: + dcb.Parity = ODDPARITY; + break; + default: + return SP_ERR_ARG; + } + + if (rts != -1) { + if (rts) + dcb.fRtsControl = RTS_CONTROL_ENABLE; + else + dcb.fRtsControl = RTS_CONTROL_DISABLE; + } + + if (dtr != -1) { + if (dtr) + dcb.fDtrControl = DTR_CONTROL_ENABLE; + else + dcb.fDtrControl = DTR_CONTROL_DISABLE; + } + + if (!SetCommState(port->hdl, &dcb)) + return SP_ERR_FAIL; +#else + struct termios term; + speed_t baud; + int controlbits; + + if (tcgetattr(port->fd, &term) < 0) + return SP_ERR_FAIL; + + switch (baudrate) { + 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; + case 57600: + baud = B57600; + break; + case 115200: + baud = B115200; + break; + case 230400: + baud = B230400; + break; +#if !defined(__APPLE__) && !defined(__OpenBSD__) + case 460800: + baud = B460800; + break; +#endif + default: + return SP_ERR_ARG; + } + + if (cfsetospeed(&term, baud) < 0) + return SP_ERR_FAIL; + + if (cfsetispeed(&term, baud) < 0) + return SP_ERR_FAIL; + + term.c_cflag &= ~CSIZE; + switch (bits) { + case 8: + term.c_cflag |= CS8; + break; + case 7: + term.c_cflag |= CS7; + break; + default: + return SP_ERR_ARG; + } + + term.c_cflag &= ~CSTOPB; + switch (stopbits) { + case 1: + term.c_cflag &= ~CSTOPB; + break; + case 2: + term.c_cflag |= CSTOPB; + break; + default: + return SP_ERR_ARG; + } + + term.c_iflag &= ~(IXON | IXOFF); + term.c_cflag &= ~CRTSCTS; + switch (flowcontrol) { + case 0: + /* No flow control. */ + break; + case 1: + term.c_cflag |= CRTSCTS; + break; + case 2: + term.c_iflag |= IXON | IXOFF; + break; + default: + return SP_ERR_ARG; + } + + term.c_iflag &= ~IGNPAR; + term.c_cflag &= ~(PARODD | PARENB); + switch (parity) { + case SP_PARITY_NONE: + term.c_iflag |= IGNPAR; + break; + case SP_PARITY_EVEN: + term.c_cflag |= PARENB; + break; + case SP_PARITY_ODD: + term.c_cflag |= PARENB | PARODD; + break; + default: + return SP_ERR_ARG; + } + + /* Turn off all serial port cooking. */ + term.c_iflag &= ~(ISTRIP | INLCR | ICRNL); + term.c_oflag &= ~(ONLCR | OCRNL | ONOCR); +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) + term.c_oflag &= ~OFILL; +#endif + + /* Disable canonical mode, and don't echo input characters. */ + term.c_lflag &= ~(ICANON | ECHO); + + /* Write the configured settings. */ + if (tcsetattr(port->fd, TCSADRAIN, &term) < 0) + return SP_ERR_FAIL; + + if (rts != -1) { + controlbits = TIOCM_RTS; + if (ioctl(port->fd, rts ? TIOCMBIS : TIOCMBIC, + &controlbits) < 0) + return SP_ERR_FAIL; + } + + if (dtr != -1) { + controlbits = TIOCM_DTR; + if (ioctl(port->fd, dtr ? TIOCMBIS : TIOCMBIC, + &controlbits) < 0) + return SP_ERR_FAIL; + } +#endif + + return SP_OK; +} + +/** + * Get error code for failed operation. + * + * In order to obtain the correct result, this function should be called + * straight after the failure, before executing any other system operations. + * + * @return The system's numeric code for the error that caused the last + * operation to fail. + */ +int sp_last_error_code(void) +{ +#ifdef _WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +/** + * Get error message for failed operation. + * + * In order to obtain the correct result, this function should be called + * straight after the failure, before executing other system operations. + * + * @return The system's message for the error that caused the last + * operation to fail. This string may be allocated by the function, + * and can be freed after use by calling sp_free_error_message. + */ +char *sp_last_error_message(void) +{ +#ifdef _WIN32 + LPVOID message; + DWORD error = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL ); + + return message; +#else + return strerror(errno); +#endif +} + +/** + * Free error message. + * + * This function can be used to free a string returned by the + * sp_last_error_message function. + */ +void sp_free_error_message(char *message) +{ +#ifdef _WIN32 + LocalFree(message); +#endif +} diff --git a/serialport.h b/serialport.h new file mode 100644 index 0000000..dc3e22d --- /dev/null +++ b/serialport.h @@ -0,0 +1,76 @@ +/* + * This file is part of the libserialport project. + * + * Copyright (C) 2013 Martin Ling + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#ifdef _WIN32 +#include +#endif + +/* A serial port. */ +struct sp_port { + /* Name used to open the port */ + char *name; + /* OS-specific port handle */ +#ifdef _WIN32 + HANDLE hdl; +#else + int fd; +#endif +}; + +/* Return values. */ +enum { + /* Operation completed successfully. */ + SP_OK = 0, + /* A system error occured while executing the operation. */ + SP_ERR_FAIL = -1, + /* Invalid arguments were passed to the function. */ + SP_ERR_ARG = -2 +}; + +/* Port access modes. */ +enum { + /* Open port for read/write access. */ + SP_MODE_RDWR = 1, + /* Open port for read access only. */ + SP_MODE_RDONLY = 2, + /* Open port in non-blocking mode. */ + SP_MODE_NONBLOCK = 4 +}; + +/* Parity settings. */ +enum { + /* No parity. */ + SP_PARITY_NONE = 0, + /* Even parity. */ + SP_PARITY_EVEN = 1, + /* Odd parity. */ + SP_PARITY_ODD = 2 +}; + +int sp_open(struct sp_port *port, char *portname, int flags); +int sp_close(struct sp_port *port); +int sp_flush(struct sp_port *port); +int sp_write(struct sp_port *port, const void *buf, size_t count); +int sp_read(struct sp_port *port, void *buf, size_t count); +int sp_set_params(struct sp_port *port, int baudrate, int bits, int parity, + int stopbits, int flowcontrol, int rts, int dtr); +int sp_last_error_code(void); +char *sp_last_error_message(void); +void sp_free_error_message(char *message);