/*---------------------------------------------------------*\ | serial_port.cpp | | | | Cross Platform Serial COM Library for Windows and Linux | | This library provides access to serial ports with a | | common API for both Windows and Linux systems | | | | Adam Honse (calcprogrammer1@gmail.com) 21 Jan 2013 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-only | \*---------------------------------------------------------*/ #include "serial_port.h" /*---------------------------------------------------------*\ | serial_port (constructor) | | The default constructor does not initialize the serial | | port | \*---------------------------------------------------------*/ serial_port::serial_port() { /*-----------------------------------------------------*\ | Set default port configuration but do not open | \*-----------------------------------------------------*/ baud_rate = 9600; parity = SERIAL_PORT_PARITY_NONE; size = SERIAL_PORT_SIZE_8; stop_bits = SERIAL_PORT_STOP_BITS_1; flow_control = true; } /*---------------------------------------------------------*\ | serial_port (constructor) | | When created with port information, the constructor | | will automatically open port at baud rate | \*---------------------------------------------------------*/ serial_port::serial_port(const char * name, unsigned int baud) { /*-----------------------------------------------------*\ | Set default port configuration and open | \*-----------------------------------------------------*/ baud_rate = baud; parity = SERIAL_PORT_PARITY_NONE; size = SERIAL_PORT_SIZE_8; stop_bits = SERIAL_PORT_STOP_BITS_1; flow_control = true; serial_open(name); } /*---------------------------------------------------------*\ | serial_port (constructor) | | When created with port information, the constructor | | will automatically open port at baud rate | | with the given port configuration | \*---------------------------------------------------------*/ serial_port::serial_port ( const char * name, unsigned int baud, serial_port_parity parity, serial_port_size size, serial_port_stop_bits stop_bits, bool flow_control ) { /*-----------------------------------------------------*\ | Set default port configuration and open | \*-----------------------------------------------------*/ this->baud_rate = baud; this->parity = parity; this->size = size; this->stop_bits = stop_bits; this->flow_control = flow_control; serial_open(name); } /*---------------------------------------------------------*\ | ~serial_port (destructor) | | Closes the port before destroying the object | \*---------------------------------------------------------*/ serial_port::~serial_port() { serial_close(); } /*---------------------------------------------------------*\ | serial_open | | Opens the serial port using stored information | | Sets the baud rate to the stored baud rate | | 8 data bits, no parity, one stop bit | \*---------------------------------------------------------*/ bool serial_port::serial_open() { /*-----------------------------------------------------*\ | Windows-specific code path for serial port opening | \*-----------------------------------------------------*/ #ifdef _WIN32 // On Windows, ports above 9 need to have "\\.\" prepended to their path. For ports below 9, this is optional. // https://support.microsoft.com/en-us/topic/howto-specify-serial-ports-larger-than-com9-db9078a5-b7b6-bf00-240f-f749ebfd913e char full_path[100]; snprintf(full_path, sizeof(full_path), "\\\\.\\%s", port_name); /*-----------------------------------------*\ | Open the port read/write | \*-----------------------------------------*/ file_descriptor = CreateFile(full_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if((int)file_descriptor < 0) { return false; } /*-----------------------------------------*\ | Get the port configuration options | \*-----------------------------------------*/ SetupComm(file_descriptor, 1, 128); GetCommState(file_descriptor, &dcb); /*-----------------------------------------*\ | Configure baud rate | \*-----------------------------------------*/ dcb.BaudRate = baud_rate; /*-----------------------------------------*\ | Configure parity | \*-----------------------------------------*/ switch(parity) { case SERIAL_PORT_PARITY_NONE: dcb.Parity = NOPARITY; break; case SERIAL_PORT_PARITY_ODD: dcb.Parity = ODDPARITY; break; case SERIAL_PORT_PARITY_EVEN: dcb.Parity = EVENPARITY; break; } /*-----------------------------------------*\ | Configure size | \*-----------------------------------------*/ switch(size) { case SERIAL_PORT_SIZE_8: dcb.ByteSize = 8; break; case SERIAL_PORT_SIZE_7: dcb.ByteSize = 7; break; case SERIAL_PORT_SIZE_6: dcb.ByteSize = 6; break; case SERIAL_PORT_SIZE_5: dcb.ByteSize = 5; break; } /*-----------------------------------------*\ | Configure stop bits | \*-----------------------------------------*/ if(stop_bits == SERIAL_PORT_STOP_BITS_2) { dcb.StopBits = TWOSTOPBITS; } else { dcb.StopBits = ONESTOPBIT; } /*-----------------------------------------*\ | Configure flow control | \*-----------------------------------------*/ if(flow_control) { dcb.fRtsControl = RTS_CONTROL_ENABLE; } else { dcb.fRtsControl = RTS_CONTROL_DISABLE; } /*-----------------------------------------*\ | Configure additional parameters | \*-----------------------------------------*/ dcb.fAbortOnError = FALSE; //Abort on error dcb.fOutX = FALSE; //XON/XOFF off for transmit dcb.fInX = FALSE; //XON/XOFF off for receive dcb.fOutxCtsFlow = FALSE; //Turn off CTS flow control dcb.fOutxDsrFlow = FALSE; //Turn off DSR flow control dcb.fDtrControl = DTR_CONTROL_DISABLE; //Disable DTR control /*-----------------------------------------*\ | Set the port configuration options | \*-----------------------------------------*/ SetCommState(file_descriptor, &dcb); /*-----------------------------------------*\ | Set the port timeouts | \*-----------------------------------------*/ COMMTIMEOUTS timeouts = {0}; timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 10; SetCommTimeouts(file_descriptor, &timeouts); #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial port opening | \*-----------------------------------------------------*/ #ifdef __linux__ /*-----------------------------------------*\ | Open the port read/write with no delay | \*-----------------------------------------*/ file_descriptor = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY); if(file_descriptor < 0) { return false; } /*-----------------------------------------*\ | Get the port configuration options | \*-----------------------------------------*/ struct termios2 options; ioctl(file_descriptor, TCGETS2, &options); /*-----------------------------------------*\ | Configure baud rate | \*-----------------------------------------*/ options.c_cflag &= ~CBAUD; options.c_cflag |= CBAUDEX; options.c_ispeed = baud_rate; options.c_ospeed = baud_rate; /*-----------------------------------------*\ | Configure parity | \*-----------------------------------------*/ switch(parity) { case SERIAL_PORT_PARITY_NONE: options.c_cflag &= ~PARENB; options.c_cflag &= ~PARODD; break; case SERIAL_PORT_PARITY_ODD: options.c_cflag |= PARENB; options.c_cflag |= PARODD; break; case SERIAL_PORT_PARITY_EVEN: options.c_cflag |= PARENB; options.c_cflag &= ~PARODD; break; } /*-----------------------------------------*\ | Configure size | \*-----------------------------------------*/ options.c_cflag &= ~CSIZE; switch(size) { case SERIAL_PORT_SIZE_8: options.c_cflag |= CS8; break; case SERIAL_PORT_SIZE_7: options.c_cflag |= CS7; break; case SERIAL_PORT_SIZE_6: options.c_cflag |= CS6; break; case SERIAL_PORT_SIZE_5: options.c_cflag |= CS5; break; } /*-----------------------------------------*\ | Configure stop bits | \*-----------------------------------------*/ if(stop_bits == SERIAL_PORT_STOP_BITS_2) { options.c_cflag |= CSTOPB; } else { options.c_cflag &= ~CSTOPB; } /*-----------------------------------------*\ | Configure flow control | \*-----------------------------------------*/ if(flow_control) { options.c_cflag |= CRTSCTS; } else { options.c_cflag &= ~CRTSCTS; } /*-----------------------------------------*\ | Configure additional parameters | \*-----------------------------------------*/ options.c_lflag &= ~ICANON; options.c_lflag &= ~ECHO; // Disable echo options.c_lflag &= ~ECHOE; // Disable erasure options.c_lflag &= ~ECHONL; // Disable new-line echo options.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP options.c_lflag &= ~IEXTEN; // Disable input processing options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl options.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes options.c_oflag &= ~OPOST; // Disable output processing; /*-----------------------------------------*\ | Set the port configuration options | \*-----------------------------------------*/ ioctl(file_descriptor, TCSETS2, &options); #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial port opening | \*-----------------------------------------------------*/ #ifdef __APPLE__ /*-----------------------------------------*\ | Open the port read/write with no delay | \*-----------------------------------------*/ file_descriptor = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY); if(file_descriptor < 0) { return false; } /*-----------------------------------------*\ | Get the port configuration options | \*-----------------------------------------*/ struct termios options; tcgetattr(file_descriptor, &options); /*-----------------------------------------*\ | Configure parity | \*-----------------------------------------*/ switch(parity) { case SERIAL_PORT_PARITY_NONE: options.c_cflag &= ~PARENB; options.c_cflag &= ~PARODD; break; case SERIAL_PORT_PARITY_ODD: options.c_cflag |= PARENB; options.c_cflag |= PARODD; break; case SERIAL_PORT_PARITY_EVEN: options.c_cflag |= PARENB; options.c_cflag &= ~PARODD; break; } /*-----------------------------------------*\ | Configure size | \*-----------------------------------------*/ options.c_cflag &= ~CSIZE; switch(size) { case SERIAL_PORT_SIZE_8: options.c_cflag |= CS8; break; case SERIAL_PORT_SIZE_7: options.c_cflag |= CS7; break; case SERIAL_PORT_SIZE_6: options.c_cflag |= CS6; break; case SERIAL_PORT_SIZE_5: options.c_cflag |= CS5; break; } /*-----------------------------------------*\ | Configure stop bits | \*-----------------------------------------*/ if(stop_bits == SERIAL_PORT_STOP_BITS_2) { options.c_cflag |= CSTOPB; } else { options.c_cflag &= ~CSTOPB; } /*-----------------------------------------*\ | Configure flow control | \*-----------------------------------------*/ if(flow_control) { options.c_cflag |= CRTSCTS; } else { options.c_cflag &= ~CRTSCTS; } /*-----------------------------------------*\ | Configure additional parameters | \*-----------------------------------------*/ options.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO); options.c_iflag &= ~(INLCR | ICRNL); options.c_iflag |= IGNPAR | IGNBRK; options.c_oflag &= ~(OPOST | ONLCR | OCRNL); options.c_cc[VTIME] = 1; options.c_cc[VMIN] = 0; /*-----------------------------------------*\ | Set the port configuration options | \*-----------------------------------------*/ if(tcsetattr(file_descriptor, TCSANOW, &options) < 0) { close(file_descriptor); return false; } /*-----------------------------------------*\ | Configure baud rate | \*-----------------------------------------*/ ioctl(file_descriptor, IOSSIOSPEED, &baud_rate); #endif /*-----------------------------------------------------*\ | Return true if successful | \*-----------------------------------------------------*/ return true; } /*---------------------------------------------------------*\ | serial_open | | Opens the serial port without changing stored | | baud rate | \*---------------------------------------------------------*/ bool serial_port::serial_open(const char * name) { return serial_open(name, baud_rate); } /*---------------------------------------------------------*\ | serial_open | | Opens the serial port at baud rate | \*---------------------------------------------------------*/ bool serial_port::serial_open(const char* name, unsigned int baud) { snprintf(port_name,sizeof(port_name),"%s",name); baud_rate = baud; return serial_open(); } /*---------------------------------------------------------*\ | serial_close | | Closes the serial port | \*---------------------------------------------------------*/ void serial_port::serial_close() { /*-----------------------------------------------------*\ | Windows-specific code path for serial close | \*-----------------------------------------------------*/ #ifdef _WIN32 CloseHandle(file_descriptor); #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial close | \*-----------------------------------------------------*/ #ifdef __linux__ close(file_descriptor); #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial close | \*-----------------------------------------------------*/ #ifdef __APPLE__ close(file_descriptor); #endif } /*---------------------------------------------------------*\ | serial_read | | Reads bytes from the serial port into | | Returns the number of bytes actually read | | If less than bytes are available, it will read| | all available bytes | \*---------------------------------------------------------*/ int serial_port::serial_read(char * buffer, int length) { /*-----------------------------------------------------*\ | Windows-specific code path for serial read | \*-----------------------------------------------------*/ #ifdef _WIN32 DWORD bytesread; ReadFile(file_descriptor, buffer, length, &bytesread, NULL); return bytesread; #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial read | \*-----------------------------------------------------*/ #ifdef __linux__ int bytesread; bytesread = read(file_descriptor, buffer, length); return bytesread; #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial read | \*-----------------------------------------------------*/ #ifdef __APPLE__ int bytesread; bytesread = read(file_descriptor, buffer, length); return bytesread; #endif /*-----------------------------------------------------*\ | Return 0 on unsupported platforms | \*-----------------------------------------------------*/ return 0; } /*---------------------------------------------------------*\ | serial_write | | Writes bytes to the serial port from | | Returns the number of bytes actually written | | Does not check for null-termination, so if is | | greater than the number of bytes in , it will | | read past and may cause a segfault | \*---------------------------------------------------------*/ int serial_port::serial_write(char * buffer, int length) { /*-----------------------------------------------------*\ | Windows-specific code path for serial write | \*-----------------------------------------------------*/ #ifdef _WIN32 DWORD byteswritten; WriteFile(file_descriptor, buffer, length, &byteswritten, NULL); return byteswritten; #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial write | \*-----------------------------------------------------*/ #ifdef __linux__ int byteswritten; tcdrain(file_descriptor); byteswritten = write(file_descriptor, buffer, length); tcdrain(file_descriptor); return byteswritten; #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial write | \*-----------------------------------------------------*/ #ifdef __APPLE__ int byteswritten; tcdrain(file_descriptor); byteswritten = write(file_descriptor, buffer, length); tcdrain(file_descriptor); return byteswritten; #endif /*-----------------------------------------------------*\ | Return 0 on unsupported platforms | \*-----------------------------------------------------*/ return 0; } /*---------------------------------------------------------*\ | serial_flush | \*---------------------------------------------------------*/ void serial_port::serial_flush_rx() { #ifdef _WIN32 PurgeComm(file_descriptor, PURGE_RXABORT | PURGE_RXCLEAR); #endif #ifdef __linux__ tcflush(file_descriptor, TCIFLUSH); #endif #ifdef __APPLE__ tcflush(file_descriptor, TCIFLUSH); #endif } /*---------------------------------------------------------*\ | serial_flush_tx | \*---------------------------------------------------------*/ void serial_port::serial_flush_tx() { #ifdef _WIN32 PurgeComm(file_descriptor, PURGE_TXABORT | PURGE_TXCLEAR); #endif #ifdef __linux__ tcflush(file_descriptor, TCOFLUSH); #endif #ifdef __APPLE__ tcflush(file_descriptor, TCOFLUSH); #endif } /*---------------------------------------------------------*\ | serial_break | \*---------------------------------------------------------*/ void serial_port::serial_break() { /*-----------------------------------------------------*\ | Windows-specific code path for serial break | \*-----------------------------------------------------*/ #ifdef _WIN32 SetCommBreak(file_descriptor); Sleep(1); ClearCommBreak(file_descriptor); #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial break | \*-----------------------------------------------------*/ #ifdef __linux__ //Send break for at least 1 ms ioctl(file_descriptor, TIOCSBRK); usleep(1000); ioctl(file_descriptor, TIOCCBRK); #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial break | \*-----------------------------------------------------*/ #ifdef __APPLE__ //Send break for at least 1 ms ioctl(file_descriptor, TIOCSBRK); usleep(1000); ioctl(file_descriptor, TIOCCBRK); #endif } void serial_port::serial_set_dtr(bool dtr) { /*-----------------------------------------------------*\ | Windows-specific code path for serial set DTR | \*-----------------------------------------------------*/ #ifdef _WIN32 if(dtr) { EscapeCommFunction(file_descriptor, SETDTR); } else { EscapeCommFunction(file_descriptor, CLRDTR); } #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial set DTR | \*-----------------------------------------------------*/ #ifdef __linux__ const int DTRFLAG = TIOCM_DTR; if(dtr) { ioctl(file_descriptor, TIOCMBIS, &DTRFLAG); } else { ioctl(file_descriptor, TIOCMBIC, &DTRFLAG); } #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial set DTR | \*-----------------------------------------------------*/ #ifdef __APPLE__ const int DTRFLAG = TIOCM_DTR; if(dtr) { ioctl(file_descriptor, TIOCMBIS, &DTRFLAG); } else { ioctl(file_descriptor, TIOCMBIC, &DTRFLAG); } #endif } void serial_port::serial_set_rts(bool rts) { /*-----------------------------------------------------*\ | Windows-specific code path for serial set RTS | \*-----------------------------------------------------*/ #ifdef _WIN32 if(rts) { EscapeCommFunction(file_descriptor, SETRTS); } else { EscapeCommFunction(file_descriptor, CLRRTS); } #endif /*-----------------------------------------------------*\ | Linux-specific code path for serial set RTS | \*-----------------------------------------------------*/ #ifdef __linux__ const int RTSFLAG = TIOCM_RTS; if(rts) { ioctl(file_descriptor, TIOCMBIS, &RTSFLAG); } else { ioctl(file_descriptor, TIOCMBIC, &RTSFLAG); } #endif /*-----------------------------------------------------*\ | MacOS-specific code path for serial set RTS | \*-----------------------------------------------------*/ #ifdef __APPLE__ const int RTSFLAG = TIOCM_RTS; if(rts) { ioctl(file_descriptor, TIOCMBIS, &RTSFLAG); } else { ioctl(file_descriptor, TIOCMBIC, &RTSFLAG); } #endif }