#include #include #include #include #include #include #include #include #include #include #include "gfaserial.h" #define min(a, b) (((a) < (b)) ? (a) : (b)) ///////////////////////////////////////////////////////////////////////////// // https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-ports-using-c-cpp/#everything-is-a-file ///////////////////////////////////////////////////////////////////////////// typedef struct _GFA_SERIAL_DEVICE { int fd; char *pszDeviceName; GFA_SER_CFG_PARAMS cfg; struct termios tty; struct termios ttySave; struct timeval tvRX; struct timeval tvTX; struct timeval tvEcho; struct timeval tvPurge; }GFA_SERIAL_DEVICE, *LPGFA_SERIAL_DEVICE; typedef const GFA_SERIAL_DEVICE *LPCGFA_SERIAL_DEVICE; ///////////////////////////////////////////////////////////////////////////// static speed_t _MapBaudrate(uint32_t b) { speed_t s; switch(b) { case 0: s = B0; break; case 50: s = B50; break; case 75: s = B75; break; case 110: s = B110; break; case 134: s = B134; break; case 150: s = B150; break; case 200: s = B200; break; case 300: s = B300; break; case 600: s = B600; break; case 1200: s = B1200; break; case 1800: s = B1800; break; case 2400: s = B2400; break; case 4800: s = B4800; break; case 9600: s = B9600; break; case 19200: s = B19200; break; case 38400: s = B38400; break; case 57600: s = B57600; break; case 115200: s = B115200; break; case 230400: s = B230400; break; case 460800: s = B460800; break; case 921600: s = B921600; break; default: s = (speed_t)-1; break; } return s; } ///////////////////////////////////////////////////////////////////////////// static void _SetDefaultTimeouts(LPGFA_SERIAL_DEVICE psd) { if(psd) { psd->tvRX.tv_sec = 0; psd->tvRX.tv_usec = 200000; psd->tvTX.tv_sec = 0; psd->tvTX.tv_usec = 100000; psd->tvEcho.tv_sec = 0; psd->tvEcho.tv_usec = 50000; psd->tvPurge.tv_sec = 0; psd->tvPurge.tv_usec = 10000; } } static void _CopyTimeval(struct timeval *ptvTo, const struct timeval *ptvFrom) { ptvTo->tv_sec = ptvFrom->tv_sec; ptvTo->tv_usec = ptvFrom->tv_usec; } ///////////////////////////////////////////////////////////////////////////// static bool _ReadEcho(HGFADEVICE hSer, const void *pData, size_t nWritten) { uint8_t b[256]; ssize_t nRet; struct timeval tvSave; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; ssize_t nLeft = (ssize_t)nWritten, nRead = 0, nToRead; const char *pszData = (const char*)pData; if(!nLeft) return false; if( !GfaSerialGetTimeouts(hSer, &tvSave, NULL) || !GfaSerialSetTimeouts(hSer, &psd->tvEcho, NULL)) return false; do { nToRead = min(nLeft, (ssize_t)sizeof(b)); if((nRet = GfaSerialReceive(hSer, b, nToRead)) <= 0) { GfaSerialSetTimeouts(hSer, &tvSave, NULL); return false; } if(memcmp(b, pszData, nRet)) { GfaSerialSetTimeouts(hSer, &tvSave, NULL); return false; } pszData += nRet; nRead += nRet; nLeft = (ssize_t)nWritten - nRead; } while(nLeft > 0); GfaSerialSetTimeouts(hSer, &tvSave, NULL); return (nRead == (ssize_t)nWritten); } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// HGFADEVICE GfaSerialOpen(const char *pszDeviceName, LPCGFA_SER_CFG_PARAMS pscp, size_t nSizeCfgParams) { if(pszDeviceName && *pszDeviceName && pscp && nSizeCfgParams >= sizeof(GFA_SER_CFG_PARAMS)) { LPGFA_SERIAL_DEVICE psd = malloc(sizeof(GFA_SERIAL_DEVICE)); memset(psd, 0, sizeof(GFA_SERIAL_DEVICE)); ///////////////////////////////////////////////////////////////////// // Open the device if((psd->fd = open(pszDeviceName, O_RDWR | O_NONBLOCK)) < 0) { GfaSerialClose(psd); return NULL; } ///////////////////////////////////////////////////////////////////// // Lock device descriptor exclusive if(flock(psd->fd, LOCK_EX | LOCK_NB) < 0) { GfaSerialClose(psd); return NULL; } ///////////////////////////////////////////////////////////////////// // Get current config and save it for restore on close if(tcgetattr(psd->fd, &psd->tty) != 0) { GfaSerialClose(psd); return NULL; } memcpy(&psd->ttySave, &psd->tty, sizeof(struct termios)); ///////////////////////////////////////////////////////////////////// // Set new config if(GfaSerialSetConfig(psd, pscp, nSizeCfgParams) < 0) { GfaSerialClose(psd); return NULL; } ///////////////////////////////////////////////////////////////////// // Set deault timeouts. _SetDefaultTimeouts(psd); ///////////////////////////////////////////////////////////////////// // Everything ok. psd->pszDeviceName = strdup(pszDeviceName); GfaSerialPurgeRXBuffer((HGFADEVICE)psd); // clear RX buffer return (HGFADEVICE)psd; } errno = EINVAL; return NULL; } ///////////////////////////////////////////////////////////////////////////// void GfaSerialClose(HGFADEVICE hSer) { if(hSer) { int nErrno = errno; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; if(psd->fd >= 0) { tcsetattr(psd->fd, TCSAFLUSH, &psd->ttySave); flock(psd->fd, LOCK_UN | LOCK_NB); close(psd->fd); } if(psd->pszDeviceName) free(psd->pszDeviceName); free(psd); errno = nErrno; } } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialGetConfig(HGFADEVICE hSer, LPGFA_SER_CFG_PARAMS pscp, size_t nSizeCfgParams) { if(hSer && pscp && nSizeCfgParams >= sizeof(GFA_SER_CFG_PARAMS)) { LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; memcpy(pscp, &psd->cfg, sizeof(GFA_SER_CFG_PARAMS)); return sizeof(GFA_SER_CFG_PARAMS); } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// int GfaSerialSetConfig(HGFADEVICE hSer, LPCGFA_SER_CFG_PARAMS pscp, size_t nSizeCfgParams) { if(hSer && pscp && nSizeCfgParams >= sizeof(GFA_SER_CFG_PARAMS)) { int nRet; speed_t s; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; ///////////////////////////////////////////////////////////////////// // Baud Rate if((s = _MapBaudrate(pscp->baud)) == (speed_t)-1) { errno = EINVAL; return -1; } if( ((nRet = cfsetispeed(&psd->tty, s)) < 0) || ((nRet = cfsetospeed(&psd->tty, s)) < 0)) { return nRet; } ///////////////////////////////////////////////////////////////////// // data bits switch(pscp->data) { case 5: psd->tty.c_cflag &= ~CSIZE; psd->tty.c_cflag |= CS5; // 5 bits per byte break; case 6: psd->tty.c_cflag &= ~CSIZE; psd->tty.c_cflag |= CS6; // 6 bits per byte break; case 7: psd->tty.c_cflag &= ~CSIZE; psd->tty.c_cflag |= CS7; // 7 bits per byte break; case 8: psd->tty.c_cflag &= ~CSIZE; psd->tty.c_cflag |= CS8; // 8 bits per byte break; default: errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////// // stop bits switch(pscp->stop) { case 1: psd->tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication break; case 2: psd->tty.c_cflag |= CSTOPB; // Set stop field, two stop bits used in communication break; default: errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////// // parity switch(toupper(pscp->parity)) { case 'N': psd->tty.c_cflag &= ~(PARENB | PARODD); // Clear parity bit, disabling parity break; case 'E': psd->tty.c_cflag |= PARENB; // Set parity bit, enabling parity psd->tty.c_cflag &= ~PARODD; // Clear odd parity bit, enabling even parity break; case 'O': psd->tty.c_cflag |= (PARENB | PARODD); // Set parity bits, enabling odd parity break; default: errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////// // RTS/CTS flow control // If the CRTSCTS field is set, hardware RTS/CTS flow control is enabled. The most common setting here is to disable it. // Enabling this when it should be disabled can result in your serial port receiving no data, as the sender will buffer // it indefinitely, waiting for you to be “ready”. if(pscp->flowHW) psd->tty.c_cflag |= CRTSCTS; // Enable RTS/CTS hardware flow control else psd->tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) ///////////////////////////////////////////////////////////////////// // Setting CLOCAL disables modem-specific signal lines such as carrier detect. It also // prevents the controlling process from getting sent a SIGHUP signal when a modem disconnect // is detected, which is usually a good thing here. Setting CLOCAL allows us to read data (we definitely want that!). psd->tty.c_cflag |= (CREAD | CLOCAL); // Turn on READ & ignore ctrl lines (CLOCAL = 1) ///////////////////////////////////////////////////////////////////// // UNIX systems provide two basic modes of input, canonical and non-canonical mode. In canonical mode, input is // processed when a new line character is received. The receiving application receives that data line-by-line. // This is usually undesirable when dealing with a serial port, and so we normally want to disable canonical mode. // Also, in canonical mode, some characters such as backspace are treated specially, and are used to edit the // current line of text (erase). Again, we don’t want this feature if processing raw serial data, as it will // cause particular bytes to go missing! psd->tty.c_lflag &= ~ICANON; ///////////////////////////////////////////////////////////////////// // Echo // If this bit is set, sent characters will be echoed back. Because we disabled canonical mode, // I don’t think these bits actually do anything, but it doesn’t harm to disable them just in case! psd->tty.c_lflag &= ~ECHO; // Disable echo psd->tty.c_lflag &= ~ECHOE; // Disable erasure psd->tty.c_lflag &= ~ECHONL; // Disable new-line echo ///////////////////////////////////////////////////////////////////// // Signal chars // When the ISIG bit is set, INTR, QUIT and SUSP characters are interpreted. // We don’t want this with a serial port, so clear this bit. psd->tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP ///////////////////////////////////////////////////////////////////// // Software Flow Control (IXOFF, IXON, IXANY) // Clearing IXOFF, IXON and IXANY disables software flow control. if(pscp->flowSW) psd->tty.c_iflag |= (IXON | IXOFF); // Turn on s/w flow ctrl else psd->tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl ///////////////////////////////////////////////////////////////////// // Disabling Special Handling Of Bytes On Receive // Clearing all of the following bits disables any special handling of the bytes as they // are received by the serial port, before they are passed to the application. We just want the raw data! psd->tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes ///////////////////////////////////////////////////////////////////// // When configuring a serial port, we want to disable any special handling of output chars/bytes. psd->tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) psd->tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed ///////////////////////////////////////////////////////////////////// // VMIN and VTIME psd->tty.c_cc[VTIME] = psd->tty.c_cc[VMIN] = 0; // No blocking, return immediately with what is available ///////////////////////////////////////////////////////////////////// // set configuration if((nRet = tcsetattr(psd->fd, TCSAFLUSH, &psd->tty)) == 0) memcpy(&psd->cfg, pscp, sizeof(GFA_SER_CFG_PARAMS)); return nRet; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// bool GfaSerialGetDeviceInterface(LPGFA_GENERIC_DEVICE_INTERFACE pDevItf) { if(pDevItf) { pDevItf->pfnOpen = (PFN_GFA_GENERIC_DEV_OPEN)GfaSerialOpen; pDevItf->pfnClose = GfaSerialClose; pDevItf->pfnGetConfig = (PFN_GFA_GENERIC_DEV_GET_CONFIG)GfaSerialGetConfig; pDevItf->pfnSetConfig = (PFN_GFA_GENERIC_DEV_SET_CONFIG)GfaSerialSetConfig; pDevItf->pfnGetTimeouts = GfaSerialGetTimeouts; pDevItf->pfnSetTimeouts = GfaSerialSetTimeouts; pDevItf->pfnPurgeRXBuffer = GfaSerialPurgeRXBuffer; pDevItf->pfnReceive = GfaSerialReceive; pDevItf->pfnRead = GfaSerialRead; pDevItf->pfnPop = GfaSerialPop; pDevItf->pfnPeek = GfaSerialPeek; pDevItf->pfnTransmit = GfaSerialTransmit; pDevItf->pfnWrite = GfaSerialWrite; pDevItf->pfnPush = GfaSerialPush; return true; } errno = EINVAL; return false; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialPurgeRXBuffer(HGFADEVICE hSer) { struct timeval tvSave; if(hSer && GfaSerialGetTimeouts(hSer, &tvSave, NULL)) { uint8_t b[256]; ssize_t nRet = 0, nRx; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; GfaSerialSetTimeouts(hSer, &psd->tvPurge, NULL); while((nRx = GfaSerialReceive(hSer, b, sizeof(b))) > 0) nRet += nRx; GfaSerialSetTimeouts(hSer, &tvSave, NULL); return nRet; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// bool GfaSerialGetTimeouts(HGFADEVICE hSer, struct timeval *ptvRX, struct timeval *ptvTX) { if(hSer && (ptvRX || ptvTX)) { LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; if(ptvRX) memcpy(ptvRX, &psd->tvRX, sizeof(struct timeval)); if(ptvTX) memcpy(ptvTX, &psd->tvTX, sizeof(struct timeval)); return true; } errno = EINVAL; return false; } ///////////////////////////////////////////////////////////////////////////// bool GfaSerialSetTimeouts(HGFADEVICE hSer, const struct timeval *ptvRX, const struct timeval *ptvTX) { if(hSer && (ptvRX || ptvTX)) { LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; if(ptvRX) memcpy(&psd->tvRX, ptvRX, sizeof(struct timeval)); if(ptvTX) memcpy(&psd->tvTX, ptvTX, sizeof(struct timeval)); return true; } errno = EINVAL; return false; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialTransmit(HGFADEVICE hSer, const void *pData, size_t nCbToWrite) { if(hSer && pData && nCbToWrite) { fd_set fds; struct timeval tv; ssize_t nRet = 0; ssize_t nWritten = 0; const char *pszData = (const char*)pData; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; _CopyTimeval(&tv, &psd->tvTX); while(nWritten < (ssize_t)nCbToWrite) { FD_ZERO(&fds); FD_SET(psd->fd, &fds); nRet = select(psd->fd + 1, NULL, &fds, NULL, &tv); if(nRet < 0) return -1; else if(nRet == 0) { errno = ETIMEDOUT; break; } nRet = write(psd->fd, pszData, nCbToWrite - nWritten); if(nRet < 0) return -1; else if(nRet == 0) { // should never happen! errno = ENODATA; return -1; } else { if( psd->cfg.bHandleTxEcho && !_ReadEcho(hSer, pszData, nRet)) { GfaSerialPurgeRXBuffer(hSer); errno = ECOMM; return -1; } pszData += nRet; nWritten += nRet; } } return nWritten; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialWrite(HGFADEVICE hSer, const void *pData, size_t nCbData) { if(hSer && pData && nCbData) { ssize_t nRet; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; if((nRet = write(psd->fd, pData, nCbData)) > 0) { if( psd->cfg.bHandleTxEcho && !_ReadEcho(hSer, pData, nRet)) { GfaSerialPurgeRXBuffer(hSer); errno = ECOMM; return -1; } } return nRet; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialPush(HGFADEVICE hSer, uint8_t b) { if(hSer) { fd_set fds; struct timeval tv; ssize_t nRet = 0; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; _CopyTimeval(&tv, &psd->tvTX); FD_ZERO(&fds); FD_SET(psd->fd, &fds); nRet = select(psd->fd + 1, NULL, &fds, NULL, &tv); if(nRet < 0) return -1; else if(nRet == 0) { errno = ETIMEDOUT; return 0; } if((nRet = write(psd->fd, &b, 1)) == 1) { if( psd->cfg.bHandleTxEcho && !_ReadEcho(hSer, &b, 1)) { GfaSerialPurgeRXBuffer(hSer); errno = ECOMM; return -1; } } return nRet; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialReceive(HGFADEVICE hSer, void *pBuf, size_t nCbToRead) { if(hSer && pBuf && nCbToRead) { fd_set fds; struct timeval tv; ssize_t nRet = 0; ssize_t nRead = 0; char *pszBuf = (char*)pBuf; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; _CopyTimeval(&tv, &psd->tvRX); while(nRead < (ssize_t)nCbToRead) { FD_ZERO(&fds); FD_SET(psd->fd, &fds); nRet = select(psd->fd + 1, &fds, NULL, NULL, &tv); if(nRet < 0) return -1; else if(nRet == 0) { errno = ETIMEDOUT; break; } nRet = read(psd->fd, pszBuf, nCbToRead - nRead); if(nRet < 0) return -1; else if(nRet == 0) { // should never happen! errno = ENODATA; return -1; } else { pszBuf += nRet; nRead += nRet; } } return nRead; } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialRead(HGFADEVICE hSer, void *pBuf, size_t nCbToRead) { if(hSer && pBuf && nCbToRead) { LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; return read(psd->fd, pBuf, nCbToRead); } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialPop(HGFADEVICE hSer, uint8_t *pb) { if(hSer && pb) { fd_set fds; struct timeval tv; ssize_t nRet = 0; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; FD_ZERO(&fds); FD_SET(psd->fd, &fds); _CopyTimeval(&tv, &psd->tvRX); nRet = select(psd->fd + 1, &fds, NULL, NULL, &tv); if(nRet < 0) return -1; else if(nRet == 0) { errno = ETIMEDOUT; return 0; } return read(psd->fd, pb, 1); } errno = EINVAL; return -1; } ///////////////////////////////////////////////////////////////////////////// ssize_t GfaSerialPeek(HGFADEVICE hSer) { if(hSer) { fd_set fds; struct timeval tvNull = {0, 0}; LPGFA_SERIAL_DEVICE psd = (LPGFA_SERIAL_DEVICE)hSer; FD_ZERO(&fds); FD_SET(psd->fd, &fds); return select(psd->fd + 1, &fds, NULL, NULL, &tvNull); } errno = EINVAL; return -1; }