如何用中断串口读取串口?

How to read serial with interrupt serial?

我正在尝试阅读 Linux 中的 NMEA 消息。但是我无法得到完整的消息:

54.441,V,,,,,0.00,0.00,010720,,,N*42
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,020954.441,,,,,0,0,,,M,,M,,*43
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GLGSV,1,1,00*65
$GPGLL,,,,,020954.441,V,N*71
$GP

第一行和最后一行是一条消息,但已拆分。我的事情,这是由睡眠 1 秒引起的。这根本不对。我想我应该使用中断串行。

我的想法是当数据进来时,中断串行将 运行 一个读取串行并处理它的函数。之后,系统将休眠直到下一条消息。我搜索了一些 material 但没有帮助。

这是我的新代码,它不起作用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>

void signal_handler_IO ();  

int fd;
int connected;
struct termios termAttr;
struct sigaction saio;

int main(int argc, char *argv[])
{
     fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
     if (fd == -1)
     {
        perror("open_port: Unable to open port\n");
        exit(1);
     }
     saio.sa_handler = signal_handler_IO;
     saio.sa_flags = 0;
     saio.sa_restorer = NULL; 
     sigaction(SIGIO,&saio,NULL);

     fcntl(fd, F_SETFL, FNDELAY);
     fcntl(fd, F_SETOWN, getpid());
     fcntl(fd, F_SETFL,  O_ASYNC );

     tcgetattr(fd,&termAttr);       
     cfsetispeed(&termAttr,B9600);  
     cfsetospeed(&termAttr,B9600);  
     termAttr.c_cflag &= ~PARENB;   
     termAttr.c_cflag &= ~CSTOPB;   
     termAttr.c_cflag &= ~CSIZE;
     termAttr.c_cflag |= CS8;         
     termAttr.c_cflag |= (CLOCAL | CREAD); 
     termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 
     termAttr.c_iflag &= ~(IXON | IXOFF | IXANY); 
     termAttr.c_oflag &= ~OPOST; 
     tcsetattr(fd,TCSANOW,&termAttr);
     printf("UART1 configured....\n");

     while(1){
         sleep(1);
     }
     close(fd);
     exit(0);   
          
}

void signal_handler_IO ()
{
    FILE *csv;
    char buff [1024];
    int n = read(fd, &buff, sizeof(buff));
    char * token = strtok(buff, ",");
    csv=fopen("csvfile.csv","w");
    while( token != NULL ) {
      fprintf(csv,"%s\n",token);
      token = strtok(NULL, ","); 
    }
    fclose(csv);
}

我现在该怎么办?

NMEA 消息为 ,以 '\n'.

结尾

read()更改为fgets()(使用fopen()打开)并作为读入字符串 用于稍后 strtok() 处理。

另请参阅 个想法。

这是我的最高评论。

thank you for your positive response. Did you mean I should use read() function like my old code ? And actually, I have never working with select before. But I very interesting with your idea. And I hope you can show me more the way which apply on my case.

好的,这是一个使用信号处理程序的简单[且未经测试]的版本。而且,我使用 poll 而不是 select [它们很相似] 因为它更容易使用。

请注意,您使用 O_NDELAY 打开了 TTY 设备文件,因此 read 调用是非阻塞的。

另请注意,打开的设备将不会产生 EOF 条件,无论是您的方式还是我的方式。

因此,您必须使用代码来查找表示最后一行的行(例如 $GP)。

或者,在循环中第一次初始等待之后,后续超时应该表明不再有输入

无论如何,这是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>
#if 1
#include <poll.h>
#endif

void signal_handler_IO();               /* definition of signal handler */

int fd;
struct termios termAttr;
struct sigaction saio;
struct pollfd fdpoll;

int
main(int argc, char *argv[])
{
    int timout;
    FILE *fout = NULL;
    int buf_has_GP = 0;
    int lastchr = -1;
    int curchr;
    int err;
    int rlen;
    int idx;
    char buf[1000];

    fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("open_port: Unable to open port\n");
        exit(1);
    }

#if 0
    saio.sa_handler = signal_handler_IO;
    saio.sa_flags = 0;
    saio.sa_restorer = NULL;
    sigaction(SIGIO, &saio, NULL);
