摘要:Linux下串口编程遇到的接收数据错误及原因
来源:https://dotblogs.com.tw/k/2012/07/24/73572
近日在调试串口的时候发现,另一设备向我ARM板的串口发送0x0d,我接收之后变成了0x0a,这是问题一;另外当对方向我发送一串数据,如果其中有0x11,那么我总是漏收此数,这是问题二。
由于问题莫名其妙,以为是笔记本的USB转232线缆的问题,换,问题依旧。
以为是对方设备的问题,采用串口调试助手模拟通讯设备与ARM板通讯,问题依旧。
无奈才去查看资料,最终得以解决,现总结如下:
1.串口操作需要的头文件
#include <stdio.h> //标准输入输出定义 #include <stdlib.h> //标准函数库定义 #include <unistd.h> //Unix标准函数定义 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //文件控制定义 #include <termios.h> //POSIX中断控制定义 #include <errno.h> //错误号定义
2.打开串口
串口位于/dev中,可作为标准文件的形式打开,其中:
串口1 /dev/ttyS0
串口2 /dev/ttyS1
代码如下:
int fd; fd = open(“/dev/ttyS0”, O_RDWR); if(fd == -1) { Perror(“串口1打开失败!”); } //else //fcntl(fd, F_SETFL, FNDELAY);
O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
3.设置波特率
最基本的串口设置包括波特率、校验位和停止位设置,且串口设置主要使用termios.h头文件中定义的termios结构,如下:
struct termios { tcflag_t c_iflag; //输入模式标志 tcflag_t c_oflag; //输出模式标志 tcflag_t c_cflag; //控制模式标志 tcflag_t c_lflag; //本地模式标志 cc_t c_line; //line discipline cc_t c_cc[NCC]; //control characters }
代码如下:
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, }; void SetSpeed(int fd, int speed) { int i; struct termios Opt; //定义termios结构 if(tcgetattr(fd, &Opt) != 0) { perror(“tcgetattr fd”); return; } for(i = 0; i < sizeof(speed_arr) / sizeof(int); i++) { if(speed == name_arr[i]) { tcflush(fd, TCIOFLUSH); cfsetispeed(&Opt, speed_arr[i]); cfsetospeed(&Opt, speed_arr[i]); if(tcsetattr(fd, TCSANOW, &Opt) != 0) { perror(“tcsetattr fd”); return; } tcflush(fd, TCIOFLUSH); } } }
TCSANOW:立即执行而不等待数据发送或者接受完成。
TCSADRAIN:等待所有数据传递完成后执行。
TCSAFLUSH:Flush input and output buffers and make the change
4.设置数据位、停止位和校验位
以下是几个数据位、停止位和校验位的设置方法:(以下均为1位停止位)
8位数据位、无校验位:
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS8;
7位数据位、奇校验:
Opt.c_cflag |= PARENB;
Opt.c_cflag |= PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位数据位、偶校验:
Opt.c_cflag |= PARENB;
Opt.c_cflag &= ~PARODD;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
7位数据位、Space校验:
Opt.c_cflag &= ~PARENB;
Opt.c_cflag &= ~CSTOPB;
Opt.c_cflag &= ~CSIZE;
Opt.c_cflag |= CS7;
代码如下:
int SetParity(int fd, int databits, int stopbits, int parity) { struct termios Opt; if(tcgetattr(fd, &Opt) != 0) { perror("tcgetattr fd"); return FALSE; } Opt.c_cflag |= (CLOCAL | CREAD); //一般必设置的标志 switch(databits) //设置数据位数 { case 7: Opt.c_cflag &= ~CSIZE; Opt.c_cflag |= CS7; break; case 8: Opt.c_cflag &= ~CSIZE; Opt.c_cflag |= CS8; berak; default: fprintf(stderr, "Unsupported data size. "); return FALSE; } switch(parity) //设置校验位 { case 'n': case 'N': Opt.c_cflag &= ~PARENB; //清除校验位 Opt.c_iflag &= ~INPCK; //enable parity checking break; case 'o': case 'O': Opt.c_cflag |= PARENB; //enable parity Opt.c_cflag |= PARODD; //奇校验 Opt.c_iflag |= INPCK //disable parity checking break; case 'e': case 'E': Opt.c_cflag |= PARENB; //enable parity Opt.c_cflag &= ~PARODD; //偶校验 Opt.c_iflag |= INPCK; //disable pairty checking break; case 's': case 'S': Opt.c_cflag &= ~PARENB; //清除校验位 Opt.c_cflag &= ~CSTOPB; //?????????????? Opt.c_iflag |= INPCK; //disable pairty checking break; default: fprintf(stderr, "Unsupported parity. "); return FALSE; } switch(stopbits) //设置停止位 { case 1: Opt.c_cflag &= ~CSTOPB; break; case 2: Opt.c_cflag |= CSTOPB; break; default: fprintf(stderr, "Unsupported stopbits. "); return FALSE; } opt.c_cflag |= (CLOCAL | CREAD); opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); opt.c_oflag &= ~OPOST; opt.c_oflag &= ~(ONLCR | OCRNL); //添加的 opt.c_iflag &= ~(ICRNL | INLCR); opt.c_iflag &= ~(IXON | IXOFF | IXANY); //添加的 tcflush(fd, TCIFLUSH); Opt.c_cc[VTIME] = 0; //设置超时为15sec Opt.c_cc[VMIN] = 0; //Update the Opt and do it now if(tcsetattr(fd, TCSANOW, &Opt) != 0) { perror("tcsetattr fd"); return FALSE; } return TRUE; }
5.某些设置项
在第四步中我们看到一些比较特殊的设置,下面简述一下他们的作用。
c_cc数组的VSTART和VSTOP元素被设定成DC1和DC3,代表ASCII标准的XON和XOFF字符,如果在传输这两个字符的时候就传不过去,需要把软件流控制屏蔽,即:
Opt.c_iflag &= ~ (IXON | IXOFF | IXANY);
有时候,在用write发送数据时没有键入回车,信息就发送不出去,这主要是因为我们在输入输出时是按照规范模式接收到回车或换行才发送,而更多情况下我们是不必键入回车或换行的。此时应转换到行方式输入,不经处理直接发送,设置如下:
Opt.c_lflag &= ~ (ICANON | ECHO | ECHOE | ISIG);
还存在这样的情况:发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:
Opt.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
Opt.c_oflag &= ~(ONLCR | OCRNL);
6.读写串口
发送数据方式如下,write函数将返回写的位数或者当错误时为-1。
char buffer[1024];
int length;
int nByte;
nByte = write(fd, buffer, length);
读取数据方式如下,原始数据模式下每个read函数将返回实际串口收到的字符数,如果串口中没有字符可用,回叫将会阻塞直到以下几种情况:有字符进入;一个间隔计时器失效;错误发送。
在打开串口成功后,使用fcntl(fd, F_SETFL, FNDELAY)语句,可以使read函数立即返回而不阻塞。FNDELAY选项使read函数在串口无字符时立即返回且为0。
char buffer[1024];
int length;
int readByte;
readByte = read(fd, buffer, len);
注意:设置为原始模式传输数据的话,read函数返回的字符数是实际串口收到的字符数。Linux下直接用read读串口可能会造成堵塞,或者数据读出错误,此时可使用tcntl或者select等函数实现异步读取。用select先查询com口,再用read去读就可以避免上述错误。
7.关闭串口
串口作为文件来处理,所以一般的关闭文件函数即可:
close(fd);
8.例子
这个例子中,需要打开串口1,设置9600波特率、8位数据位、1位停止位以及空校验,之后利用while语句循环判断串口中是否可以读出数据,将串口中数据连续读出后重新写回到串口中。
该程序可与minicom联合测试。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <errno.h> main() { int fd; int i; int len; int n = 0; char read_buf[256]; char write_buf[256]; struct termios opt; fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); //默认为阻塞读方式 if(fd == -1) { perror("open serial 0 "); exit(0); } tcgetattr(fd, &opt); cfsetispeed(&opt, B9600); cfsetospeed(&opt, B9600); if(tcsetattr(fd, TCSANOW, &opt) != 0 ) { perror("tcsetattr error"); return -1; } opt.c_cflag &= ~CSIZE; opt.c_cflag |= CS8; opt.c_cflag &= ~CSTOPB; opt.c_cflag &= ~PARENB; opt.c_cflag &= ~INPCK; opt.c_cflag |= (CLOCAL | CREAD); opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); opt.c_oflag &= ~OPOST; opt.c_oflag &= ~(ONLCR | OCRNL); //添加的 opt.c_iflag &= ~(ICRNL | INLCR); opt.c_iflag &= ~(IXON | IXOFF | IXANY); //添加的 opt.c_cc[VTIME] = 0; opt.c_cc[VMIN] = 0; tcflush(fd, TCIOFLUSH); printf("configure complete "); if(tcsetattr(fd, TCSANOW, &opt) != 0) { perror("serial error"); return -1; } printf("start send and receive data "); while(1) { n = 0; len = 0; bzero(read_buf, sizeof(read_buf)); //类似于memset bzero(write_buf, sizeof(write_buf)); while( (n = read(fd, read_buf, sizeof(read_buf))) > 0 ) { for(i = len; i < (len + n); i++) { write_buf[i] = read_buf[i - len]; } len += n; } write_buf[len] = '