173 lines
4.1 KiB
C
173 lines
4.1 KiB
C
|
#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;
|
||
|
}
|