#endif

    fcntl(fd, F_SETFL, FNDELAY);
    fcntl(fd, F_SETOWN, getpid());
    fcntl(fd, F_SETFL, O_ASYNC);

    tcgetattr(fd, &termAttr);
    cfsetispeed(&termAttr, B9600);
    cfsetospeed(&termAttr, B9600);
    termAttr.c_cflag &= ~PARENB;
    termAttr.c_cflag &= ~CSTOPB;
    termAttr.c_cflag &= ~CSIZE;
    termAttr.c_cflag |= CS8;
    termAttr.c_cflag |= (CLOCAL | CREAD);
    termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
    termAttr.c_oflag &= ~OPOST;
    tcsetattr(fd, TCSANOW, &termAttr);
    printf("UART1 configured....\n");

    fout = fopen("csvfile.csv","w");

    fdpoll.fd = fd;
    fdpoll.events = POLLIN;
    timout = 10000;

    while (1) {
        err = poll(&fdpoll,1,timout);

        // timeout
        if (err == 0)
            break;

        // error
        if (err < 0) {
            fprintf(stderr,"error -- %s\n",strerror(errno));
            break;
        }

        // err will always be _one_ because poll's second arg is 1

        while (1) {
            rlen = read(fd,buf,sizeof(buf));
            if (rlen <= 0)
                break;

            fwrite(buf,1,rlen,fout);

            // need to check buf looking for last line (e.g. $GP)
            // to know when to stop
            // since read is _not_ line oriented we have to check for G followed
            // by P [and they may or may not occur in the same read call]
            // FIXME -- this is quite crude -- just to illustrate
            for (idx = 0;  idx < rlen;  ++idx) {
                curchr = buf[idx];
                buf_has_GP = ((lastchr == 'G') && (curchr == 'P'));
                if (buf_has_GP)
                    break;
                lastchr = curchr;
            }

            if (buf_has_GP)
                break;
        }

        if (buf_has_GP)
            break;

        timout = 1000;

#if 0
        sleep(1);
#endif
    }

    close(fd);
    fclose(fout);

    exit(0);
}

void
signal_handler_IO()
{
    FILE *csv;
    FILE *ff;
    char buff[1024];

    ff = fopen("/dev/ttyUSB0", "r");
    // int n = read(fd, &buff, sizeof(buff));
    fgets(buff, sizeof(buff), ff);
    char *token = strtok(buff, ",");

    csv = fopen("csvfile.csv", "w");
    while (token != NULL) {
        fprintf(csv, "%s\n", token);
        token = strtok(NULL, ",");
    }
    sleep(0.2);
    fclose(csv);
}

更新:

Thank you so much for spend your time for me. I compiled it without error. Unfortunately, I get nothing from output and empty file.

可能是因为simple/crude EOF字符串检测代码。我认为它可能过早地停止了。我添加了更强大的检查。

我还添加了调试打印(如果给出 -d)。

因为我无法访问真正的 ttyUSB 设备,所以我使用 PTY [伪 TTY] 添加了测试代码。要使用它,请将示例 NMEA 数据放入文件中(例如 input.txt)并将 -pinput.txt 添加到参数中。

这就是我调试一般程序流程的方式。

我已经关闭了所有不必要的 fcntl 选项。

如果尝试此操作后仍有问题,您可能希望使用终端程序(例如 minicom)测试您的设备接口,以验证远程设备确实在发送数据。

如果 minicom 产生输出,但您的程序没有,您可能需要修改某些 termios 选项。

一些usbtty/uart设备需要RTS/CTS[我实际上已经使用这样的设备工作]。 minicom 有一个配置选项来处理这个问题。

在程序中[虽然我怀疑它默认是关闭],你可能需要禁用RTS/CTS硬件,这样端口才不会被挂断。 And/or 确保 XON/XOFF 流量控制被禁用。

或者,远程设备需要 RTS/CTS支持[你必须以某种方式强制远程设备看到 CTS 高]。虽然不太可能,但 可能 必须在电缆本身中完成。

无论如何,这是更新后的代码:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>
#if 1
#include <poll.h>
#include <pty.h>
#include <sys/wait.h>
#include <time.h>
#endif

#ifndef RAWOUT
#define RAWOUT      1
#endif

void signal_handler_IO();               /* definition of signal handler */

const char *ttydev = "/dev/ttyUSB0";
int fd;

int opt_d;                              // 1=debug print
char *opt_pty;                          // PTY input file
int ptypid;
#define PTYSLP      1

FILE *fout = NULL;

struct termios termAttr;
struct sigaction saio;
struct pollfd fdpoll;

int linelen;
char linebuf[1000];

