www.pudn.com > Linuxserial.zip > tty.c


/*
 * File         : tty.c
 * Author       : yfy001
 * Date         : 2002-04-20
 * Description  : To provide underlying serial port function,
 *                for high level applications.
 * Copyright (C) 2001, 2002  yfy001
 */
#include               // perror, printf, puts, fprintf, fputs
#include              // read, write, close
#include             // tcgetattr, tcsetattr
#include               // open
#include          // sigaction
#include           // pid_t
#include              // bzero, memcpy
#include              // CHAR_MAX
#include           // ioctl

#include "types.h"              // int32, int16, int8, uint32, uint16, uint8
#include "tty.h"                // tty_open, tty_close, tty_read, tty_write
#include "prompt.h"             // prompt

static INT8 firstrecv = 1;      // whether data received
static int fd = -1;             // File descriptor for the port 
static struct termios termios_old, termios_new;
static struct timeval tv_timeout;

static void tty_sig_handler();
static void tty_set_baudrate(int);
static int tty_get_baudrate();
static void tty_set_databit(INT8 databit);
static int tty_set_attr(const portinfo_t * p_portinfo);
static void tty_set_stopbit(INT8 stopbit);
static void tty_set_parity_check(INT8 parity);
static void tty_set_flow_control(INT8 fctrl);
static INT8 tty_is_open();

/*
 * Arguments    : const portinfo_t* p_portinfo;
 * Return value : 0 on success, ERROR on failure.
 * Description  : Open serial port ComPort at baudrate baud rate.
 */
