/********************************************************************************* * Copyright: (C) 2023 LingYun IoT System Studio. * All rights reserved. * * Filename: comport.c * Description: This file is linux comport common API functions * * Version: 1.0.0(11/08/23) * Author: Guo Wenxue * ChangeLog: 1, Release initial version on "11/08/23 16:18:43" * ********************************************************************************/ #include "comport.h" #define CONFIG_PRINT_LOGGER //#define CONFIG_PRINT_STDOUT #if ( defined CONFIG_PRINT_LOGGER ) #include "logger.h" #define dbg_print(format,args...) log_error(format, ##args) #elif ( defined CONFIG_PRINT_STDOUT ) #define dbg_print(format,args...) printf(format, ##args) #else #define dbg_print(format,args...) do{} while(0); #endif static inline void set_settings(comport_t * comport, const char *settings); /* * description: Open the serial port * * input args: $comport: corresponding comport point * $dev_name: The comport device name path, such as '/dev/ttyS3' * $baudrate: The baudrate, such as 115200 * $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N' * * return value: The comport opened file description, <0 means failure */ int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings) { int rv = -1; struct termios old_cfg, new_cfg; int old_flags; long tmp; if( !comport || !devname ) { dbg_print("invalid input arugments\n"); return -1; } /*+-----------------------+ *| open the serial port | *+-----------------------+*/ memset(comport, 0, sizeof(*comport)); strncpy(comport->devname, devname, sizeof(comport->devname)); comport->baudrate = baudrate; comport->fd = -1; comport->fragsize = CONFIG_DEF_FRAGSIZE; set_settings(comport, settings); if( !strstr(comport->devname, "tty") ) { dbg_print("comport device \"%s\" is not tty device\n", comport->devname); return -2; } comport->fd = open(comport->devname, O_RDWR | O_NOCTTY | O_NONBLOCK); if( comport->fd<0 ) { dbg_print("comport open \"%s\" failed:%s\n", comport->devname, strerror(errno)); return -3; } if( (-1 != (old_flags = fcntl(comport->fd, F_GETFL, 0))) && (-1 != fcntl(comport->fd, F_SETFL, old_flags & ~O_NONBLOCK)) ) { /* Flush input and output */ tcflush(comport->fd, TCIOFLUSH); } else { rv = -4; goto CleanUp; } if (0 != tcgetattr(comport->fd, &old_cfg)) { rv = -5; goto CleanUp; } /*+-----------------------+ *| configure serial port | *+-----------------------+*/ memset(&new_cfg, 0, sizeof(new_cfg)); new_cfg.c_cflag &= ~CSIZE; new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); new_cfg.c_oflag &= ~(OPOST); /* Set the data bit */ switch (comport->databit) { case 0x07: new_cfg.c_cflag |= CS7; break; case 0x06: new_cfg.c_cflag |= CS6; break; case 0x05: new_cfg.c_cflag |= CS5; break; default: new_cfg.c_cflag |= CS8; break; } /* Set the parity */ switch (comport->parity) { case 0x01: /* Odd */ new_cfg.c_cflag |= (PARENB | PARODD); new_cfg.c_cflag |= (INPCK | ISTRIP); break; case 0x02: /* Even */ new_cfg.c_cflag |= PARENB; new_cfg.c_cflag &= ~PARODD;; new_cfg.c_cflag |= (INPCK | ISTRIP); break; case 0x03: new_cfg.c_cflag &= ~PARENB; new_cfg.c_cflag &= ~CSTOPB; break; default: new_cfg.c_cflag &= ~PARENB; } /* Set Stop bit */ if (0x01 != comport->stopbit) { new_cfg.c_cflag |= CSTOPB; } else { new_cfg.c_cflag &= ~CSTOPB; } /* Set flow control */ switch (comport->flowctrl) { case 1: /* Software control */ case 3: new_cfg.c_cflag &= ~(CRTSCTS); new_cfg.c_iflag |= (IXON | IXOFF); break; case 2: /* Hardware control */ new_cfg.c_cflag |= CRTSCTS; new_cfg.c_iflag &= ~(IXON | IXOFF); break; default: /* NONE */ new_cfg.c_cflag &= ~(CRTSCTS); new_cfg.c_iflag &= ~(IXON | IXOFF); break; } /* Set baudrate */ switch (comport->baudrate) { /* Upper is not POSIX(bits/termios-baud.h) */ case 4000000: tmp = B4000000; break; case 3500000: tmp = B3500000; break; case 3000000: tmp = B3000000; break; case 2500000: tmp = B2500000; break; case 2000000: tmp = B2000000; break; case 1500000: tmp = B1500000; break; case 1152000: tmp = B1152000; break; case 1000000: tmp = B1000000; break; case 921600: tmp = B921600; break; case 576000: tmp = B576000; break; case 500000: tmp = B500000; break; case 460800: tmp = B460800; break; case 230400: tmp = B230400; break; case 115200: tmp = B115200; break; case 57600: tmp = B57600; break; /* Below is POSIX(bits/termios.h) */ case 38400: tmp = B38400; break; case 19200: tmp = B19200; break; case 9600: tmp = B9600; break; case 4800: tmp = B4800; break; case 2400: tmp = B2400; break; case 1800: tmp = B1800; break; case 1200: tmp = B1200; break; case 600: tmp = B600; break; case 300: tmp = B300; break; case 200: tmp = B200; break; case 150: tmp = B150; break; case 134: tmp = B134; break; case 110: tmp = B110; break; case 75: tmp = B75; break; case 50: tmp = B50; break; default: tmp = B115200; } cfsetispeed(&new_cfg, tmp); cfsetispeed(&new_cfg, tmp); /* Set the Com port timeout settings */ new_cfg.c_cc[VMIN] = 0; new_cfg.c_cc[VTIME] = 0; tcflush(comport->fd, TCIFLUSH); if (0 != tcsetattr(comport->fd, TCSANOW, &new_cfg)) { rv = -6; // Failed to set device com port settings goto CleanUp; } rv = comport->fd; CleanUp: return rv; } /* * description: close comport * input args: $comport: corresponding comport point */ void comport_close(comport_t *comport) { if( !comport ) { dbg_print("invalid input arugments\n"); return ; } if ( comport->fd >= 0 ) { close(comport->fd); } comport->fd = -1; return ; } /* * description: write $data_bytes $data to $comport * return value: 0: write ok <0: write failure */ int comport_send(comport_t *comport, char *data, int data_bytes) { char *ptr; int left, bytes = 0; int rv = 0; if( !comport || !data || data_bytes<=0 ) { dbg_print("invalid input arugments\n"); return -1; } if( comport->fd < 0 ) { dbg_print("Serail port not opened\n"); return -2; } ptr = data; left = data_bytes; while( left > 0 ) { /* Large data, then slice them to frag and send */ bytes = left>comport->fragsize ? comport->fragsize : left; rv = write(comport->fd, ptr, bytes); if( rv<0 ) { rv = -3; break; } left -= rv; ptr += rv; } return rv; } /* * description: read data from $comport in $timeout to $buf no more than $buf_size bytes * return value: the actual read data bytes, <0: read failure */ int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout) { fd_set rdfds, exfds; struct timeval to, *to_ptr = NULL; int ret, rv = 0; int bytes = 0; if ( !comport || !buf || buf_size<=0 ) { dbg_print("invalid input arugments\n"); return -1; } if ( comport->fd < 0 ) { dbg_print("Serail port not opened\n"); return -2; } memset(buf, 0, buf_size); FD_ZERO(&rdfds); FD_ZERO(&exfds); FD_SET(comport->fd, &rdfds); FD_SET(comport->fd, &exfds); if( TIMEOUT_NONE != timeout ) { to.tv_sec = (time_t) (timeout / 1000); to.tv_usec = (long)(1000 * (timeout % 1000)); to_ptr = &to; } while( 1 ) { /* check got data arrive or not */ ret = select(comport->fd+1, &rdfds, 0, &exfds, to_ptr); if( ret<0 ) { /* EINTR means catch interrupt signal */ dbg_print("comport select() failed: %s\n", strerror(errno)); rv = EINTR==errno ? 0 : -3; break; } else if( 0 == ret ) /* timeout */ { break; } /* read data from comport */ ret = read(comport->fd, buf+bytes, buf_size-bytes); if(ret <= 0) { dbg_print("comport read() failed: %s\n", strerror(errno)); break; } bytes += ret; if( bytes >= buf_size ) break; /* try to read data in 1ms again, if no data arrive it will break */ to.tv_sec = 0; to.tv_usec = 10000; to_ptr = &to; } if( !rv ) rv = bytes; return rv; } /************************************************************************************** * Description: Set the comport databit,parity,stopbit,flowctrl into the comport structure * Input Args: comport: the comport_t pointer * settings: The databit/parity/stopbit/flowctrl settings as like "8N1N" * Output Args: NONE * Return Value: NONE *************************************************************************************/ static inline void set_settings(comport_t * comport, const char *settings) { if( !settings || !comport ) { dbg_print("invalid input arugments\n"); return ; } switch (settings[0]) /* data bit */ { case '7': comport->databit = 7; break; case '8': default: comport->databit = 8; break; } switch (settings[1]) /* parity */ { case 'O': case 'o': comport->parity = 1; break; case 'E': case 'e': comport->parity = 2; break; case 'S': case 's': comport->parity = 3; break; case 'N': case 'n': default: comport->parity = 0; break; } switch (settings[2]) /* stop bit */ { case '0': comport->stopbit = 0; break; case '1': default: comport->stopbit = 1; break; } switch (settings[3]) /* flow control */ { case 'S': case 's': comport->flowctrl = 1; break; case 'H': case 'h': comport->flowctrl = 2; break; case 'B': case 'b': comport->flowctrl = 3; break; case 'N': case 'n': default: comport->flowctrl = 0; break; } }