#include "serial_dev.h" int serial_dev_create(serial_dev_t *dev, const char* port, unsigned baud) { if (!dev) return -1; dev->port = port; dev->baud = baud; #ifdef IS_WINDOWS dev->handle = INVALID_HANDLE_VALUE; #else dev->fd = -1; #endif return 0; } int serial_dev_open(serial_dev_t *dev) { fprintf(stderr, "Opening serial port %s at %u baud.\n", dev->port, dev->baud); #ifdef IS_WINDOWS // Windows quirk: port = "COM10" is invalid, has to be encoded as "\\.\COM10". // This also works for COM below 9. So, let's give the user the ability to use // any "COMx" string and just prepend the "\\.\". char winPortName[64]; if(dev->port[0] != '\\') { snprintf(winPortName, sizeof(winPortName), "\\\\.\\%s", dev->port); } else { // copy verbatim if string already starts with a '\' snprintf(winPortName, sizeof(winPortName), "%s", dev->port); } dev->handle = CreateFileA(winPortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0); if (dev->handle == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_FILE_NOT_FOUND) { fprintf(stderr, "Serial port %s not found.\n", dev->port); // weird: without this, errno = 0 (no error). _set_errno(ERROR_FILE_NOT_FOUND); return -1; // Device not found } // Error while opening the device return -1; } DCB dcbSerialParams; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(dev->handle, &dcbSerialParams)) { return -1; } // set baud and 8N1 serial formatting dcbSerialParams.BaudRate = dev->baud; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; // write back if (!SetCommState(dev->handle, &dcbSerialParams)){ return -1; } // Set the timeout parameters to "no timeout" (blocking). // see https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts COMMTIMEOUTS timeouts; timeouts.ReadIntervalTimeout = 0; timeouts.ReadTotalTimeoutConstant = MAXDWORD; timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = MAXDWORD; timeouts.WriteTotalTimeoutMultiplier = 0; // Write the parameters if (!SetCommTimeouts(dev->handle, &timeouts)) { return -1; } #else struct termios attr; if ((dev->fd = open(dev->port, O_RDWR | O_NOCTTY)) == -1) { perror("open"); return -1; } if (tcgetattr(dev->fd, &attr) == -1) { perror("tcgetattr"); return -1; } cfmakeraw(&attr); cfsetspeed(&attr, dev->baud); if (tcsetattr(dev->fd, TCSANOW, &attr) == -1) { perror("tcsetattr"); return -1; } #endif // all okay if we get here return 0; } int serial_dev_write(serial_dev_t *dev, const void* data, size_t len) { #ifdef IS_WINDOWS DWORD dwBytesWritten; if (!WriteFile(dev->handle, data, len, &dwBytesWritten,NULL)) { return -1; } return (int) dwBytesWritten; #else return write(dev->fd, data, len); #endif } int serial_dev_read(serial_dev_t *dev, void* data, size_t len) { #ifdef IS_WINDOWS DWORD dwBytesRead = 0; if (!ReadFile(dev->handle, data, len, &dwBytesRead, NULL)) { return -1; } return (int) dwBytesRead; #else return read(dev->fd, data, len); #endif } int serial_dev_do_dtr_reset(serial_dev_t *dev) { #ifdef IS_WINDOWS // EscapeCommFunction returns 0 on fail if(EscapeCommFunction(dev->handle, SETDTR) == 0) { return -1; } if(EscapeCommFunction(dev->handle, CLRDTR) == 0) { return -1; } #else int argp = TIOCM_DTR; // Arduino DTR reset. if (ioctl(dev->fd, TIOCMBIC, &argp) == -1) { perror("ioctl"); return -1; } if (tcdrain(dev->fd) == -1) { perror("tcdrain"); return -1; } if (ioctl(dev->fd, TIOCMBIS, &argp) == -1) { perror("ioctl"); return -1; } #endif return 0; } int serial_dev_flush_rx(serial_dev_t *dev) { #ifdef IS_WINDOWS // PurgeComm returns 0 on fail if (PurgeComm(dev->handle, PURGE_RXCLEAR) == 0) { return -1; } #else if (tcflush(dev->fd, TCIFLUSH) == -1) { perror("tcflush"); return -1; } #endif return 0; } int serial_dev_close(serial_dev_t *dev) { #ifdef IS_WINDOWS if(!CloseHandle(dev->handle)) { return -1; } dev->handle = INVALID_HANDLE_VALUE; #else int ret = 0; if((ret = close(dev->fd)) != 0) { return ret; } dev->fd = -1; #endif return 0; }