int tty_open(const portinfo_t * p_portinfo)
{
    char *p_tty;
    int retval;

    if (tty_is_open()) {
        return (0);
    }
    if (0 == p_portinfo->tty) {
        p_tty = "/dev/ttyS0";
    } else if (1 == p_portinfo->tty) {
        p_tty = "/dev/ttyS1";
    } else if (2 == p_portinfo->tty) {
        p_tty = "/dev/ttyS2";
    } else if (3 == p_portinfo->tty) {
        p_tty = "/dev/ttyS3";
    } else if (4 == p_portinfo->tty) {
        p_tty = "/dev/ttyS4";
    } else if (5 == p_portinfo->tty) {
        p_tty = "/dev/ttyS5";
    } else if (6 == p_portinfo->tty) {
        p_tty = "/dev/ttyS6";
    } else if (7 == p_portinfo->tty) {
        p_tty = "/dev/ttyS7";
    } else {
        p_tty = "/dev/ttyS0";
    }

    fd = open(p_tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (-1 == fd) {
        fprintf(stderr, "cannot open port %s\n", p_tty);
        return (E_OPEN);
    }
    tcgetattr(fd, &termios_old);        // save old termios value 
    // 0 on success, -1 on failure 
    retval = tty_set_attr(p_portinfo);
    if (-1 == retval) {
        fprintf(stderr, "\nport %s cannot set baudrate at %d\n", p_tty,
                p_portinfo->baudrate);
        return (E_SET_BAUDRATE);
    }
    return (0);
}

/* 
 * Arguments    : none
 * Return value : none
 * Description  : close serial port by use of file descriptor fd
 */
void tty_close()
{
    if (!tty_is_open()) {
        return;
    }
    tcsetattr(fd, TCSAFLUSH, &termios_old);
    close(fd);
    fd = -1;
}

/* 
 * Arguments    : void *data, int datalength;	
 * Description  : Read data ready from serial port	
 * Return value :
 *                E_NOTOPEN : port isn't open.
 *                E_NODATA  : no data has arrived before time is out.
 */
int tty_read(void *data, int datalength)
{
    int retval = 0;
    fd_set fs_read;

    if (!tty_is_open()) {
        return (E_NOTOPEN);
    }

    FD_ZERO(&fs_read);
    FD_SET(fd, &fs_read);
    tv_timeout.tv_sec = TIMEOUT_SEC(datalength, tty_get_baudrate());
    tv_timeout.tv_usec = TIMEOUT_USEC;
    retval = select(fd + 1, &fs_read, NULL, NULL, &tv_timeout);
    if (retval) {
        return (read(fd, data, datalength));
    } else {
        return (E_NODATA);
    }
}

/* 
 * Arguments    : uint8 *data, int datalength;
 * Return value : bytes written
 * Description  : Write datalength bytes in buffer given by uint8 *data,
 */
int tty_write(uint8 * data, int datalength)
{
    int retval, len = 0, total_len = 0;
    fd_set fs_write;

    if (!tty_is_open()) {
        return (E_NOTOPEN);
    }

    FD_ZERO(&fs_write);
    FD_SET(fd, &fs_write);
    tv_timeout.tv_sec = TIMEOUT_SEC(datalength, tty_get_baudrate());
    tv_timeout.tv_usec = TIMEOUT_USEC;

    for (total_len = 0, len = 0; total_len < datalength;) {
        retval = select(fd + 1, NULL, &fs_write, NULL, &tv_timeout);
        if (retval) {
            len = write(fd, &data[total_len], datalength - total_len);
            if (len > 0) {
                total_len += len;
            }
        } else {
            tcflush(fd, TCOFLUSH);      // flush all output data 
            break;
        }
    }
    return (total_len);
}

/*
 * Arguments    : INT8 databit;	
 * Description  : Set databit; 
 */
static void tty_set_databit(INT8 databit)
{
    termios_new.c_cflag &= ~CSIZE;
    if (7 == databit) {
        termios_new.c_cflag |= CS7;
    } else if (6 == databit) {
        termios_new.c_cflag |= CS6;
    } else if (5 == databit) {
        termios_new.c_cflag |= CS5;
    } else {
        termios_new.c_cflag |= CS8;
    }
}

/*
 * Arguments    : int stopbit;
 * Description  : Set Stop Bit 
 */
static void tty_set_stopbit(INT8 stopbit)
{
    if (2 == stopbit) {
        termios_new.c_cflag |= CSTOPB;  // 2 stop bits 
    } else {
        termios_new.c_cflag &= ~CSTOPB; // 1 stop bit 
    }
}

/*
 * Arguments    : INT8 parity;	
 * Description  : Set Parity Check 
 */
static void tty_set_parity_check(INT8 parity)
{
    parity = parity % 3;
    if (2 == parity) {          // even 
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag &= ~PARODD;
    } else if (1 == parity) {   // odd 
        termios_new.c_cflag |= PARENB;
        termios_new.c_cflag |= ~PARODD;
    } else {                    // no parity check 
        termios_new.c_cflag &= ~PARENB;
    }
}

/*
 * Arguments    : int fctrl;
 *     0: No Flow Control;
 *     1: Hardware Control;
 *     2: Software Control;
 * Return value : none
 * Description  : set flow control of fd wth fctrl
 */
static void tty_set_flow_control(INT8 fctrl)
{
    if (0 == fctrl) {           // no flow control 
        termios_new.c_cflag &= ~CRTSCTS;
    } else if (1 == fctrl) {    // hardware flow control 
        termios_new.c_cflag |= CRTSCTS;
    } else if (2 == fctrl) {    // software flow control 
        termios_new.c_iflag |= IXON | IXOFF | IXANY;
    }
}

/*
 * Arguments    : const portinfo_t* p_portinfo;
 * Description  : Set Attribute of serial port
 */
static int tty_set_attr(const portinfo_t * p_portinfo)
{
    bzero(&termios_new, sizeof(termios_new));
    cfmakeraw(&termios_new);
    tty_set_baudrate(p_portinfo->baudrate);
    termios_new.c_cflag |= CLOCAL | CREAD;
    tty_set_flow_control(p_portinfo->fctrl);
    tty_set_databit(p_portinfo->databit);
    tty_set_parity_check(p_portinfo->parity);
    tty_set_stopbit(p_portinfo->stopbit);
    termios_new.c_oflag &= ~OPOST;
    termios_new.c_cc[VTIME] = 1;        // unit: (1/10) second. 
    termios_new.c_cc[VMIN] = 1; // minimal characters for reading 
    tcflush(fd, TCIFLUSH);
    return (tcsetattr(fd, TCSANOW, &termios_new));
}

/* 
 * Arguments    : int baudrate;
 * Description  : set serial port baudrate by use of file descriptor fd
 */
static void tty_set_baudrate(int baudrate)
{
    if (0 == baudrate) {
    } else if (50 == baudrate) {
        termios_new.c_cflag = B50;
    } else if (75 == baudrate) {
        termios_new.c_cflag = B75;
    } else if (110 == baudrate) {
        termios_new.c_cflag = B110;
    } else if (134 == baudrate) {
        termios_new.c_cflag = B134;
    } else if (150 == baudrate) {
        termios_new.c_cflag = B150;
    } else if (200 == baudrate) {
        termios_new.c_cflag = B200;
    } else if (300 == baudrate) {
        termios_new.c_cflag = B300;
    } else if (600 == baudrate) {
        termios_new.c_cflag = B600;
    } else if (1200 == baudrate) {
        termios_new.c_cflag = B1200;
    } else if (2400 == baudrate) {
        termios_new.c_cflag = B2400;
    } else if (9600 == baudrate) {
        termios_new.c_cflag = B9600;
    } else if (19200 == baudrate) {
        termios_new.c_cflag = B19200;
    } else if (38400 == baudrate) {
        termios_new.c_cflag = B38400;
    } else if (57600 == baudrate) {
        termios_new.c_cflag = B57600;
    } else if (115200 == baudrate) {
        termios_new.c_cflag = B115200;
    } else {
        termios_new.c_cflag = B38400;
    }
}

/*
 * Arguments    : void;
 * Description  : get serial port baudrate
 */
static int tty_get_baudrate()
{
    switch (cfgetospeed(&termios_new)) {
    case B0:
        return (0);
    case B50:
        return (50);
    case B75:
        return (75);
    case B110:
        return (110);
    case B134:
        return (134);
    case B150:
        return (150);
    case B200:
        return (200);
    case B300:
        return (300);
    case B600:
        return (600);
    case B1200:
        return (1200);
    case B2400:
        return (2400);
    case B9600:
        return (9600);
    case B19200:
        return (19200);
    case B38400:
        return (38400);
    case B57600:
        return (57600);
    case B115200:
        return (115200);
    default:
        return (9600);
    }
}

/*
 * Arguments    : const char *pathname, int echo;
 * Return value : none
 * Description  : send a file
 */
int tty_send_file(const char *pathname, INT8 echo)
{
    int fd, buflen, len;        //  fd for file 
    char buf[BUFFER_LEN + 1];
    fd = open(pathname, O_RDONLY);
    if (fd < 0) {
        perror(pathname);
        return (E_NOTOPEN);
    }

    while (1) {
        bzero(buf, sizeof(buf));
        buflen = read(fd, buf, BUFFER_LEN);
        if (0 == buflen)
            break;
        buf[buflen] = 0;
        echo ? printf("%s", buf) : 0;
        if ((len = tty_write(buf, buflen)) != buflen) {
            fprintf(stderr, "write %d bytes for %d bytes\n", len, buflen);
            fprintf(stderr, "to tty_close()\n");
            tty_close();
            close(fd);
            fprintf(stderr, "tty_close() completed\n");
            return (-1);
        }
    }

    fflush(stdout);
    fflush(stderr);
    close(fd);
    return (0);
}

/*
 * Arguments    : none
 * Return value : none
 * Description  : Read the data ready.
 */
static void tty_sig_handler()
{
    char recvbuf[CHAR_MAX + 1];
    int retval;
    fd_set fs_read;
    int byte_recv = 0;

    FD_ZERO(&fs_read);
    FD_SET(fd, &fs_read);
    tv_timeout.tv_sec = 0;
    tv_timeout.tv_usec = 500;

    while (1) {
        retval = select(fd + 1, &fs_read, NULL, NULL, &tv_timeout);
        if (retval) {
            byte_recv = read(fd, recvbuf, sizeof(recvbuf) - 1);
            if (firstrecv) {    // first recieve
                printf("\n");
                firstrecv = 0;
            }
        } else {
            if (byte_recv && p_args->p_portinfo->prompt) {
                prompt();
            }
            break;
        }
        if (byte_recv > 0) {
            recvbuf[byte_recv] = '\0';
            fprintf(stderr, "%s", recvbuf);
            if (DEBUG)
                fprintf(stderr, "%d-byte\n", byte_recv);
            fflush(stderr);
        }
        if (byte_recv > 80) {
            tv_timeout.tv_sec = 0;
            tv_timeout.tv_usec = 500;
        } else {
            tv_timeout.tv_sec = 0;
            tv_timeout.tv_usec = 200;
        }
    }
}

/*
 * Arguments    : none	
 * Return value : none
 * Description  : Set Signal for read;
 */
int tty_set_sig_on()
{
    // install the signal handler before making the device asynchronous 
    static struct sigaction sigaction_io;
    sigaction_io.sa_handler = tty_sig_handler;
    sigemptyset(&(sigaction_io.sa_mask));
    sigaction_io.sa_flags = 0;
    sigaction_io.sa_restorer = NULL;
    sigaction(SIGIO, &sigaction_io, NULL);
    // allow the process to receive SIGIO 
    if (-1 == fcntl(fd, F_SETFL, O_ASYNC)) {
        return (-1);
    } else if (-1 == fcntl(fd, F_SETOWN, getpid())) {
        return (-1);
    }
    return (0);
}

/*
 * Arguments    : none 
 * Return value : INT8, 0: closed, 1: open
 * Description  : return whether /dev/ttyS[tty] is open
 */
static INT8 tty_is_open()
{
    return -1 == fd ? 0 : 1;    // fd == -1, return 0       
}