linux下的串口编程

本文转自:http://www.cnblogs.com/jason-lu/articles/3173988.html

做人个人学习使用,绝无侵权之意。如果侵权,请尽快联系,谢谢。

Linux下串口编程入门

1. 串口简介

串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用.常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准.它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定.传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺.

Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的 《Serial Programming Guide for POSIX Operating Systems》

计算机串口的引脚说明


linux下的串口编程_第1张图片

2. 串口操作

串口操作需要的头文件


linux下的串口编程_第2张图片

1#include    /*标准输入输出定义*/2#include    /*标准函数库定义*/3#include    /*Unix 标准函数定义*/4#include    5#include    6#include    /*文件控制定义*/7#include    /*PPSIX 终端控制定义*/8#include    /*错误号定义*/


3. 打开串口

Linux系统上一般有一个或者多个串口,而这些串口设备文件名字比较奇怪,如比下面这样


linux下的串口编程_第3张图片



因为串口和其他设备一样,在类Unix系统中都是以设备文件的形式存在的,所以,理所当然得你可以使用open(2)系统调用/函数来访问它.但Linux系统中却有一个稍微不方便的地方,那就是普通用户一般不能直接访问设备文件.你可以选择以下方式做一些调整,以便你编写的程序可以访问串口.

改变设备文件的访问权限设置 [#cd9bd1e0]

以root超级用户的身份运行程序 [#kdd0e577]

将你的程序编写位setuid程序,以串口设备所有者的身份运行程序 [#s7b703ff]

OK.假如你已经准备好了让串口设备文件可以被所有用户访问,你可以在Linux系统中实验一下下面这个程序,它可以打开计算机的串口1.


linux下的串口编程_第4张图片

1int fd;2/*以读写方式打开串口*/3fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);

4if(-1==fd){

5/* 不能打开串口一*/

6perror(" 提示错误!");

7}


打开串口连接的时候,程序在open函数中除了Read+Write模式以外还指定了两个选项;

标志O_NOCTTY可以告诉UNIX这个程序不会成为这个端口上的“控制终端”.如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程.而有些程序比如getty(1M/8)则会在打开登录进程的时候使用这个特性,但是通常情况下,用户程序不会使用这个行为.

O_NDELAY标志则是告诉UNIX,这个程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接.如果不指定这个标志的话,除非DCD信号线上有space电压否则这个程序会一直睡眠.

4. 设置串口

最基本的设置串口包括波特率设置,效验位和停止位设置.

很多系统都支持POSIX终端(串口)接口.程序可以利用这个接口来改变终端的参数,比如,波特率,字符大小等等.要使用这个端口的话,你必须将头文件包含到你的程序中.这个头文件中定义了终端控制结构体和POSIX控制函数.

与串口操作相关的最重要的两个POSIX函数可能就是tcgetattr(3)和tcsetattr(3).顾名思义,这两个函数分别用来取得设设置终端的属性.调用这两个函数的时候,你需要提供一个包含着所有串口选项的termios结构体,串口的设置主要是设置struct termios结构体的各成员值.


linux下的串口编程_第5张图片








通过termio结构体的c_cflag成员可以控制波特率,数据的比特数,parity,停止位和硬件流控制,下面这张表列出了所有可以使用的常数


linux下的串口编程_第6张图片


linux下的串口编程_第7张图片


linux下的串口编程_第8张图片

在传统的POSIX编程中,当连接一个本地的(不通过调制解调器)或者远程的终端(通过调制解调器)时,这里有两个选项应当一直打开,一个是CLOCAL,另一个是CREAD.这两个选项可以保证你的程序不会变成端口的所有者,而端口所有者必须去处理发散性作业控制和挂断信号,同时还保证了串行接口驱动会读取过来的数据字节.

波特率常数(CBAUD,B9600等等)通常指用到那些不支持c_ispeed和c_ospeed成员的旧的接口上.后面文章将会提到如何使用其他POSIX函数来设置波特率.

千万不要直接用使用数字来初始化c_cflag(当然还有其他标志),最好的方法是使用位运算的与或非组合来设置或者清除这个标志.不同的操作系统版本会使用不同的位模式,使用常数定义和位运算组合来避免重复工作从而提高程序的可移植性.

波特率设置

不同的操作系统会将波特率存储在不同的位置.旧的编程接口将波特率存储在上表所示的c_cflag成员中,而新的接口实装则提供了c_ispeed和c_ospeed成员来保存实际波特率的值.

程序中可是使用cfsetospeed(3)和cfsetispeed(3)函数在termios结构体中设置波特率而不用去管底层操作系统接口.下面的代码是个非常典型的设置波特率的例子.


linux下的串口编程_第9张图片

1struct termios options; 2 3/* 4 * Get the current options for the port... 5*/ 6tcgetattr(fd, &options); 7/* 8 * Set the baud rates to 19200... 9*/10cfsetispeed(&options, B19200);11cfsetospeed(&options, B19200);1213/*14 * Enable the receiver and set local mode...15*/16options.c_cflag |= (CLOCAL | CREAD);1718/*19 * Set the new options for the port...20*/21tcsetattr(fd, TCSANOW, &options);

函数tcgetattr(3)会将当前串口配置回填到termio结构体option中.然后,程序设置了输入输出的波特率并且将本地模式(CLOCAL)和串行数据接收(CREAD)设置为有效,接着将新的配置作为参数传递给函数tcsetattr(3).常量TCSANOW标志所有改变必须立刻生效而不用等到数据传输结束.其他另一些常数可以保证等待数据结束或者刷新输入输出之后再生效.

常量描述

linux下的串口编程_第10张图片





不同的系统上可能支持不同的输入输出速度,所以,通过串口连接两台机器或者设备的时候,应该将波特率设置成两者中较小的那个,即MIN(speed1, speed2).

设置字符大小

设置字符大小的时候,这里却没有像设置波特率那么方便的函数.所以,程序中需要一些位掩码运算来把事情搞定.字符大小以比特为单位指定:


1options.c_flag &= ~CSIZE;/* Mask the character size bits */2options.c_flag |= CS8;/* Select 8 data bits */

设置奇偶校验和停止位

与设置字符大小的方式差不多,这里仍然需要组合一些位掩码来将奇偶校验设为有效和奇偶校验的类型.UNIX串口驱动可以生成even,odd和no parity位码.设置space奇偶校验需要耍点小手段.


linux下的串口编程_第11张图片



linux下的串口编程_第12张图片

No parity (8N1)

1options.c_cflag &= ~PARENB2options.c_cflag &= ~CSTOPB3options.c_cflag &= ~CSIZE;4options.c_cflag |= CS8;

Even parity (7E1)

1options.c_cflag |= PARENB2options.c_cflag &= ~PARODD3options.c_cflag &= ~CSTOPB4options.c_cflag &= ~CSIZE;5options.c_cflag |= CS7;

Odd parity (7O1)

1options.c_cflag |= PARENB2options.c_cflag |= PARODD3options.c_cflag &= ~CSTOPB4options.c_cflag &= ~CSIZE;5options.c_cflag |= CS7;

Space parity is setup the same as no parity (7S1)

1options.c_cflag &= ~PARENB2options.c_cflag &= ~CSTOPB3options.c_cflag &= ~CSIZE;4options.c_cflag |= CS8;

设置效验的函数实例:


linux下的串口编程_第13张图片


linux下的串口编程_第14张图片


linux下的串口编程_第15张图片

1/** 2*@brief  设置串口数据位,停止位和效验位 3*@param  fd    类型  int  打开的串口文件句柄 4*@param  databits 类型  int 数据位  取值 为 7 或者8 5*@param  stopbits 类型  int 停止位  取值为 1 或者2 6*@param  parity  类型  int  效验类型 取值为N,E,O,,S 7*/ 8intset_Parity(intfd,intdatabits,intstopbits,int parity) 9{ 10struct termios options; 11if( tcgetattr( fd,&options)  !=0) { 12perror("SetupSerial 1");    13return(FALSE);  14    }15options.c_cflag &= ~CSIZE; 16switch(databits)/*设置数据位数*/17    {  18case7:        19options.c_cflag |= CS7; 20break;21case8:    22options.c_cflag |= CS8;23break;  24default:    25fprintf(stderr,"Unsupported data size\n");return (FALSE);  26    }27switch (parity) 28{  29case'n':30case'N':    31options.c_cflag &= ~PARENB;/* Clear parity enable */32options.c_iflag &= ~INPCK;/* Enable parity checking */33break;  34case'o':  35case'O':    36options.c_cflag |= (PARODD | PARENB);/* 设置为奇效验*/37options.c_iflag |= INPCK;/* Disnable parity checking */38break;  39case'e':  40case'E':  41options.c_cflag |= PARENB;/* Enable parity */42options.c_cflag &= ~PARODD;/* 转换为偶效验*/43options.c_iflag |= INPCK;/* Disnable parity checking */44break;45case'S': 46case's':/*as no parity*/47options.c_cflag &= ~PARENB;48options.c_cflag &= ~CSTOPB;break;  49default:  50fprintf(stderr,"Unsupported parity\n");    51return (FALSE);  52    }  53/* 设置停止位*/54switch (stopbits)55{  56case1:    57options.c_cflag &= ~CSTOPB;  58break;  59case2:    60options.c_cflag |= CSTOPB;  61break;62default:    63fprintf(stderr,"Unsupported stop bits\n");  64return (FALSE); 65} 66/* Set input parity option */67if(parity !='n')  68options.c_iflag |= INPCK; 69tcflush(fd,TCIFLUSH);70options.c_cc[VTIME] =150;/* 设置超时15 seconds*/71options.c_cc[VMIN] =0;/* Update the options and do it NOW */72if(tcsetattr(fd,TCSANOW,&options) !=0)  73{ 74perror("SetupSerial 3");  75return (FALSE);  76} 77return (TRUE);  78}


需要注意的是:

如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:


1options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);/*Input*/2options.c_oflag  &= ~OPOST;/*Output*/

设置硬件流控制

某些版本的UNIX系统支持通过CTS(Clear To Send)和RTS(Request To Send)信号线来设置硬件流控制.如果系统上定义了CNEW_RTSCTS和CRTSCTS常量,那么很可能它会支持硬件流控制.使用下面的方法将硬件流控制设置成有效:

1options.c_cflag |= CNEW_RTSCTS;/* Also called CRTSCTS

将它设置成为无效的方法与此类似:

1options.c_cflag &= ~CNEW_RTSCTS;

本地设置

本地模式成员变量c_lflag可以控制串口驱动怎样控制输入字符.通常,你可能需要通过c_lflag成员来设置经典输入和原始输入模式。


linux下的串口编程_第16张图片


成员变量c_lflag可以使用的常量

选择经典输入

经典输入是以面向行设计的.在经典输入模式中输入字符会被放入一个缓冲之中,这样可以以与用户交互的方式编辑缓冲的内容,直到收到CR(carriage return)或者LF(line feed)字符.

选择使用经典输入模式的时候,你通常需要选择ICANON,ECHO和ECHOE选项:

1options.c_lflag |= (ICANON | ECHO | ECHOE);

选择原始输入

原始输入根本不会被处理.输入字符只是被原封不动的接收.一般情况中,如果要使用原始输入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG选项:

1options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

输入选项

可以通过输入模式成员c_iflag来控制从端口上收到的字符的输入过程.与c_cflag一样,c_iflag的最终值是想要使用的所有状态的位运算OR的组合.


linux下的串口编程_第17张图片

c_iflag成员可以使用的常量

设置输入奇偶校验选项

当程序在c_cflag中设置了奇偶校验成员(PARENB)的时候,程序就需要将输入奇偶校验设置成为有效.与奇偶校验相关的常量有INPCK,IGNPAR,PARMRK和ISTRIP.一般情况下,你可能需要选择INPCK和ISTRIP将奇偶校验设置为有效同时从接收字串中脱去奇偶校验位:

1options.c_iflag |= (INPCK | ISTRIP);

IGNPAR是一个比较危险选项,即便有错误发生时,它也会告诉串口驱动直接忽略奇偶校验错误给数据放行.这个选项在测试链接的通讯质量时比较有用而通常不会被用在实际程序中.

PARMRK会导致奇偶校验错误被标志成特殊字符加入到输入流之中.如果IGNPAR选项也是有效的,那么一个NUL(八进制000)字符会被加入到发生奇偶校验错误的字符前面.否则,DEL(八进制177)和NUL字符会和出错的字符一起送出.

设置软件流控制

软件流控制可以通过IXON,IXOFF和IXANY常量设置成有效:

1options.c_iflag |= (IXON | IXOFF | IXANY);

将其设置为无效的时候,很简单,只需要对这些位取反:

1options.c_iflag &= ~(IXON | IXOFF | IXANY);

XON(start data)和XOFF(stop data)字符却是在c_cc数组中定义的,下面会详细描述这个数组.

输出选项

成员变量c_oflag之中包括了输出过滤选项.和输入模式相似,程序可以选择使用经过加工的或者原始的数据输出.


linux下的串口编程_第18张图片


linux下的串口编程_第19张图片

c_oflag成员的常量

选择加工过的输出

通过在c_oflag成员变量中设置OPOST选项的方法程序可以选择加工过的输入.

1options.c_oflag |= OPOST;

在所有选项当中,你可能只需要使用ONLCR选项来将行分隔符映射到CR-LF组合对上.其他选项主要是历史遗留,仅仅与行打印机和终端跟不上串行数据的年代有关.

选择原始输出

原始输出方式可以通过在c_oflag中重置OPOST选项来选择:

1options.c_oflag &= ~OPOST;

如果OPOST选项被设置成无效的话,其他c_oflag中的选项都会失效.

控制字符

字符数组c_cc里面包括了控制字符的定义和超时参数.这个数组的每个元素都是以常量定义的.

常量描述键


linux下的串口编程_第20张图片



成员变量c_cc中的控制字符

设置软件流控制字符

用来做软件流控制的字符包含在数组c_cc的VSTART和VSTOP元素里面.通常情况下,它们应该被设置成DC1(八进制021)和DC3(八进制023),它们在ASCII标准中代表着XON和XOFF字符.

设置读取超时

UNIX串口驱动提供了设置字符和包超时的能力.数组c_cc中有两个元素可以用来设置超时:VMIN和VTIME.在经典输入模式或者通过open(2)和fcntl(2)函数传递NDELAY选项时,超时设置会被忽略.

VMIN可以指定读取的最小字符数.如果它被设置为0,那么VTIME值则会指定每个字符读取的等待时间.

如果VMIN不为零,VTIME会指定等待第一个字符读取操作的时间.如果在这个指定时间中可以开始读取某个字符,直到VMIN个数的所有字符全部被读取,其他读取操作将会被阻塞(等待).也就是说,一旦读取第一个字符,串口驱动的预期就是接收到整个字符包(一共VMIN字节).如果在允许的时间内没有字符被读取,那么read(2)调用就会返回0.通过这个方法可以确切得告诉串口驱动程序需要读取N个字节,而且read(2)调用只会返回N或者0.然而,超时设置只对第一个字符的读取操作有效,所以,如果因为某些原因驱动程序在N字节的包中丢失某个字符的话,read(2)调用将会一直等下去.

VTIME可以以十分之一秒为单位指定等待字符输入的时间.如果VTIME设置为0(默认情况),除非open(2)或者fcntl(2)函数设置了NDELAY选项,否则read(2)将会永久得阻塞(等待).

5. 读写串口

设置好串口之后,读写串口就很容易了,把串口当作文件读写就是.

发送数据

1charbuffer[1024];intLength;intnByte;nByte = write(fd, buffer ,Length)

和写入其他设备文件的方式相同,write函数也会返回发送数据的字节数或者在发生错误的时候返回-1.通常,发送数据最常见的错误就是EIO,当调制解调器或者数据链路将Data Carrier Detect(DCD)信号线弄掉了,就会发生这个错误.而且,直至关闭端口这个情况会一直持续.

读取串口数据

使用文件操作read函数读取,如果设置为原始数据模式(Raw Date Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数,也就是返回从串口输入缓冲区中实际得到的字符的个数.在不能得到数据的情况下,read(2)系统调用就会一直等着,只到有端口上新的字符可以读取或者发生超时或者错误的情况发生.

1charbuff[1024];intLen;intreadByte = read(fd,buff,Len);

如果需要read(2)函数迅速返回的话,可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作:

1fcntl(fd, F_SETFL, FNDELAY);

标志FNDELAY可以保证read(2)函数在端口上读不到字符的时候返回0.需要回到正常(阻塞)模式的时候,需要再次在不带FNDELAY标志的情况下调用fcntl(2)函数:

1fcntl(fd, F_SETFL,0);

当然,如果你最初就是以O_NDELAY标志打开串口的,你也可在之后使用这个方法改变读取的行为方式.

6. 关闭串口

关闭串口就是关闭文件.

1close(fd);

关闭串口会将DTR信号线设置成low,这会导致很多调制解调器挂起.

7. 例子

下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件.


linux下的串口编程_第21张图片


linux下的串口编程_第22张图片

1/********************************************************************** 2代码说明:使用串口二测试的,发送的数据是字符, 3但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。 4我测试使用的是单片机发送数据到第二个串口,测试通过。 5**********************************************************************/ 6#defineFALSE  -1 7#defineTRUE  0 8/*********************************************************************/ 9intOpenDev(char*Dev)10{11intfd = open( Dev, O_RDWR );//| O_NOCTTY | O_NDELAY    12if(-1== fd)    13    {            14perror("Can't Open Serial Port");15return-1;        16    }    17else18return fd;19}20intmain(intargc,char**argv){21int fd;22int nread;23charbuff[512];24char*dev  ="/dev/ttyS1";//串口二25fd = OpenDev(dev);26set_speed(fd,19200);27if(set_Parity(fd,8,1,'N') == FALSE)  {28printf("Set Parity Error\n");29exit (0);30    }31while(1)//循环读取数据32{  33while((nread = read(fd, buff,512))>0)34    { 35printf("\nLen %d\n",nread); 36buff[nread+1] ='\0';  37printf("\n%s", buff);  38    }39}40//close(fd);  41// exit (0);42}

本文转自:http://www.ibm.com/developerworks/cn/linux/l-serials/

你可能感兴趣的:(linux下的串口编程)