嵌入式Linux 串口编程系列3——通过VTIM、VMIN、select实现串口不定长数据接收功能

2019-07-13 04:58发布

    上一篇文章中,我们详细分析了VTIM和VMIN的功能, 《嵌入式Linux 串口编程系列2--termios的VMIN和VTIME深入理解》    也明白了这两个参数设计的初衷和使用方法,接下来我们 就详细的说明一下,具体编程中,我们要将VMIN 设置的足够大,将VTIME设置的尽量小,同时在应用接收线程中,配合select机制。我们来分析下为什么要这么设计:     VMIN设置的足够大,这样在串口在有数据接收时,不会因为接收了 几个或者几十个字符就 改变了read的阻塞状态,至于说要设定多大,这个范围一般是不超过255的,因为VMIN是一个 char类型,数值上不能超过255,超过255了,会被认为是0.     VTIME设置的尽量小:如果VTIME设置成0,那么串口只有接收VMIN个字节才会改变read阻塞 状态,而上面说到VMIN又比较大,这显然是不行的,而如果VTIME设置的比较大,比如说200,换算成时间就是20秒,也就是说,串口在接收完数据后,需要等待20秒才会 改变read阻塞状态,这显然也是不行的。那么可能就会有人说,0不行,200不行,那就设定为1呗,这个想法可以有,不过不是万能的,要根据实际情况来定,比如通信波特率如果设置的过小,那么也就意味着两个字符之间的间隔时间就会很长,所以VTIME设置的太小,可能会提前改变read阻塞状态,不过一般常用的波特率9600,115200,就不存在这个问题,设置成1即可。    配合select使用:这里我开始有点迷惑,既然有了VTIME,而且把VTIME设置的足够小了,为什么还要select呢,因为select也要设置超时改变read阻塞的,答案是这里的select是为了 处理 串口无数据接收的,因为我们使用的过程中,串口的发送我们是可以控制的,但是串口的数据接收时随机的,可能会不停的有数据进来,也可能偶尔有数据进来,也可能一直没有数据进来,那么对于没有数据进来的这段时间,我们的应用程序里,必然是有read函数的,这个时候read会一直阻塞(抛开一些非阻塞设置),这肯定不是我们想要的,我们可能 会说,可以用一个线程啊,阻塞就阻塞呗,这当然是一个方案,不过我们还是倾向于不要让任何一个进程一直阻塞,这一点可以参考之前的文章《 嵌入式Linux编程之select使用总结》。 当然为了程序的实时性,select的超时时间也不要设置的过长。     还有一点需要注意:我们在read 以后,尽可能的清理掉串口缓存,这样防止下次有新数据来了后,数据混淆的bug。     程序框架如下: #include #include #include #include #include #include #include #include #include #include #include #include #include typedef int u16; typedef char u8; #define DATA_LEN 200 u16 openSerial(u16 iPort) { u16 iFd; struct termios opt; u8 cSerialName[15]; if(iPort >= 10){ printf("no this serial:ttySP%d ", iPort); exit(1); //err return } sprintf(cSerialName, "/dev/ttySP%d", iPort - 1); printf("open serial name:%s ", cSerialName); iFd = open(cSerialName, O_RDWR | O_NOCTTY); if( iFd < 0){ perror(cSerialName); return -1; } //串口输入输出模式 各种参数配置 tcgetattr(iFd, &opt); cfsetispeed(&opt, B115200); cfsetospeed(&opt, B115200); //raw mode opt.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); opt.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); opt.c_oflag &= ~(OPOST); opt.c_cflag &= ~(CSIZE | PARENB); opt.c_cflag |= CS8; //VMIN和VTIME配置 opt.c_cc[VMIN] = DATA_LEN; opt.c_cc[VTIME] = 1; if( tcsetattr(iFd, TCSANOW, &opt) < 0){ return -1; } return iFd; } static struct timeval tv_timeout; static fd_set fs_read; static u16 usartFd; int main( void ) { u16 cnt = 0; u16 iRet2,rx_len; u8 rx_buf[256]; usartFd = openSerial( 1 ); while( 1 ){ FD_ZERO( &fs_read ); FD_SET(usartFd, &fs_read); tv_timeout.tv_sec = 0; tv_timeout.tv_usec = 10000; //10000us iRet2 = select(usartFd + 1, &fs_read, NULL, NULL, &tv_timeout); if( iRet2 ){ rx_len = read(usartFd, rx_buf, DATA_LEN); tcflush(usartFd, TCIFLUSH); //清除串口缓存 if( rx_len ){ write(usartFd, rx_buf, rx_len); //将接收的数据 原样打印 } } } exit( 0 ); }    上面的程序框架,测试可以实现串口的不定长数据接收。不过这个VTIME和VMIN的 设计也有一个小问题,那就是VTIME的单位是100ms,这就意味着,最短时间也是100ms,所以如果 想要进一步提高不定长数据接收的实时性,就不能 采用这种方式, 而是要将VTIME设置为0,VMIN则也是尽可能小,或者直接设置为0,不过这就要求在应用程序中做相应的 接收处理,可能会稍微麻烦。不过这个100ms对于串口通信来讲,还是可以接受的,大多数的串口通信,比如说Modbus通信,通信间隔一般都会超过100ms,毕竟太快的通信频率本身就不适合串口。