浅谈linux下的串口通讯开发
串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是RS-232-C接口(又称EIARS-232-C)它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。串口通讯指的是计算机依次以位(bit)为单位来传送数据,串行通讯使用的范围很广,在嵌入式系统开发过程中串口通讯也经常用到通讯方式之一。
Linux对所有设备的访问是通过设备文件来进行的,串口也是这样,为了访问串口,只需打开其设备文件即可操作串口设备。在linux系统下面,每一个串口设备都有设备文件与其关联,设备文件位于系统的/dev目录下面。如linux下的/ttyS0,/ttyS1分别表示的是串口1和串口2。下面来详细介绍linux下是如何使用串口的:
1.串口操作需要用到的头文件
#include/*标准输入输出定义*/
#include/*标准函数库定义*/
#include/*Unix标准函数定义*/
#include
#include
#include/*文件控制定义*/
#include/*POSIX终端控制定义*/
#include/*错误号定义*/
#include/*字符串功能函数*/
2.串口通讯波特率设置
波特率的设置定义在,其包含在头文件里。
常用的波特率常数如下:
B0-------à0B1800-------à1800
B50-----à50B2400------à2400
B75-----à75B4800------à4800
B110----à110B9600------à9600
B134----à134.5B19200-----à19200
B200----à200B38400------à38400
B300----à300B57600------à57600
B600----à600B76800------à76800
B1200---à1200B115200-----à115200
假定程序中想要设置通讯的波特率,使用cfsetispeed()和cfsetospeed()函数来操作,获取波特率信息是通过cfgetispeed()和cfgetospeed()函数来完成的。比如可以这样来指定串口通讯的波特率:
#include//头文件定义 ........ ........ ....... structtermiosopt;/*定义指向termios结构类型的指针opt*/ /***************以下设置通讯波特率****************/ cfsetispeed(&opt,B9600);/*指定输入波特率,9600bps*/ cfsetospeed(&opt,B9600);/*指定输出波特率,9600bps*/ /************************************************/ ......... ..........
一般来说,输入、输出的波特率应该是一致的。
3.串口属性配置
在程序中,很容易配置串口的属性,这些属性定义在结构体structtermios中。为在程序中使用该结构体,需要包含文件,该头文件定义了结构体structtermios。
该结构体定义如下:
#defineNCCS19 structtermios{ tcflag_tc_iflag;/*输入参数*/ tcflag_tc_oflag;/*输出参数*/ tcflag_tc_cflag;/*控制参数*/ tcflag_tc_ispeed;/*输入波特率*/ tcflag_tc_ospeed;/*输出波特率*/ cc_tc_line;/*线控制*/ cc_tc_cc[NCCS];/*控制字符*/ };
其中成员c_line在POSIX(PortableOperatingSystemInterfaceforUNIX)系统中不使用。对于支持POSIX终端接口的系统中,对于端口属性的设置和获取要用到两个重要的函数是:
(1).inttcsetattr(intfd,intopt_DE,*ptr)
该函数用来设置终端控制属性,其参数说明如下:
lfd:待操作的文件描述符
lopt_DE:选项值,有三个选项以供选择:
TCSANOW:不等数据传输完毕就立即改变属性
TCSADRAIN:等待所有数据传输结束才改变属性
TCSAFLUSH:清空输入输出缓冲区才改变属性
l*ptr:指向termios结构的指针
函数返回值:成功返回0,失败返回-1。
(2).inttcgetattr(intfd,*ptr)
该函数用来获取终端控制属性,它把串口的默认设置赋给了termios数据数据结构,其参数说明如下:
lfd:待操作的文件描述符
l*ptr:指向termios结构的指针
函数返回值:成功返回0,失败返回-1。
4.打开串口
在前面已经提到linux下的串口访问是以设备文件形式进行的,所以打开串口也即是打开文件的操作。函数原型可以如下所示:
intopen(“DE_name”,intopen_Status)
参数说明:
(1).DE_name:要打开的设备文件名
比如要打开串口1,即为/dev/ttyS0。
(2).open_Status:文件打开方式,可采用下面的文件打开模式:
lO_RDONLY:以只读方式打开文件
lO_WRONLY:以只写方式打开文件
lO_RDWR:以读写方式打开文件
lO_APPEND:写入数据时添加到文件末尾
lO_CREATE:如果文件不存在则产生该文件,使用该标志需要设置访问权限位mode_t
lO_EXCL:指定该标志,并且指定了O_CREATE标志,如果打开的文件存在则会产生一个错误
lO_TRUNC:如果文件存在并且成功以写或者只写方式打开,则清除文件所有内容,使得文件长度变为0
lO_NOCTTY:如果打开的是一个终端设备,这个程序不会成为对应这个端口的控制终端,如果没有该标志,任何一个输入,例如键盘中止信号等,都将影响进程。
lO_NONBLOCK:该标志与早期使用的O_NDELAY标志作用差不多。程序不关心DCD信号线的状态,如果指定该标志,进程将一直在休眠状态,直到DCD信号线为0。
函数返回值:
成功返回文件描述符,如果失败返回-1
例如假定以可读写方式打开/dev/ttyS0设备,就可以这样操作:
#include//头文件包含 ...... ...... intfd;/*文件描述符*/ fd=open("/dev/ttyS0",O_RDWR|0_NOCTTY);/*以读写方式打开设备*/ if(fd==-1) perror("CannotopenSerial_Port1/n!");/*打开失败时的错误提示*/ ........ ........
5.串口读操作(接收端)
用open函数打开设备文件,函数返回一个文件描述符(filedescriptors,fd),通过文件描述符来访问文件。读串口操作是通过read函数来完成的。函数原型如下:
intread(intfd,*buffer,length);
参数说明:
(1).intfd:文件描述符
(2).*buffer:数据缓冲区
(3).length:要读取的字节数
函数返回值:
读操作成功读取返回读取的字节数,失败则返回-1。
6.串口写操作(发送端)
写串口操作是通过write函数来完成的。函数原型如下:
write(intfd,*buffer,length);
参数说明:
(1).fd:文件描述符
(2).*buffer:存储写入数据的数据缓冲区
(3).length:写入缓冲去的数据字节数
函数返回值:
成功返回写入数据的字节数,该值通常等于length,如果写入失败返回-1。
例如:向终端设备发送初始化命令
#include//头文件包含 ...... ...... intn sbuf[]={Hello,thisisaSerial_Porttest!/n};//待发送数据 intlen_send="sizeof"(sbuf);//发送缓冲区字节数定义 n=write(fd,sbuf,len_send);//写缓冲区 if(n==-1) { printf("Wirtesbuferror./n"); } ...... ......
7.关闭串口
对设备文件的操作与对普通文件的操作一样,打开操作之后还需要关闭,关闭串口用函数close()来操作,函数原型为:
intclose(intfd);
参数说明:
fd:文件描述符
函数返回值:
成功返回0,失败返回-1。
NAME
termios,tcgetattr,tcsetattr,tcsendbreak,tcdrain,tcflush,tcflow,cfmakeraw,cfgetospeed,cfgetispeed,cfsetispeed,cfsetospeed-获取和设置终端属性,行控制,获取和设置波特率
SYNOPSIS总览
#include<termios.h>
#include<unistd.h>
inttcgetattr(intfd,structtermios*termios_p);
inttcsetattr(intfd,intoptional_actions,structtermios*termios_p);
inttcsendbreak(intfd,intduration);
inttcdrain(intfd);
inttcflush(intfd,intqueue_selector);
inttcflow(intfd,intaction);
intcfmakeraw(structtermios*termios_p);
speed_tcfgetispeed(structtermios*termios_p);
speed_tcfgetospeed(structtermios*termios_p);
intcfsetispeed(structtermios*termios_p,speed_tspeed);
intcfsetospeed(structtermios*termios_p,speed_tspeed);
DESCRIPTION描述
termios函数族提供了一个常规的终端接口,用于控制非同步通信端口。
这里描述的大部分属性有一个termios_p类型的参数,它是指向一个termios结构的指针。这个结构包含了至少下列成员:
tcflag_tc_iflag; /*输入模式*/
tcflag_tc_oflag; /*输出模式*/
tcflag_tc_cflag; /*控制模式*/
tcflag_tc_lflag; /*本地模式*/
cc_tc_cc[NCCS]; /*控制字符*/
c_iflag标志常量:
IGNBRK
忽略输入中的BREAK状态。
BRKINT
如果设置了IGNBRK,将忽略BREAK。如果没有设置,但是设置了BRKINT,那么BREAK将使得输入和输出队列被刷新,如果终端是一个前台进程组的控制终端,这个进程组中所有进程将收到SIGINT信号。如果既未设置IGNBRK也未设置BRKINT,BREAK将视为与NUL字符同义,除非设置了PARMRK,这种情况下它被视为序列/377/0/0。
IGNPAR
忽略桢错误和奇偶校验错。
PARMRK
如果没有设置IGNPAR,在有奇偶校验错或桢错误的字符前插入/377/0。如果既没有设置IGNPAR也没有设置PARMRK,将有奇偶校验错或桢错误的字符视为/0。
INPCK
启用输入奇偶检测。
ISTRIP
去掉第八位。
INLCR
将输入中的NL翻译为CR。
IGNCR
忽略输入中的回车。
ICRNL
将输入中的回车翻译为新行(除非设置了IGNCR)。
IUCLC
(不属于POSIX)将输入中的大写字母映射为小写字母。
IXON
启用输出的XON/XOFF流控制。
IXANY
(不属于POSIX.1;XSI)允许任何字符来重新开始输出。(?)
IXOFF
启用输入的XON/XOFF流控制。
IMAXBEL
(不属于POSIX)当输入队列满时响零。Linux没有实现这一位,总是将它视为已设置。
POSIX.1中定义的c_oflag标志常量:
OPOST
启用具体实现自行定义的输出处理。
其余c_oflag标志常量定义在POSIX1003.1-2001中,除非另外说明。
OLCUC
(不属于POSIX)将输出中的小写字母映射为大写字母。
ONLCR
(XSI)将输出中的新行符映射为回车-换行。
OCRNL
将输出中的回车映射为新行符
ONOCR
不在第0列输出回车。
ONLRET
不输出回车。
OFILL
发送填充字符作为延时,而不是使用定时来延时。
OFDEL
(不属于POSIX)填充字符是ASCIIDEL(0177)。如果不设置,填充字符则是ASCIINUL。
NLDLY
新行延时掩码。取值为NL0和NL1。
CRDLY
回车延时掩码。取值为CR0,CR1,CR2,或CR3。
TABDLY
水平跳格延时掩码。取值为TAB0,TAB1,TAB2,TAB3(或XTABS)。取值为TAB3,即XTABS,将扩展跳格为空格(每个跳格符填充8个空格)。(?)
BSDLY
回退延时掩码。取值为BS0或BS1。(从来没有被实现过)
VTDLY
竖直跳格延时掩码。取值为VT0或VT1。
FFDLY
进表延时掩码。取值为FF0或FF1。
c_cflag标志常量:
CBAUD
(不属于POSIX)波特率掩码(4+1位)。
CBAUDEX
(不属于POSIX)扩展的波特率掩码(1位),包含在CBAUD中。
(POSIX规定波特率存储在termios结构中,并未精确指定它的位置,而是提供了函数cfgetispeed()和cfsetispeed()来存取它。一些系统使用c_cflag中CBAUD选择的位,其他系统使用单独的变量,例如sg_ispeed和sg_ospeed。)
CSIZE
字符长度掩码。取值为CS5,CS6,CS7,或CS8。
CSTOPB
设置两个停止位,而不是一个。
CREAD
打开接受者。
PARENB
允许输出产生奇偶信息以及输入的奇偶校验。
PARODD
输入和输出是奇校验。
HUPCL
在最后一个进程关闭设备后,降低modem控制线(挂断)。(?)
CLOCAL
忽略modem控制线。
LOBLK
(不属于POSIX)从非当前shell层阻塞输出(用于shl)。(?)
CIBAUD
(不属于POSIX)输入速度的掩码。CIBAUD各位的值与CBAUD各位相同,左移了IBSHIFT位。
CRTSCTS
(不属于POSIX)启用RTS/CTS(硬件)流控制。
c_lflag标志常量:
ISIG
当接受到字符INTR,QUIT,SUSP,或DSUSP时,产生相应的信号。
ICANON
启用标准模式(canonicalmode)。允许使用特殊字符EOF,EOL,EOL2,ERASE,KILL,LNEXT,REPRINT,STATUS,和WERASE,以及按行的缓冲。
XCASE
(不属于POSIX;Linux下不被支持)如果同时设置了ICANON,终端只有大写。输入被转换为小写,除了以/前缀的字符。输出时,大写字符被前缀/,小写字符被转换成大写。
ECHO
回显输入字符。
ECHOE
如果同时设置了ICANON,字符ERASE擦除前一个输入字符,WERASE擦除前一个词。
ECHOK
如果同时设置了ICANON,字符KILL删除当前行。
ECHONL
如果同时设置了ICANON,回显字符NL,即使没有设置ECHO。
ECHOCTL
(不属于POSIX)如果同时设置了ECHO,除了TAB,NL,START,和STOP之外的ASCII控制信号被回显为^X,这里X是比控制信号大0x40的ASCII码。例如,字符0x08(BS)被回显为^H。
ECHOPRT
(不属于POSIX)如果同时设置了ICANON和IECHO,字符在删除的同时被打印。
ECHOKE
(不属于POSIX)如果同时设置了ICANON,回显KILL时将删除一行中的每个字符,如同指定了ECHOE和ECHOPRT一样。
DEFECHO
(不属于POSIX)只在一个进程读的时候回显。
FLUSHO
(不属于POSIX;Linux下不被支持)输出被刷新。这个标志可以通过键入字符DISCARD来开关。
NOFLSH
禁止在产生SIGINT,SIGQUIT和SIGSUSP信号时刷新输入和输出队列。
TOSTOP
向试图写控制终端的后台进程组发送SIGTTOU信号。
PENDIN
(不属于POSIX;Linux下不被支持)在读入下一个字符时,输入队列中所有字符被重新输出。(bash用它来处理typeahead)
IEXTEN
启用实现自定义的输入处理。这个标志必须与ICANON同时使用,才能解释特殊字符EOL2,LNEXT,REPRINT和WERASE,IUCLC标志才有效。
c_cc数组定义了特殊的控制字符。符号下标(初始值)和意义为:
VINTR
(003,ETX,Ctrl-C,oralso0177,DEL,rubout)中断字符。发出SIGINT信号。当设置ISIG时可被识别,不再作为输入传递。
VQUIT
(034,FS,Ctrl-/)退出字符。发出SIGQUIT信号。当设置ISIG时可被识别,不再作为输入传递。
VERASE
(0177,DEL,rubout,or010,BS,Ctrl-H,oralso#)删除字符。删除上一个还没有删掉的字符,但不删除上一个EOF或行首。当设置ICANON时可被识别,不再作为输入传递。
VKILL
(025,NAK,Ctrl-U,orCtrl-X,oralso@)终止字符。删除自上一个EOF或行首以来的输入。当设置ICANON时可被识别,不再作为输入传递。
VEOF
(004,EOT,Ctrl-D)文件尾字符。更精确地说,这个字符使得tty缓冲中的内容被送到等待输入的用户程序中,而不必等到EOL。如果它是一行的第一个字符,那么用户程序的read()将返回0,指示读到了EOF。当设置ICANON时可被识别,不再作为输入传递。
VMIN
非canonical模式读的最小字符数。
VEOL
(0,NUL)附加的行尾字符。当设置ICANON时可被识别。
VTIME
非canonical模式读时的延时,以十分之一秒为单位。
VEOL2
(notinPOSIX;0,NUL)另一个行尾字符。当设置ICANON时可被识别。
VSWTCH
(notinPOSIX;notsupportedunderLinux;0,NUL)开关字符。(只为shl所用。)
VSTART
(021,DC1,Ctrl-Q)开始字符。重新开始被Stop字符中止的输出。当设置IXON时可被识别,不再作为输入传递。
VSTOP
(023,DC3,Ctrl-S)停止字符。停止输出,直到键入Start字符。当设置IXON时可被识别,不再作为输入传递。
VSUSP
(032,SUB,Ctrl-Z)挂起字符。发送SIGTSTP信号。当设置ISIG时可被识别,不再作为输入传递。
VDSUSP
(notinPOSIX;notsupportedunderLinux;031,EM,Ctrl-Y)延时挂起信号。当用户程序读到这个字符时,发送SIGTSTP信号。当设置IEXTEN和ISIG,并且系统支持作业管理时可被识别,不再作为输入传递。
VLNEXT
(notinPOSIX;026,SYN,Ctrl-V)字面上的下一个。引用下一个输入字符,取消它的任何特殊含义。当设置IEXTEN时可被识别,不再作为输入传递。
VWERASE
(notinPOSIX;027,ETB,Ctrl-W)删除词。当设置ICANON和IEXTEN时可被识别,不再作为输入传递。
VREPRINT
(notinPOSIX;022,DC2,Ctrl-R)重新输出未读的字符。当设置ICANON和IEXTEN时可被识别,不再作为输入传递。
VDISCARD
(notinPOSIX;notsupportedunderLinux;017,SI,Ctrl-O)开关:开始/结束丢弃未完成的输出。当设置IEXTEN时可被识别,不再作为输入传递。
VSTATUS
(notinPOSIX;notsupportedunderLinux;statusrequest:024,DC4,Ctrl-T).
这些符号下标值是互不相同的,除了VTIME,VMIN的值可能分别与VEOL,VEOF相同。(在non-canonical模式下,特殊字符的含义更改为延时含义。MIN表示应当被读入的最小字符数。TIME是以十分之一秒为单位的计时器。如果同时设置了它们,read将等待直到至少读入一个字符,一旦读入MIN个字符或者从上次读入字符开始经过了TIME时间就立即返回。如果只设置了MIN,read在读入MIN个字符之前不会返回。如果只设置了TIME,read将在至少读入一个字符,或者计时器超时的时候立即返回。如果都没有设置,read将立即返回,只给出当前准备好的字符。)(?)
tcgetattr()得到与fd指向的对象相关的参数,将它们保存于termios_p引用的termios结构中。函数可以从后台进程中调用;但是,终端属性可能被后来的前台进程所改变。
tcsetattr()设置与终端相关的参数(除非需要底层支持却无法满足),使用termios_p引用的termios结构。optional_actions指定了什么时候改变会起作用:
TCSANOW
改变立即发生
TCSADRAIN
改变在所有写入fd的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。
TCSAFLUSH
改变在所有写入fd引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃。
tcsendbreak()传送连续的0值比特流,持续一段时间,如果终端使用异步串行数据传输的话。如果duration是0,它至少传输0.25秒,不会超过0.5秒。如果duration非零,它发送的时间长度由实现定义。
如果终端并非使用异步串行数据传输,tcsendbreak()什么都不做。
tcdrain()等待直到所有写入fd引用的对象的输出都被传输。
tcflush()丢弃要写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据,取决于queue_selector的值:
TCIFLUSH
刷新收到的数据但是不读
TCOFLUSH
刷新写入的数据但是不传送
TCIOFLUSH
同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送
tcflow()挂起fd引用的对象上的数据传输或接收,取决于action的值:
TCOOFF
挂起输出
TCOON
重新开始被挂起的输出
TCIOFF
发送一个STOP字符,停止终端设备向系统传送数据
TCION
发送一个START字符,使终端设备向系统传输数据
打开一个终端设备时的默认设置是输入和输出都没有挂起。
波特率函数被用来获取和设置termios结构中,输入和输出波特率的值。新值不会马上生效,直到成功调用了tcsetattr()函数。
设置速度为B0使得modem"挂机"。与B38400相应的实际比特率可以用setserial(8)调整。
输入和输出波特率被保存于termios结构中。
cfmakeraw设置终端属性如下:
termios_p->c_iflag&=~(IGNBRK|BRKINT|PARMRK|ISTRIP
|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag&=~OPOST;
termios_p->c_lflag&=~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag&=~(CSIZE|PARENB);
termios_p->c_cflag|=CS8;
cfgetospeed()返回termios_p指向的termios结构中存储的输出波特率
cfsetospeed()设置termios_p指向的termios结构中存储的输出波特率为speed。取值必须是以下常量之一:
B0
B50
B75
B110
B134
B150
B200
B300
B600
B1200
B1800
B2400
B4800
B9600
B19200
B38400
B57600
B115200
B230400
零值B0用来中断连接。如果指定了B0,不应当再假定存在连接。通常,这样将断开连接。CBAUDEX是一个掩码,指示高于POSIX.1定义的速度的那一些(57600及以上)。因此,B57600&CBAUDEX为非零。
cfgetispeed()返回termios结构中存储的输入波特率。
cfsetispeed()设置termios结构中存储的输入波特率为speed。如果输入波特率被设为0,实际输入波特率将等于输出波特率。
RETURNVALUE返回值
cfgetispeed()返回termios结构中存储的输入波特率。
cfgetospeed()返回termios结构中存储的输出波特率。
其他函数返回:
0
成功
-1
失败,并且为errno置值来指示错误。
注意tcsetattr()返回成功,如果任何所要求的修改可以实现的话。因此,当进行多重修改时,应当在这个函数之后再次调用tcgetattr()来检测是否所有修改都成功实现。
NOTES注意
UnixV7以及很多后来的系统有一个波特率的列表,在十四个值B0,...,B9600之后可以看到两个常数EXTA,EXTB("ExternalA"and"ExternalB")。很多系统将这个列表扩展为更高的波特率。
tcsendbreak中非零的duration有不同的效果。SunOS指定中断duration*N秒,其中N至少为0.25,不高于0.5。Linux,AIX,DU,Tru64发送duration微秒的break。FreeBSD,NetBSD,HP-UX以及MacOS忽略duration的值。在Solaris和Unixware中,tcsendbreak搭配非零的duration效果类似于tcdrain。
所有的范例来源自miniterm.c.Thetypeahead暂存器被限制在255个字元,就跟标准输入程序的最大字串长度相同(或).
参考程序码中的注解它会解释不同输入模式的使用.我希望这些程序码都能被了解.标准输入程序的程序范例的注解写得最好,其它的范例都只在不同于其它范例的地方做注解.
叙述不是很完整,但可以激励你对这范例做实验,以延生出合于你所需应用程序的最佳解.
别忘记要把序列埠的权限设定正确(也就是:chmoda+rw/dev/ttyS1)!
3.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:设定bps的速度.你也可以用cfsetispeed及cfsetospeed来设定. CRTSCTS:输出资料的硬件流量控制(只能在具完整线路的缆线下工作 参考Serial-HOWTO第七节) CS8:8n1(8位元,不做同位元检查,1个终止位元) CLOCAL:本地连线,不具数据机控制功能 CREAD:致能接收字元 */ newtio.c_cflag=BAUDRATE|CRTSCTS|CS8|CLOCAL|CREAD; /* IGNPAR:忽略经同位元检查后,错误的位元组 ICRNL:比CR对应成NL(否则当输入信号有CR时不会终止输入) 在不然把装置设定成raw模式(没有其它的输入处理) */ newtio.c_iflag=IGNPAR|ICRNL; /* Raw模式输出. */ 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;/*在读取到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*/ printf(":%s:%d/n",buf,res); if(buf[0]=='z')STOP=TRUE; } /*回存旧的序列埠设定值*/ tcsetattr(fd,TCSANOW,&oldtio); }
3.2非标准输入程序
在非标准的输入程序模式下,输入的资料不会被组合成一行而输入后的处理功能(清除,杀掉,删除,等等.)都不能使用.这个模式有两个功能控制参数:c_cc[VTIME]设定字元输入时间计时器,及c_cc[VMIN]设定满足读取功能的最低字元接收个数.
如果MIN>0且TIME=0,MIN设定为满足读取功能的最低字元接收个数.由于TIME是零,所以计时器将不被使用.
如果MIN=0且TIME>0,TIME将被当做逾时设定值.满足读取功能的情况为读取到单一字元,或者超过TIME所定义的时间(t=TIME*0.1s).如果超过TIME所定义的时间,则不会传回任何字元.
如果MIN>0且TIME>0,TIME将被当做一个分割字元组的计时器.满足读取功能的条件为接收到MIN个数的字元,或两个字元的间隔时间超过TIME所定义的值.计时器会在每读到一个字元后重新计时,且只会在第一个字元收到后才会启动.
如果MIN=0且TIME=0,读取功能就马上被满足.目前所存在的字元组个数,或者将回传的字元组个数.根据Antonino(参考贡献)所说,你可以用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; /*设定输入模式(非标准型,不回应,...)*/ newtio.c_lflag=0; newtio.c_cc[VTIME]=0;/*不使用分割字元组计时器*/ newtio.c_cc[VMIN]=5;/*在读取到5个字元前先停止*/ tcflush(fd,TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); while(STOP==FALSE){/*输入回圈*/ res=read(fd,buf,255);/*在输入5个字元后即返回*/ buf[res]=0;/*所以我们能用printf...*/ 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;/*definitionofsignalaction*/ charbuf[255]; /*开启装置为non-blocking(读取功能会马上结束返回)*/ 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()); /*使文档akethefiledescriptor非同步(使用手册上说只有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;/*如果只输入CR则停止回圈*/ wait_flag=TRUE;/*等待新的输入信号*/ } } /*回存旧的序列埠设定值*/ tcsetattr(fd,TCSANOW,&oldtio); } /*************************************************************************** *信号处理程序.设定wait_flag为FALSE,以使上述的回圈能接收字元* ***************************************************************************/ voidsignal_handler_IO(intstatus) { printf("receivedSIGIOsignal./n"); wait_flag=FALSE; }
3.4等待来自多个信号来源的输入
这一段很短.它只能被拿来当成写程序时的提示,故范例程序也很简短.但这个范例不只能用在序列埠上,还可以用在被当成文档来使用的装置上.
select呼叫及伴随它所引发的巨集共用fd_set.fd_set则是一个位元阵列,而其中每一个位元代表一个有效的文档叙述结构.select呼叫接受一个有效的文档叙述结构并传回fd_set位元阵列,而该位元阵列中若有某一个位元为1,就表示相对映的文档叙述结构的文档发生了输入,输出或有例外事件.而这些巨集提供了所有处理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;/*测试最大位元输入(fd)*/ /*输入回圈*/ while(loop){ FD_SET(fd1,&readfs);/*测试输入源1*/ FD_SET(fd2,&readfs);/*测试输入源2*/ /*blockuntilinputbecomesavailable*/ select(maxfd,&readfs,NULL,NULL,NULL); if(FD_ISSET(fd1))/*如果输入源1有信号*/ handle_input_from_source1(); if(FD_ISSET(fd2))/*如果输入源2有信号*/ handle_input_from_source2(); } }
这个范例程序在等待输入信号出现前,不能确定它会停顿下来.如果你需要在输入时加入逾时功能,只需把select呼叫换成:
intres; structtimevalTimeout; /*设定输入回圈的逾时值*/ Timeout.tv_usec=0;/*毫秒*/ Timeout.tv_sec=1;/*秒*/ res=select(maxfd,&readfs,NULL,NULL,&Timeout); if(res==0) /*文档叙述结构数在input=0时,会发生输入逾时.*/
这个程序会在1秒钟后逾时.如果超过时间,select会传回0,但是应该留意Timeout的时间递减是由select所等待输入信号的时间为基准.如果逾时的值是0,select会马上结束返回.
Linux环境下使用RS-232接口
RS是英文"推荐标准"的缩写
232为标识号
RS-485
串口通信表示计算机一次传送一个位的数据,
当使用串行通信时,每个字的数据是一个位一个位的传输或接收的,
每个位不是高电平,就是低电平.
串行通信的速率通常是使用"位/每秒"的方式来表示的,即波特率。
全双工--计算机可以同时收发数据,
它有两个独立的数据通道,一个输入,一个输出,
半双工意味着计算机不能同时收发信息,
只能有一人通道进行通信.
流控:
通常,当数据在两个串行接口之间进行传输时需要对其进行控制.
这通常依赖于串行通信连接的各种规定,
对异步数据传输的控制有两种方法.
一种叫:“软件”流控。
一种叫:“硬件"流控。
串口设备:
打开一个串行口
#include #include #include #include//文件控制定义 #include #include//POSIX终端控制定义 /* *open_port()--打开串行口 * *成功的话,返回文件描述符,错误则返回-1. */ intopen_port(void) { intfd; fd=open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY); if(fd==-1) { /*无法打开串口*/ perror("open_port:Unabletoopen/dev/ttyS0"); } else fcntl(fd,F_SETFL,0); return(fd); } //O_NOCTTY标志,该程序不想成为此端口的“控制终端"。如果没有强调这一点, //O_NDELAY标志,标志告诉Linux,该程序并不关注DCD信叼线所处的状态,即不管另外一端的设备是在运行还是被挂起。如果没有指定该标志,那么程序就会被设置睡眠状态,
(2)向端口写数据
向端口写数据是很容易的,只要使用write()系统调用就可以了。
例如:
n=write(fd,"ATZ/r",4); if(n<0) fputs("write()of4bytesfailed!/n",stderr);
write函数返回发送数据的个数,如果出现错误,则返回-1。
(3)读端口数据
从端口读数据则需要些技巧。如果在原始数据的模式下对端口进行操作,read()系统调用将返回串行口输入缓冲区中所有的字符数据,不管有多少,如果没有数据,那么该调用将被阻塞.处于等待状态,直到有字符输入,
或者到了规定的时限和出现错误为止,通过以下方法,能使read函数立即返回。
fcntl(fd,F_SETFL,FNDELAY);
FNDELAY函数使read函数在端口没月字符存在的情况下,立刻返回0,
如果要恢复正常(阻塞)状态,可以调用fcntl()函数,不要FNDELAY参数,
如下所示:
fcntl(Fd,F_SETFL,0);
在使用O_NDELAY参数打开串行口后,同样与使用了该函数调用。
fcntl(fd,F_SETFL,0);
POSIX终端接口串口,波特率,字符大小等, POSIX函数是tcgetattr()和tcsetattr()获取和设置终端的属性,可以提供structruretermios的指针
以上就是小编为大家带来的浅谈linux下的串口通讯开发全部内容了,希望大家多多支持毛票票~