在基于AT91的嵌入式linux中接收串口数据时,发现对于接收的数据经常出现接收不完整的现象。一帧的数据可能会被当做两帧接收,导致对于一帧数据接收出现问题。虽然这种情况在一般情况下,并不是经常出现,但是只要数据量稍微大一些,情况就会出现。
于是仔细看了程序中关于串口配置这一块的程序,
- int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
- {
- struct termios oldtio;
- struct termios newtio;
- if( tcgetattr(fd, &oldtio) != 0) {
- perror("SetupSerial 1");
- return -1;
- }
- bzero( &newtio, sizeof( newtio ));
- newtio.c_cflag |= CLOCAL | CREAD;
- newtio.c_cflag &= ~CSIZE;
- switch( nBits )
- {
- case 7:
- newtio.c_cflag |= CS7;
- break;
- case 8:
- newtio.c_cflag |= CS8;
- break;
- }
- switch( nEvent )
- {
- case 'O':
- newtio.c_cflag |= PARENB;
- newtio.c_cflag |= PARODD;
- newtio.c_iflag |= INPCK ;
- break;
- case 'E':
- newtio.c_iflag |= INPCK ;
- newtio.c_cflag |= PARENB;
- newtio.c_cflag &= ~PARODD;
- break;
- case 'N':
- newtio.c_cflag &= ~PARENB;
- break;
- }
- switch( nSpeed )
- {
- case 2400:
- cfsetispeed(&newtio, B2400);
- cfsetospeed(&newtio, B2400);
- break;
- case 4800:
- cfsetispeed(&newtio, B4800);
- cfsetospeed(&newtio, B4800);
- break;
- case 9600:
- cfsetispeed(&newtio, B9600);
- cfsetospeed(&newtio, B9600);
- break;
- case 115200:
- cfsetispeed(&newtio, B115200);
- cfsetospeed(&newtio, B115200);
- break;
- case 460800:
- cfsetispeed(&newtio, B460800);
- cfsetospeed(&newtio, B460800);
- break;
- default:
- cfsetispeed(&newtio, B9600);
- cfsetospeed(&newtio, B9600);
- break;
- }
- if( nStop == 1 )
- newtio.c_cflag &= ~CSTOPB;
- else if ( nStop == 2 )
- newtio.c_cflag |= CSTOPB;
- newtio.c_cc[VTIME] = 0;
- newtio.c_cc[VMIN] = 1;
- tcflush(fd,TCIFLUSH);
- if((tcsetattr(fd,TCSANOW,&newtio))!=0)
- {
- perror("com set error");
- return -1;
- }
- tcflush(fd,TCIFLUSH);//The `tcflush' function is used to clear the input and/or output
- return 0;
- }
在分析完程序后发现可能导致出问题的地方:在使用oldtio读取串口配置后,却没有将其复制给newtio,并且将newtio清零,这造成下边的设置操作,修改了一些原来的设置。
根据程序修改VTIME,VMIN可推知这里要使用非规范方式,
根据APUE可知由VTIME,VMIN的设置共可以有四种选择
A:VTIME > 0, VMIN > 0
B: VTIME = 0, VMIN > 0
C: VTIME > 0, VMIN = 0
D: VTIME = 0, VMIN = 0
由程序修改的值
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 1;
可知这里要设置为第二种方式:只有接收到MIN个字节数据,read才返回;否则,read将阻塞。
因为这里没有将oldtio复制给newtio所以这里的ICANON标识一定没有设置,所以是处于非规范模式下。这里的VTIME, VMIN对这里的设置也是有效的。
但是这样难免修改一些我们没有注意的选项,根据这里的设置,结合APUE中的示例程序,发现APUE中将终端设置为原始模式(raw modle)与这里的设置较为相似,于是想采用APUE中的部分参数设置,来修改此处的程序。
==================APUE中 put terminal into a raw modle ===========================
- int
- tty_raw(int fd) /* put terminal into a raw mode */
- {
- int err;
- struct termios buf;
- if (ttystate != RESET) {
- errno = EINVAL;
- return(-1);
- }
- if (tcgetattr(fd, &buf) < 0)
- return(-1);
- save_termios = buf; /* structure copy */
- /*
- * Echo off, canonical mode off, extended input
- * processing off, signal chars off.
- */
- buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
- /*
- * No SIGINT on BREAK, CR-to-NL off, input parity
- * check off, don't strip 8th bit on input, output
- * flow control off.
- */
- buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
- /*
- * Clear size bits, parity checking off.
- */
- buf.c_cflag &= ~(CSIZE | PARENB);
- /*
- * Set 8 bits/char.
- */
- buf.c_cflag |= CS8;
- /*
- * Output processing off.
- */
- buf.c_oflag &= ~(OPOST);
- /*
- * Case B: 1 byte at a time, no timer.
- */
- buf.c_cc[VMIN] = 1;
- buf.c_cc[VTIME] = 0;
- if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
- return(-1);
- /*
- * Verify that the changes stuck. tcsetattr can return 0 on
- * partial success.
- */
- if (tcgetattr(fd, &buf) < 0) {
- err = errno;
- tcsetattr(fd, TCSAFLUSH, &save_termios);
- errno = err;
- return(-1);
- }
- if ((buf.c_lflag & (ECHO | ICANON | IEXTEN | ISIG)) ||
- (buf.c_iflag & (BRKINT | ICRNL | INPCK | ISTRIP | IXON)) ||
- (buf.c_cflag & (CSIZE | PARENB | CS8)) != CS8 ||
- (buf.c_oflag & OPOST) || buf.c_cc[VMIN] != 1 ||
- buf.c_cc[VTIME] != 0) {
- /*
- * Only some of the changes were made. Restore the
- * original settings.
- */
- tcsetattr(fd, TCSAFLUSH, &save_termios);
- errno = EINVAL;
- return(-1);
- }
- ttystate = RAW;
- ttysavefd = fd;
- return(0);
- }
从英文注释,可以了解到,各个参数的具体意义,
同时参考博客http://blog.csdn.net/awei_xu/article/details/3725329 中的红色标记部分设置来补充,
- if((fd = open(dev,O_RDWR | O_NOCTTY | O_NDELAY)) == -1)
- /*---------------------- 重要----------------------*/
- //保证本程序不会成为端口的所有者,从而妨碍控制工作和挂起信号.
- opt.c_cflag |= (CLOCAL | CREAD);
- //选择原始输入方式: 原始输入方式是不经处理的.
- opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- //输出不经处理
- opt.c_oflag &= ~OPOST;
- //取消软件流控制(不设置可能存在丢码)
- opt.c_iflag &= ~(IXON | IXOFF | IXANY); /*----------------------------------------------------*/
修改后的程序:
- int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
- {
- struct termios oldtio;
- struct termios newtio;
- if( tcgetattr(fd, &oldtio) != 0) {
- perror("SetupSerial 1");
- return -1;
- }
- newtio = oldtio;
- /*************Debug*******************/
- newtio.c_cflag |= CLOCAL | CREAD;
- newtio.c_cflag &= ~(CSIZE | PARENB);
- newtio.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE | IEXTEN);
- newtio.c_oflag &= ~OPOST;
- newtio.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | BRKINT
- | INPCK | ISTRIP);
- /***************************************/
- switch( nBits )
- {
- case 7:
- newtio.c_cflag |= CS7;
- break;
- case 8:
- newtio.c_cflag |= CS8;
- break;
- }
- switch( nEvent )
- {
- case 'O':
- /*odd parity*/
- newtio.c_iflag |= INPCK ;
- newtio.c_cflag |= PARENB;
- newtio.c_cflag |= PARODD;
- break;
- case 'E':
- /*even parity*/
- newtio.c_iflag |= INPCK ;
- newtio.c_cflag |= PARENB;
- newtio.c_cflag &= ~PARODD;
- break;
- case 'N':
- /*no parity*/
- newtio.c_cflag &= ~PARENB;
- break;
- }
- switch( nSpeed )
- {
- case 2400:
- cfsetispeed(&newtio, B2400);
- cfsetospeed(&newtio, B2400);
- break;
- case 4800:
- cfsetispeed(&newtio, B4800);
- cfsetospeed(&newtio, B4800);
- break;
- case 9600:
- cfsetispeed(&newtio, B9600);
- cfsetospeed(&newtio, B9600);
- break;
- case 115200:
- cfsetispeed(&newtio, B115200);
- cfsetospeed(&newtio, B115200);
- break;
- case 460800:
- cfsetispeed(&newtio, B460800);
- cfsetospeed(&newtio, B460800);
- break;
- default:
- cfsetispeed(&newtio, B9600);
- cfsetospeed(&newtio, B9600);
- break;
- }
- if( nStop == 1 )
- newtio.c_cflag &= ~CSTOPB;
- else if ( nStop == 2 )
- newtio.c_cflag |= CSTOPB;
- newtio.c_cc[VTIME] = 0;
- newtio.c_cc[VMIN] = 1;
- tcflush(fd,TCIFLUSH);
- if((tcsetattr(fd,TCSANOW,&newtio))!=0)
- {
- perror("com set error");
- return -1;
- }
- tcflush(fd,TCIFLUSH);
- return 0;
- }
修改完的初步测试中发现有些地方还有问题,
待下一步测试,找到原因所在。