Linux下串口编程遇到的接收数据错误及原因.docx
《Linux下串口编程遇到的接收数据错误及原因.docx》由会员分享,可在线阅读,更多相关《Linux下串口编程遇到的接收数据错误及原因.docx(24页珍藏版)》请在冰豆网上搜索。
Linux下串口编程遇到的接收数据错误及原因
Linux下串口编程遇到的接收数据错误及原因
Linux下串口編程遇到的接收數据錯誤及原因
近日在调试串口的时候发现,另一设备向我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
代码如下:
intfd;
fd=open(“/dev/ttyS0”,O_RDWR);
if(fd==-1)
{
Perror(“串口1打开失败!
”);
}
//else
//fcntl(fd,F_SETFL,FNDELAY);
除了使用O_RDWR标志之外,通常还会使用O_NOCTTY和O_NDELAY这两个标志。
O_NOCTTY:
告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序。
O_NDELAY:
告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
3.设置波特率
最基本的串口设置包括波特率、校验位和停止位设置,且串口设置主要使用termios.h头文件中定义的termios结构,如下:
structtermios
{
tcflag_tc_iflag;//输入模式标志
tcflag_tc_oflag;//输出模式标志
tcflag_tc_cflag;//控制模式标志
tcflag_tc_lflag;//本地模式标志
cc_tc_line;//linediscipline
cc_tc_cc[NCC];//controlcharacters
}
代码如下:
intspeed_arr[]={B38400,B19200,B9600,B4800,B2400,B1200,B300,B38400,B19200,B9600,B4800,B2400,B1200,B300,};
intname_arr[]={38400,19200,9600,4800,2400,1200,300,38400,19200,9600,4800,2400,1200,300,};
voidSetSpeed(intfd,intspeed)
{
inti;
structtermiosOpt;//定义termios结构
if(tcgetattr(fd,&Opt)!
=0)
{
perror(“tcgetattrfd”);
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(“tcsetattrfd”);
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
注意tcsetattr函数中使用的标志:
TCSANOW:
立即执行而不等待数据发送或者接受完成。
TCSADRAIN:
等待所有数据传递完成后执行。
TCSAFLUSH:
Flushinputandoutputbuffersandmakethechange
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;
代码如下:
intSetParity(intfd,intdatabits,intstopbits,intparity)
{
structtermiosOpt;
if(tcgetattr(fd,&Opt)!
=0)
{
perror("tcgetattrfd");
returnFALSE;
}
Opt.c_cflag|=(CLOCAL|CREAD);//一般必设置的标志
switch(databits)//设置数据位数
{
case7:
Opt.c_cflag&=~CSIZE;
Opt.c_cflag|=CS7;
break;
case8:
Opt.c_cflag&=~CSIZE;
Opt.c_cflag|=CS8;
berak;
default:
fprintf(stderr,"Unsupporteddatasize.\n");
returnFALSE;
}
switch(parity)//设置校验位
{
case'n':
case'N':
Opt.c_cflag&=~PARENB;//清除校验位
Opt.c_iflag&=~INPCK;//enableparitychecking
break;
case'o':
case'O':
Opt.c_cflag|=PARENB;//enableparity
Opt.c_cflag|=PARODD;//奇校验
Opt.c_iflag|=INPCK//disableparitychecking
break;
case'e':
case'E':
Opt.c_cflag|=PARENB;//enableparity
Opt.c_cflag&=~PARODD;//偶校验
Opt.c_iflag|=INPCK;//disablepairtychecking
break;
case's':
case'S':
Opt.c_cflag&=~PARENB;//清除校验位
Opt.c_cflag&=~CSTOPB;//?
?
?
?
?
?
?
?
?
?
?
?
?
?
Opt.c_iflag|=INPCK;//disablepairtychecking
break;
default:
fprintf(stderr,"Unsupportedparity.\n");
returnFALSE;
}
switch(stopbits)//设置停止位
{
case1:
Opt.c_cflag&=~CSTOPB;
break;
case2:
Opt.c_cflag|=CSTOPB;
break;
default:
fprintf(stderr,"Unsupportedstopbits.\n");
returnFALSE;
}
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;//UpdatetheOptanddoitnow
if(tcsetattr(fd,TCSANOW,&Opt)!
=0)
{
perror("tcsetattrfd");
returnFALSE;
}
returnTRUE;
}
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。
charbuffer[1024];
intlength;
intnByte;
nByte=write(fd,buffer,length);
读取数据方式如下,原始数据模式下每个read函数将返回实际串口收到的字符数,如果串口中没有字符可用,回叫将会阻塞直到以下几种情况:
有字符进入;一个间隔计时器失效;错误发送。
在打开串口成功后,使用fcntl(fd,F_SETFL,FNDELAY)语句,可以使read函数立即返回而不阻塞。
FNDELAY选项使read函数在串口无字符时立即返回且为0。
charbuffer[1024];
intlength;
intreadByte;
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()
{
intfd;
inti;
intlen;
intn=0;
charread_buf[256];
charwrite_buf[256];
structtermiosopt;
fd=open("/dev/ttyS0",O_RDWR|O_NOCTTY);//默认为阻塞读方式
if(fd==-1)
{
perror("openserial0\n");
exit(0);
}
tcgetattr(fd,&opt);
cfsetispeed(&opt,B9600);
cfsetospeed(&opt,B9600);
if(tcsetattr(fd,TCSANOW,&opt)!
=0)
{
perror("tcsetattrerror");
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("configurecomplete\n");
if(tcsetattr(fd,TCSANOW,&opt)!
=0)
{
perror("serialerror");
return-1;
}
printf("startsendandreceivedata\n");
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]='\0';
printf("Len%d\n",len);
printf("%s\n",write_buf);
n=write(fd,write_buf,len);
printf("write%dchars\n",n);
sleep
(2);
}
}
9.附录
c_cflag用于设置控制参数,除了波特率外还包含以下内容:
EXTAExternalrateclock
EXTBExternalrateclock
CSIZEBitmaskfordatabits
CS55个数据位
CS66个数据位
CS77个数据位
CS88个数据位
CSTOPB2个停止位(清除该标志表示1个停止位
PARENB允许校验位
PARODD使用奇校验(清除该标志表示使用偶校验)
CREADEnablereceiver
HUPCLHangup(dropDTR)onlastclose
CLOCALLocalline–donotchange“owner”ofport
LOBLKBlockjobcontroloutpu
c_cflag标志可以定义CLOCAL和CREAD,这将确保该程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。
CLOCAL和CREAD通常总是被是能的。
c_lflag用于设置本地模式,决定串口驱动如何处理输入字符,设置内容如下:
ISIGEnableSIGINTR,SIGSUSP,SIGDSUSP,andSIGQUITsignals
ICANONEnablecanonicalinput(elseraw)
XCASEMapuppercase\lowercase(obsolete)
ECHOEnableechoingofinputcharacters
ECHOEEchoerasecharacterasBS-SP-BS
ECHOKEchoNLafterkillcharacter
ECHONLEchoNL
NOFLSHDisableflushingofinputbuffersafterinterruptorquitcharacters
IEXTENEnableextendedfunctions
ECHOCTLEchocontrolcharactersas^charanddeleteas~?
ECHOPRTEchoerasedcharacterascharactererased
ECHOKEBS-SP-BSentirelineonlinekill
FLUSHOOutputbeingflushed
PENDINRetypependinginputatnextreadorinputchar
TOSTOPSendSIGTTOUforbackgroundoutput
c_iflag用于设置如何处理串口上接收到的数据,包含如下内容:
INPCKEnableparitycheck
IGNPARIgnoreparityerrors
PARMRKMarkparityerrors
ISTRIPStripparitybits
IXONEnablesoftwareflowcontrol(outgoing)
IXOFFEnablesoftwareflowcontrol(incoming)
IXANYAllowanycharactertostartflowagain
IGNBRKIgnorebreakcondition
BRKINTSendaSIGINTwhenabreakconditionisdetected
INLCRMapNLtoCR
IGNCRIgnoreCR
ICRNLMapCRtoNL
IUCLCMapuppercasetolowercase
IMAXBELEchoBELoninputlinetoolong
c_oflag用于设置如何处理输出数据,包含如下内容:
OPOSTPostprocessoutput(notset=rawoutput)
OLCUCMaplowercasetouppercase
ONLCRMapNLtoCR-NL
OCRNLMapCRtoNL
NOCRNoCRoutputatcolumn0
ONLRETNLperformsCRfunction
OFILLUsefillcharactersfordelay
OFDELFillcharacterisDEL
NLDLYMaskfordelaytimeneededbetweenlines
NL0NodelayforNLs
NL1Delayfurtheroutputafternewlinefor100milliseconds
CRDLYMaskfordelaytimeneededtoreturncarriagetoleftcolumn
CR0NodelayforCRs
CR1DelayafterCRsdependingoncu
rrentcolumnposition
CR2Delay100millisecondsaftersendingCRs
CR3Delay150millisecondsaftersendingCRs
TABDLYMaskfordelaytimeneededafterTABs
TAB0NodelayforTABs
TAB1DelayafterT