如何进行串口编程.docx
《如何进行串口编程.docx》由会员分享,可在线阅读,更多相关《如何进行串口编程.docx(12页珍藏版)》请在冰豆网上搜索。
如何进行串口编程
如何进行串口编程
arm2009-12-2623:
15:
49阅读122评论0字号:
大中小
1、 调试方法:
调试代码的最好办法是安装另一个Linux盒子,并且通过一个空载的调制解调器电缆把这两台机子连接起来。
使用miniterm(是LDP程序员向导的一个例子程序(ftp:
//sunsite。
unc。
edu/pub/Linux/docs/LDP/programmers-guide/lpg-0.4.tar。
gzin))向你的Linux盒子传输数据。
Miniterm可以很容易的编译并且可以通过串口传送所有的键盘输入。
使用的时候需要检查下面这个宏:
#defineMODEMDEVICE"/dev/ttyS0"。
如果是COM1,则对应ttyS0,COM2对应ttyS1,等等。
有必要在没有任何输出的情况下通过串口线传送所有字符的输入。
要测试你的连接是否正确,在两台机子上启动miniterm,然后敲一下键盘。
那末刚才在计算机上输入的字符会在另一台机子上显示出来,反之亦然。
这个输入不会回显到触动键盘集资的显示屏上。
要制作一条零讯号的调制解调器电缆,你需要通过TxD(传输)和RxD(接收)线。
有关电缆设置的描述可以参看下面的第七部分。
如果在你的机子上有两个空闲的串口,你也可以只通过这一台计算机运行上面的测试程序。
那末你可以在两个控制台上同时运行miniterm。
如果你想拔掉鼠标释放这个串口,要查看一下是否存在/dev/mouse,如存在记着对它进行重定向。
如果你使用的是一个多串口的控制卡,要确保配置正确。
我曾经犯过这样的配置错误:
程序只在我自己的机子上才正常工作。
当我连接另一台机子的时候, 串口开始零星的丢失字符。
不过,在一台程序上运行两个程序并不是完全异步的。
2、端口设置
设备文件/dev/ttyS*用来与你的Linux机子中的终端关联,这些关联是在启动之后完成配置的。
在使用原始设备进行通讯编程时需要特别注意,举例来说,有些设备被配置成在设备上进行数据回显的,当用这些端口进行数据传输时需要对配置进行改变以适应数据传输的要求。
所有的参数都可以通过程序来配置。
这些配置存放在一个结构termios中:
它的定义在中:
#defineNCCS19
structtermios{
tcflag_tc_iflag; /*输入模式标志*/
tcflag_tc_oflag; /*输出模式标志*/
tcflag_tc_cflag; /*控制模式标志*/
tcflag_tc_lflag; /*本地模式标志*/
cc_tc_line; /*引脚设置*/
cc_tc_cc[NCCS]; /*字符控制*/
};
这个文件中也包含了所有的标志定义。
c_iflag中的输入模式标志控制所有的输入处理,这意味着从设备上传来的字符在被读出之前可以处理一下。
类似地,c_oflag控制输出处理。
c_cflag包含了一些端口设置参数,比如波特率,每个字符的位数,终止位,等等。
。
。
存放在c_lflag中的本地模式标志决定字符是否回显,信号是否被发送到你的程序,等等…最后的数组c_cc定义了一些控制字符,比如文件尾,停止,等等…控制字符的缺省值的定义在文件中。
这些标志在termios(3)的手册中都有描述。
Termios结构包含c_line(引脚设置)成员,它在POSIX兼容机中不被使用。
3.串口设备的输入概念
下面有三种输入方式的介绍。
我们可以根据程序选择适当的方式。
值得一提的是无论采用哪种方式,不要通过一个个字符的读取方式来获取一个字符串。
因为当我试图这样做的时候,即使没有任何错误,也会丢失一些字符。
3.1.规范的输入处理
这是通常的终端处理模式,但是这种模式下一个读取操作会返回整条线上的输入,这在跟其它设备以整条线的输入作为通信单元通信的时候也是很有用的。
线路输入缺省情况下可以被一个NL(ASCIILF)中断,它可以看作是文件结尾或者行结尾。
在缺省的设置中一个CR符号(DOS/Windows下的缺省行终止符)不会中断线路的传输。
规范的输入处理擦除,删除字符,以及重复打印字符,将CR转化成NL,等等。
3.2.不规范的输入处理
不规范的输入处理可以再一次读取中处理固定量的字符,并且允许使用字符定时器。
如果你的程序总是读取定量的字符,或者被连接的设备常常短时间内发送大量数据的时候,可以使用这种模式。
3.3.异步输入
上面描述的两种模式可以用在同步和异步模式下。
缺省使用同步模式,在此模式下,读取操作会一直处于阻塞状态直到允许读取。
在异步模式下读取操作会立刻返回并在完成后向调用程序发送一个信号。
这个信号可以被信号处理者接受到。
3.4.多方输入的等待处理
如果你需要同时控制多个设备,多方输入的等待处理可能会很有用,其实跟前面的几种方式没有本质的区别的。
在我的应用中我同时通过TCP/IP套接字和串口连接来控制另一台机子。
下面给出的例子会等待两个不同的输入资源。
如果一个输入资源变为有效,它会被处理,同时程序会等待新的输入。
下面介绍的方法看起来相当复杂,但是要记着Linux是多任务操作系统。
用来等待输入的系统调用不会消耗CPU时间,只有有输入并进行处理的时候才会降低同一时刻运行的其他程序的处理速度。
4.程序例子
所有的例子都来自miniterm.c。
前面定义的缓冲区大小局限于255个字符,这跟规范输入处理的最大字符串长度是一样的(或者)。
可以根据代码注释来研究不同的输入模式。
我希望代码可以被读懂。
其中规范输入的注释是最好的,其他模式的注释只标出了与规范输入不同的地方以作强调。
描述虽然不完整,但是欢迎你进行试验,从而在你的程序中找到最好的解决方案。
不要忘了为使用的串口设置正确的访问权限(举例来说.:
chmoda+rw/dev/ttyS1)!
4.1.规范的输入处理
#include
#include
#include
#include
#include
/*波特率的定义在中,它包含在中*/
#defineBAUDRATEB38400
/*为对应的的端口设置对应的节点名称*/
#defineMODEMDEVICE"/dev/ttyS1"
#define_POSIX_SOURCE1/*POSIX兼容资源*/
#defineFALSE0
#defineTRUE1
volatileintSTOP=FALSE;
main()
{
intfd,c,res;
structtermiosoldtio,newtio;
charbuf[255];
/*
以读写方式和不受tty可能控制的方式打开调制设备,因为我们不希望由于线路上误输入CTRL?
C而终止程序。
*/
fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY);
if(fd<0){perror(MODEMDEVICE);exit(?
1);}
tcgetattr(fd,&oldtio);/*保存当前的串口设置*/
bzero(&newtio,sizeof(newtio));/*对新的设置清零*/
/*
BAUDRATE:
设置速率。
你也可以使用cfsetispeed和cfsetospeed进行设置。
CRTSCTS:
输出硬件流程(仅在电缆有所有必需的线的时候使用,参看串口HOWTO的第七部分)
CS8:
8n1的意思是(位数8,不用奇偶校验,停止位是1)
CLOCAL:
本地连接,没有解调器控制
CREAD:
允许接收字符*/
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;
/*
IGNPAR:
忽略奇偶校验错误的字节
ICRNL:
把CR影射为NL(否则在其他机子上的一个CR输入无法中断输入过程)否则使设备处于自然状态(没有其他输入处理)
*/
newtio.c_iflag=IGNPAR|ICRNL;
/*
原始输出
*/
newtio.c_oflag=0;
/*
ICANON:
规范输入,使所有的回显功能失效,并且不发送调用程序信号
*/
newtio.c_lflag=ICANON;
/*
初始化所有的控制字符,缺省值在/usr/include/termios.h中可以找到,主食里面有详细的描述,在这里我们不需要这些*/
newtio.c_cc[VINTR]=0;/*Ctrl?
c*/
newtio.c_cc[VQUIT]=0;/*Ctrl?
\*/
newtio.c_cc[VERASE]=0;/*del*/
newtio.c_cc[VKILL]=0;/*@*/
newtio.c_cc[VEOF]=4;/*Ctrl?
d*/
newtio.c_cc[VTIME]=0;/*字符间的定时器,此处未使用*/
newtio.c_cc[VMIN]=1;/*阻塞读取,直到有一个字符输入时才解除*/
newtio.c_cc[VSWTC]=0;/*'\0'*/
newtio.c_cc[VSTART]=0;/*Ctrl?
q*/
newtio.c_cc[VSTOP]=0;/*Ctrl?
s*/
newtio.c_cc[VSUSP]=0;/*Ctrl?
z*/
newtio.c_cc[VEOL]=0;/*'\0'*/
newtio.c_cc[VREPRINT]=0;/*Ctrl?
r*/
newtio.c_cc[VDISCARD]=0;/*Ctrl?
u*/
newtio.c_cc[VWERASE]=0;/*Ctrl?
w*/
newtio.c_cc[VLNEXT]=0;/*Ctrl?
v*/
newtio.c_cc[VEOL2]=0;/*'\0'*/
/*
现在清除调制解调器线路并激活端口设置
*/
tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/*
上面的设置做完了,现在控制输入。
比如,在开始的线路上输入一个'z'就退出程序。
*/
while(STOP==FALSE){/*直到满足终止条件时终止循环*/
/*如果么没有读到线路上输入终止字符,即使有超过255个字符的输入,读取数据块的操作也不会停止。
如果一次读不完所有字符,下一轮将会读取剩下的字符。
res变量会保存实际读到的字符个数。
*/
res=read(fd,buf,255);
buf[res]=0;/*设置字符串结尾以便打印输出*/
printf(":
%s:
%d\n",buf,res);
if(buf[0]=='z')STOP=TRUE;
}
/*恢复原来的设置*/
tcsetattr(fd,TCSANOW,&oldtio);
}
4.2.不规范的输入处理
在不规范输入的处理模式中,输入不会局限在控制线上,没有输入处理操作(擦除,杀掉,删除,等等)。
有两个参数来控制它的行为:
:
c_cc[VTIME]用来设置字符定时器,c_cc[VMIN]用来在读取动作执行前设置接收的最小字符数。
如果MIN>0并且TIME=0,MIN用来在读操作执行前设置要接收的字符个数。
由于TIME是零,所以此处不用。
如果MIN=0并且TIME>0,TIME会作为一个超时值。
读操作会在如下的情况下被执行:
一个单字符被读到,或者TIME过期(时间t=TIME*0。
1秒)。
如果TIME超时,不会读到任何字符。
如果MIN>0并且TIME>0,TIME就会作为一个字符间的定时器。
读操作会在如下两种情况下执行:
一是最少的(MIN)字符被接收到,或者字符间的延时超过了TIME。
定时器会在每个字符被接收到以后重启动,并且仅当收到首字符以后才被激活。
如果MIN=0并且TIME=0,读操作会立刻被执行。
当前有效字符串的个数,或者被请求的字符个数会被返回。
根据安东尼奥的研究(参看下面的文档),需要调用fcntl(fd,F_SETFL,FNDELAY)以后才能读到相同的结果。
通过修改newtio.c_cc[VTIME]和newtio.c_cc[VMIN],上面描述的所有模式都可以进行测试的。
#include
#include
#include
#include
#include
#defineBAUDRATEB38400
#defineMODEMDEVICE"/dev/ttyS1"
#define_POSIX_SOURCE1/*POSIX兼容资源*/
#defineFALSE0
#defineTRUE1
volatileintSTOP=FALSE;
main()
{
intfd,c,res;
structtermiosoldtio,newtio;
charbuf[255];
fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY);
if(fd<0){perror(MODEMDEVICE);exit(?
1);}
tcgetattr(fd,&oldtio);/*保存当前设置*/
bzero(&newtio,sizeof(newtio));
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;
newtio.c_iflag=IGNPAR;
newtio.c_oflag=0;
/*setinputmode(non?
canonical,noecho,。
。
。
)*/
newtio.c_lflag=0;
newtio.c_cc[VTIME]=0;/*字符间定时器,此处未用*/
newtio.c_cc[VMIN]=5;/*收到五个字符时阻塞读取操作*/
tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
while(STOP==FALSE){/*输入循环*/
res=read(fd,buf,255);/*收到5个字符后返回*/
buf[res]=0;/*这一步方便打印。
。
。
*/
printf(":
%s:
%d\n",buf,res);
if(buf[0]=='z')STOP=TRUE;
}
tcsetattr(fd,TCSANOW,&oldtio);
}
3.3.异步输入
#include
#include
#include
#include
#include
#include
#defineBAUDRATEB38400
#defineMODEMDEVICE"/dev/ttyS1"
#define_POSIX_SOURCE1/*POSIX兼容资源*/
#defineFALSE0
#defineTRUE1
volatileintSTOP=FALSE;
voidsignal_handler_IO(intstatus); /*定义信号控制*/
intwait_flag=TRUE; /*没有信号输入时为TRUE*/
main()
{
intfd,c,res;
structtermiosoldtio,newtio;
structsigactionsaio;/*信号动作定义*/
charbuf[255];
/*以非阻塞方式打开设备节点(读取操作会立刻返回)*/
fd=open(MODEMDEVICE,O_RDWR|O_NOCTTY|O_NONBLOCK);
if(fd<0){perror(MODEMDEVICE);exit(?
1);}
/*在设备进行异步操作前安装信号控制*/
saio.sa_handler=signal_handler_IO;
saio.sa_mask=0;
saio.sa_flags=0;
saio.sa_restorer=NULL;
sigaction(SIGIO,&saio,NULL);
/*当收到SIGIO信号时同意处理*/
fcntl(fd,F_SETOWN,getpid());
/*使文件描述符异步(手册上说只能使用O_APPEND和O_NONBLOCK方式,通过设置F_SETFL标记工作。
。
。
)*/
fcntl(fd,F_SETFL,FASYNC);
tcgetattr(fd,&oldtio);/*保存当前的端口设置*/
/*为标准输入处理设置新的端口设置*/
newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD;
newtio.c_iflag=IGNPAR|ICRNL;
newtio.c_oflag=0;
newtio.c_lflag=ICANON;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
tcflush(fd,TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/*当读取到输入时进行循环。
通常我们在这里会做些有用的事情*/
while(STOP==FALSE){
printf("。
\n");usleep(100000);
/*收到SIGIO信号以后,wait_flag被置为FALSE,输入操作有效并开始读取*/
if(wait_flag==FALSE){
res=read(fd,buf,255);
buf[res]=0;
printf(":
%s:
%d\n",buf,res);
if(res==1)STOP=TRUE;/*如果只有一个字符输入终止操作*/
wait_flag=TRUE;/*等待新的输入*/
}
}
/*恢复原来的设置*/
tcsetattr(fd,TCSANOW,&oldtio);
}
/***************************************************************************
*信号控制。
设置 wait_flag为FALSE以表明已经收到字符的输入。
*
***************************************************************************/
voidsignal_handler_IO(intstatus)
{
printf("receivedSIGIOsignal。
\n");
wait_flag=FALSE;
}
4.4.多方输入的等待处理
Thissectioniskepttoaminimum。
Itisjustintendedtobeahint,andthereforetheexamplecodeiskeptshort。
这种模式不仅可以适用多个串口而且对其他的设备描述字集也适用。
选择调用和伴随的宏设置通过fd_set来进行。
它是一个位数组,它对每个有效的文件描述符都有个位入口。
Select操作会接受一个afd_set对一个文件描述符进行设置并返回一个fd_set,在里面文件描述符的一些操作位会被设置,诸如输入,输出或者例外发生。
所有的fd_set处理都是通过提供的宏定义来设置的。
也请参看手册select
(2)。
#include
#include
#include
main()
{
intfd1,fd2;/*输入来源有两个:
1和2*/
fd_setreadfs;/*文件描述符设置*/
intmaxfd;/*可用的最大文件描述符*/
intloop=1;/*为TRUE时循环操作*/
/*open_input_source函数打开一个设备,正确的设置端口并返回一个文件描述符*/
fd1=open_input_source("/dev/ttyS1");/*COM2*/
if(fd1<0)exit(0);
fd2=open_input_source("/dev/ttyS2");/*COM3*/
if(fd2<0)exit(0);
maxfd=MAX(fd1,fd2)+1;/*要测试的最大位入口*/
/*循环输入*/
while(loop){
FD_SET(fd1,&readfs);/*对来源(文件描述符)