上一篇文章中,我们详细分析了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,毕竟太快的通信频率本身就不适合串口。