#define SHOWPOLL(_msk) \
    if (events & _msk) \
        bp += sprintf(bp,"/" #_msk)

typedef long long tsc_t;

tsc_t
tscget(void)
{
    struct timespec ts;
    tsc_t tsc;
    static tsc_t tsczero = 0;

    clock_gettime(CLOCK_REALTIME,&ts);

    tsc = ts.tv_sec;
    tsc *= 1000000000;
    tsc += ts.tv_nsec;

    if (tsczero == 0)
        tsczero = tsc;

    tsc -= tsczero;

    return tsc;
}

double
tscsec(tsc_t tsc)
{
    double sec;

    sec = tsc;
    sec /= 1e9;

    return sec;
}

void
tscprt(void)
{
    tsc_t tsc;

    tsc = tscget();

    printf("%.9f ",tscsec(tsc));
}

#define dbgprt(_fmt...) \
    do { \
        if (! opt_d) \
            break; \
        int sverr = errno; \
        tscprt(); \
        printf(_fmt); \
        errno = sverr; \
    } while (0)

// dopty -- generate pseudo TTY test device
void
dopty(void)
{
    int fdm;
    int fdtxt;
    int rlen;
    int wlen;
    int off;
    char buf[1000];

#if 0
    fdm = open("/dev/pts/ptmx",O_RDWR | O_NDELAY);
#else
    fdm = getpt();
#endif
    if (fdm < 0) {
        perror("dopty/open");
        exit(1);
    }
    dbgprt("dopty: GETPT fdm=%d\n",fdm);

    ttydev = ptsname(fdm);
    dbgprt("dopty: PTSNAME ttydev=%s\n",ttydev);

    grantpt(fdm);
    unlockpt(fdm);

    dbgprt("dopty: INPUT opt_pty=%s\n",opt_pty);

    do {
        ptypid = fork();

        if (ptypid != 0) {
            close(fdm);
            break;
        }

        // open sample test data file
        fdtxt = open(opt_pty,O_RDONLY);
        if (fdtxt < 0) {
            perror("dopty/open");
            exit(1);
        }

        sleep(PTYSLP);

        while (1) {
            rlen = read(fdtxt,buf,sizeof(buf));
            if (rlen <= 0)
                break;
            dbgprt("dopty: READ rlen=%d\n",rlen);

            for (off = 0;  off < rlen;  off += wlen) {
                wlen = rlen - off;
                wlen = write(fdm,&buf[off],wlen);
                dbgprt("dopty: WRITE wlen=%d\n",wlen);
            }
        }

        sleep(PTYSLP);

        dbgprt("dopty: CLOSE\n");
        close(fdtxt);
        close(fdm);

        sleep(PTYSLP);

        dbgprt("dopty: EXIT\n");
        exit(0);
    } while (0);
}

char *
showpoll(short events)
{
    char *bp;
    static char buf[1000];

    bp = buf;
    bp += sprintf(bp,"%4.4X",events);

    SHOWPOLL(POLLIN);
    SHOWPOLL(POLLPRI);
    SHOWPOLL(POLLOUT);
    SHOWPOLL(POLLRDHUP);
    SHOWPOLL(POLLERR);
    SHOWPOLL(POLLHUP);

    return buf;
}

// lineadd -- add character to line buffer
void
lineadd(int chr)
{
    char *bp;
    char buf[10];

    if (opt_d) {
        bp = buf;
        *bp = 0;
        if ((chr >= 0x20) && (chr <= 0x7E))
            bp += sprintf(bp," '%c'",chr);
        dbgprt("showchr: CHR chr=%2.2X%s\n",chr,buf);
    }

    linebuf[linelen++] = chr;
    linebuf[linelen] = 0;
}

// eoftst -- decide if current line is the last line
int
eoftst(void)
{
    static char *eofstr = "$GP\n";
    static int eoflen = 0;
    int stopflg = 0;

    if (eoflen == 0)
        eoflen = strlen(eofstr);

    stopflg = ((linelen == eoflen) && (memcmp(linebuf,eofstr,eoflen) == 0));

    dbgprt("eoftst: %s\n",stopflg ? "STOP" : "CONT");

    return stopflg;
}

int
main(int argc, char **argv)
{
    int timout;
    int buf_has_eof = 0;
    int curchr;
    int err;
    int rlen;
    int idx;
    char buf[1000];

    --argc;
    ++argv;

    setlinebuf(stdout);
    setlinebuf(stderr);

    for (;  argc > 0;  --argc, ++argv) {
        char *cp = *argv;

        if (*cp != '-')
            break;

        cp += 2;
        switch (cp[-1]) {
        case 'd':
            opt_d = ! opt_d;
            break;

        case 'p':
            opt_pty = (*cp != 0) ? cp : "input.txt";
            break;
        }
    }

    do {
        // create test device
        if (opt_pty != NULL) {
            dopty();
            break;
        }

        if (argc > 0) {
            ttydev = *argv;
            --argc;
            ++argv;
        }
    } while (0);

    dbgprt("main: TTYDEV ttydev=%s\n",ttydev);
    fd = open(ttydev, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("open_port: Unable to open port\n");
        exit(1);
    }

#if 0
    saio.sa_handler = signal_handler_IO;
    saio.sa_flags = 0;
    saio.sa_restorer = NULL;
    sigaction(SIGIO, &saio, NULL);
#endif

    // not needed unless doing signal handler
#if 0
    fcntl(fd, F_SETFL, FNDELAY);
    fcntl(fd, F_SETOWN, getpid());
    fcntl(fd, F_SETFL, O_ASYNC);
#endif

#if 1
    tcgetattr(fd, &termAttr);
#endif

#if 1
    cfsetispeed(&termAttr, B9600);
    cfsetospeed(&termAttr, B9600);
#endif

    // force immediate return from device read if no chars available
#if 1
    dbgprt("main: CC VMIN=%d VTIME=%d\n",
        termAttr.c_cc[VMIN],termAttr.c_cc[VTIME]);
    termAttr.c_cc[VMIN] = 0;
    termAttr.c_cc[VTIME] = 0;
#endif

    termAttr.c_cflag &= ~PARENB;
    termAttr.c_cflag &= ~CSTOPB;
    termAttr.c_cflag &= ~CSIZE;
    termAttr.c_cflag |= CS8;
    termAttr.c_cflag |= (CLOCAL | CREAD);

    // FIXME -- you may need to handle this
#if 1
    termAttr.c_cflag &= ~CRTSCTS;
#else
    termAttr.c_cflag |= CRTSCTS;
#endif

    termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
    termAttr.c_oflag &= ~OPOST;

#if 1
    tcsetattr(fd, TCSANOW, &termAttr);
#endif
    printf("UART1 configured....\n");

    // open output file
    fout = fopen("csvfile.csv","w");
    if (fout == NULL) {
        perror("main/fopen");
        exit(1);
    }

    fdpoll.fd = fd;
    fdpoll.events = POLLIN;
    fdpoll.revents = 0;

    // set initial timeout of 10 seconds
    timout = 10000;

    // NOTE: iter is just for testing to prevent infinite looping if failure to
    // read or match the EOF string
    for (int iter = 1;  iter < 10;  ++iter) {
        dbgprt("main: POLL iter=%d events=%s timout=%d\n",
            iter,showpoll(fdpoll.events),timout);
        err = poll(&fdpoll,1,timout);

        dbgprt("main: POLL revents=%s err=%d\n",showpoll(fdpoll.revents),err);

        // timeout
        if (err == 0)
            break;

        // error
        if (err < 0) {
            fprintf(stderr,"error -- %s\n",strerror(errno));
            break;
        }

        // err will always be _one_ because poll's second arg is 1

        // process all data in current chunk
        while (1) {
            rlen = read(fd,buf,sizeof(buf));
            dbgprt("main: READ iter=%d rlen=%d\n",iter,rlen);
            if (rlen <= 0)
                break;

            // send data to output file
#if RAWOUT
            fwrite(buf,1,rlen,fout);
#endif

            // need to check buf looking for last line (e.g. $GP)
            // to know when to stop
            // since read is _not_ line oriented we have to check for G followed
            // by P [and they may or may not occur in the same read call]
            // FIXME -- this is quite crude -- just to illustrate
            for (idx = 0;  idx < rlen;  ++idx) {
                curchr = buf[idx];

                // add to line buffer
                lineadd(curchr);

                // wait for newline
                if (curchr != '\n')
                    continue;

                // decide if this is the last line of the current NMEA message
                buf_has_eof = eoftst();

#if (! RAWOUT)
                // do processing on line buffer ...
#endif

                // reset line buffer index/length for next line
                linelen = 0;

                if (buf_has_eof)
                    break;
            }

            if (buf_has_eof)
                break;
        }

        if (buf_has_eof)
            break;

        // set 1 second timeout for subsequent reads
        timout = 1000;

#if 0
        sleep(1);
#endif
    }

    close(fd);
    fclose(fout);

    // reap any child processes [only if doing PTY mode]
    while (opt_pty != NULL) {
        pid_t pid = wait(NULL);
        dbgprt("main: WAIT pid=%d\n",pid);
        if (pid <= 0)
            break;
    }

    exit(0);
}

void
signal_handler_IO()
{
    FILE *csv;
    FILE *ff;
    char buff[1024];

    ff = fopen("/dev/ttyUSB0", "r");
    // int n = read(fd, &buff, sizeof(buff));
    fgets(buff, sizeof(buff), ff);
    char *token = strtok(buff, ",");

    csv = fopen("csvfile.csv", "w");
    while (token != NULL) {
        fprintf(csv, "%s\n", token);
        token = strtok(NULL, ",");
    }
    sleep(0.2);
    fclose(csv);
}