linux下串口通信主要步骤

串口通信流程图

下面我会一一介绍这几个步骤。

1.打开串口


代码(串口为ttyUSB0)

//打开串口
int open_port(void)
{
    int fd;
     

  //O_NONBLOCK设置为非阻塞模式,在read时不会阻塞住,在读的时候将read放在while循环中,下一节篇文档将详细讲解阻塞和非阻塞
    fd=open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NONBLOCK);
//    printf("fd=%d\n",fd);
    
    if(fd==-1)
    {
        perror("Can't Open SerialPort");
    }
    
    return fd;
}
打开串口时也可以多加一些内容,比如判断串口为阻塞状态、测试是否为终端设备等,这些是必要的,所以较上面的基本的打开串口的代码,更加完整健壮一些的代码流程如下所示:


 

代码:

/**
 * open port
 * @param  fd
 * @param  comport 想要打开的串口号
 * @return  返回-1为打开失败
 */
int open_port(int fd,int comport) 
{ 
    char *dev[]={"/dev/ttyUSB0","/dev/ttyS1","/dev/ttyS2"};
 
    if (comport==1)//串口1 
    {
        fd = open( "/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NDELAY); 
        if (-1 == fd)
        { 
            perror("Can't Open Serial Port"); 
            return(-1); 
        } 
     } 
     else if(comport==2)//串口2 
     {     
        fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY); 
      //没有设置O_NONBLOCK非阻塞模式,也可以设置为非阻塞模式,两个模式在下一篇具体说明
 
        if (-1 == fd)
        { 
            perror("Can't Open Serial Port"); 
            return(-1); 
        } 
     } 
     else if (comport==3)//串口3 
     { 
        fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY); 
        if (-1 == fd)
        { 
            perror("Can't Open Serial Port"); 
            return(-1); 
        } 
     } 
     /*恢复串口为阻塞状态*/ 
     if(fcntl(fd, F_SETFL, 0)<0) 
             printf("fcntl failed!\n"); 
     else 
        printf("fcntl=%d\n",fcntl(fd, F_SETFL,0)); 
     /*测试是否为终端设备*/ 
     if(isatty(STDIN_FILENO)==0) 
        printf("standard input is not a terminal device\n"); 
     else 
        printf("isatty success!\n"); 
     printf("fd-open=%d\n",fd); 
     return fd; 
}


关键函数解释:

open

功能描述:用于打开或创建文件,成功则返回文件描述符,否则返回-1,open返回的文件描述符一定是最小的未被使用的描述符

#include<fcntl.h>
int open(const char *pathname, int oflag, ... );
参数解释:
pathname:文件路径名,串口在Linux中被看做是一个文件

oflag:一些文件模式选择,有如下几个参数可以设置

O_RDONLY只读模式
O_WRONLY只写模式
O_RDWR读写模式
上面三个参数在设置的时候必须选择其中一个!!!下面的是可选的
O_APPEND每次写操作都写入文件的末尾
O_CREAT如果指定文件不存在,则创建这个文件
O_EXCL如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
下面三个常量同样是选用的,他们用于同步输入输出

O_DSYNC等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC读(read)等待所有写入同一区域的写操作完成后再进行
O_SYNC等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
对于串口的打开操作,必须使用O_NOCTTY参数,它表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务的一个输入(比如键盘终止信号等)都会影响进程。
O_NDELAY表示不关心DCD信号所处的状态(端口的另一端是否激活或者停止)。

fcntl

功能描述:根据文件描述词来操作文件的特性,返回-1代表出错

#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);
参数说明:
fd:文件描述符
cmd:命令参数
fcntl函数有5种功能: 
1. 复制一个现有的描述符(cmd=F_DUPFD). 
2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 
3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). 
4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). 
5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
具体使用见http://www.cnblogs.com/lonelycatcher/archive/2011/12/22/2297349.html
isatty
函数功能,实现只使用了一个终端专用的函数tcgetattr(如果成功之星,它不改变任何东西),并取其返回值。若为终端设备返回1,否则返回0。详情见 http://blog.csdn.net/wangjingyu00711/article/details/41693155
2.串口的初始化
串口初始化工作需要做以下工作:
设置波特率
设置数据流控制
设置帧的格式(即数据位个数,停止位,校验位)

串口初始化


代码:
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop) 

     struct termios newtio,oldtio; 
/*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/ 
     if  ( tcgetattr( fd,&oldtio)  !=  0) {  
      perror("SetupSerial 1");
    printf("tcgetattr( fd,&oldtio) -> %d\n",tcgetattr( fd,&oldtio)); 
      return -1; 
     } 
     bzero( &newtio, sizeof( newtio ) ); 
/*步骤一,设置字符大小*/ 
     newtio.c_cflag  |=  CLOCAL | CREAD;  
     newtio.c_cflag &= ~CSIZE;  
/*设置停止位*/ 
     switch( nBits ) 
     { 
     case 7: 
      newtio.c_cflag |= CS7; 
      break; 
     case 8: 
      newtio.c_cflag |= CS8; 
      break; 
     } 
/*设置奇偶校验位*/ 
     switch( nEvent ) 
     { 
     case 'o':
     case 'O': //奇数 
      newtio.c_cflag |= PARENB; 
      newtio.c_cflag |= PARODD; 
      newtio.c_iflag |= (INPCK | ISTRIP); 
      break; 
     case 'e':
     case 'E': //偶数 
      newtio.c_iflag |= (INPCK | ISTRIP); 
      newtio.c_cflag |= PARENB; 
      newtio.c_cflag &= ~PARODD; 
      break;
     case 'n':
     case 'N':  //无奇偶校验位 
      newtio.c_cflag &= ~PARENB; 
      break;
     default:
      break;
     } 
     /*设置波特率*/ 
switch( nSpeed ) 
     { 
     case 2400: 
      cfsetispeed(&newtio, B2400); 
      cfsetospeed(&newtio, B2400); 
      break; 
     case 4800: 
      cfsetispeed(&newtio, B4800); 
      cfsetospeed(&newtio, B4800); 
      break; 
     case 9600: 
      cfsetispeed(&newtio, B9600); 
      cfsetospeed(&newtio, B9600); 
      break; 
     case 115200: 
      cfsetispeed(&newtio, B115200); 
      cfsetospeed(&newtio, B115200); 
      break; 
     case 460800: 
      cfsetispeed(&newtio, B460800); 
      cfsetospeed(&newtio, B460800); 
      break; 
     default: 
      cfsetispeed(&newtio, B9600); 
      cfsetospeed(&newtio, B9600); 
     break; 
     } 
/*设置停止位*/ 
     if( nStop == 1 ) 
      newtio.c_cflag &=  ~CSTOPB; 
     else if ( nStop == 2 ) 
      newtio.c_cflag |=  CSTOPB; 
/*设置等待时间和最小接收字符*/ 
     newtio.c_cc[VTIME]  = 0; 
     newtio.c_cc[VMIN] = 0; 
/*处理未接收字符*/ 
     tcflush(fd,TCIFLUSH); 
/*激活新配置*/ 
if((tcsetattr(fd,TCSANOW,&newtio))!=0) 
     { 
      perror("com set error"); 
      return -1; 
     } 
     printf("set done!\n"); 
     return 0; 

讲解这片代码之前,我们要先研究一下termios的数据结构。最小的termios结构的典型定义如下:
struct termios
{
           tcflag_t c_iflag;
           tcflag_t c_oflag;
           tcflag_t c_cflag;
           tcflag_t c_lflag;
           cc_t           c_cc[NCCS];
};
上面五个结构成员名称分别代表:
c_iflag:输入模式
c_oflag:输出模式
c_cflag:控制模式
c_lflag:本地模式
c_cc[NCCS]:特殊控制模式
五种模式的参数说明见博客 http://blog.csdn.net/querdaizhi/article/details/7436722
tcgetattr可以初始化一个终端对应的termios结构,tcgetattr函数原型如下:
#include<termios.h>  
int tcgetattr(int fd, struct termios *termios_p); 
这个函数调用把低昂前终端接口变量的值写入termios_p参数指向的结构。如果这些值其后被修改了,可以通过调用函数tcsetattr来重新配置。
tcsetattr函数原型如下:
#include<termios.h>  
int tcsetattr(int fd , int actions , const struct termios *termios_h);  
参数actions控制修改方式,共有三种修改方式,如下所示:
TCSANOW:立刻对值进行修改
TCSADRAIN:等当前的输出完成后再对值进行修改
TCSAFLUSH:等当前的输出完成之后,再对值进行修改,但丢弃还未从read调用返回的当前的可用的任何输入。
在我们的代码中,我们设置为NOW立即对值进行修改。
tcflush用于清空中端为完成的输入/输出请求及数据,它的函数原型如下:
int tcflush(int fd, int queue_selector);
其中queue_selector时控制tcflush的操作,取值可以为如下参数中的一个:TCIFLUSH清楚正收到的数据,且不会读出来;TCOFLUSH清楚正写入的数据,且不会发送至终端;TCIOFLUSH清除所有正在发送的I/O数据。
再看我们的代码,我们修改字符大小的代码为
newtio.c_cflag  |=  CLOCAL | CREAD;  
newtio.c_cflag &= ~CSIZE;  
c_cflag代表控制模式
CLOCAL含义为忽略所有调制解调器的状态行,这个目的是为了保证程序不会占用串口。
CREAD代表启用字符接收器,目的是是的能够从串口中读取输入的数据。
CS5/6/7/8表示发送或接收字符时使用5/6/7/8比特。
CSTOPB表示每个字符使用两位停止位。
HUPCL表示关闭时挂断调制解调器。
PARENB:启用奇偶校验码的生成和检测功能。
PARODD:只使用奇校验而不使用偶校验。
c_iflag代表输入模式
BRKINT:当在输入行中检测到一个终止状态时,产生一个中断。
TGNBRK:忽略输入行中的终止状态。
TCRNL:将接受到的回车符转换为新行符。
TGNCR:忽略接受到的新行符。
INLCR:将接受到的新行符转换为回车符。
IGNPAR:忽略奇偶校检错误的字符。
INPCK:对接收到的字符执行奇偶校检。
PARMRK:对奇偶校检错误作出标记。
ISTRIP:将所有接收的字符裁减为7比特。
IXOFF:对输入启用软件流控。
IXON:对输出启用软件流控。
c_cc特殊的控制字符

标准模式和非标准模式下,c_cc数组的下标有不同的值:

标准模式:

VEOF:EOF字符
VEOL:EOF字符
VERASE:ERASE字符
VINTR:INTR字符
VKILL:KILL字符
VQUIT:QUIT字符
VSTART:START字符 
VSTOP:STOP字符
非标准模式:

VINTR:INTR字符
VMIN:MIN值
VQUIT:QUIT字符
VSUSP:SUSP字符
VTIME:TIME值
VSTART:START字符 
VSTOP:STOP字符
cfsetispeed和cfsetospeed用来设置输入输出的波特率,函数模型如下:

int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termptr, speed_t speed);
参数说明:
struct termios *termptr:指向termios结构的指针
speed_t speed:需要设置的波特率
返回值:成功返回0,否则返回-1
这样,所有的初始化操作我们就完成了。

串口的读写及关闭操作的详细步骤

读串口


读串口就是接收串口数据,通过read来实现。

read函数原型:

#include <unistd.h>    
ssize_t read(int fd, void *buf, size_t count);  
参数说明:

fd:文件描述符
*buf:缓冲区,读取的数据会被放到这个缓冲区中去
count:请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。
如下两句代码即可:
nread=read(fd,buff,8);//读串口 
printf("nread=%d,%s\n",nread,buff); 
注意:
read默认为阻塞模式,若在open操作中设置O_NONBLOCK则是非阻塞模式。在阻塞模式中,read没有读到数据会阻塞住,直到收到数据;非阻塞模式read没有读到数据会返回-1不会阻塞。
如果是非阻塞模式,read要放在循环中保证持续读数据:
while(1)

{
非阻塞read(设备1);
if(设备1有数据到达)
  处理数据;
非阻塞read(设备2);
if(设备2有数据到达)
  处理数据;
...
sleep(n);
}
sleep(n)的目的是为了做个延迟防止一直在循环做无用功,在延迟等待的时候可以调度其它进程。
如果是阻塞模式,便可以直接调用read,不用放在while循环中。

阻塞模式有出现一个问题,见下面代码:

#include<unistd.h>
#include<stdlib.h>
int main(void)
{
    char buf[10];
    int n;
    n=read(STDIN_FILENO,buf,10);
    if(n<0)
   {
        perror("read STDIN_FILENO");
        exit(1);
    }
    write(STDOUT_FILENO,buf,n);
    return 0;
}

编译后执行如下图所示:

大家可以看到第一次输入hello没有问题,第二次输入helloworldhelloworld后helloworld这10个字符打印出来,程序退出后,Shell继续读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符和换行符当做一条命令去处理,运行不了显示未找到命令。

写串口


写串口即发送数据,用write函数,write函数原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数含义同read相同,需要注意的也同read相同.设置阻塞非阻塞也会同样的影响到write.
我上传的代码有阻塞模式的,也有非阻塞模式的,大家看open中有无设置O_NONBLOCK即可判断阻塞或是非阻塞模式。非阻塞模式read和write要放在while循环中。

关闭串口


close函数,函数原型:

#include<unistd.h>  
int close(int fd);
关闭串口就这一个知识点,没有其它的了。
 

实例

打开串口

 

void SerialPort::Open( const BaudRate baudRate )
    throw( AlreadyOpen, OpenFailed, UnsupportedBaudRate )
{

    if ( this->IsOpen() )
    {
        throw AlreadyOpen( ERR_MSG_PORT_ALREADY_OPEN ) ;
    }
    /*
     * Try to open the serial port and throw an exception if we are
     * not able to open it.
     *
     * :FIXME: Exception thrown by this method after opening the
     * serial port might leave the port open even though mIsOpen
     * is false. We need to close the port before throwing an
     * exception or close it next time this method is called before
     * calling open() again.
     */
    mFileDescriptor = open( mSerialPortName.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK ) ;
    if ( mFileDescriptor < 0 )
    {
        throw OpenFailed( strerror(errno) )  ;
    }

    if ( fcntl( mFileDescriptor, F_SETFL, /*O_ASYNC | */O_NONBLOCK) < 0 )
    {
        throw OpenFailed( strerror(errno) ) ;
    }

    if ( tcgetattr( mFileDescriptor, &mOldPortSettings ) < 0 )
    {
        throw OpenFailed( strerror(errno) ) ;
    }

    termios port_settings ;
    bzero( &port_settings, sizeof( port_settings ) ) ;

    port_settings.c_cflag = CREAD | CLOCAL ;
	
    if ( ( cfsetispeed( &port_settings,baudRate ) < 0 ) ||
         ( cfsetospeed( &port_settings, baudRate ) < 0 ) )
    {
        //
        // If any of the settings fail, we abandon this method.
        //
        throw UnsupportedBaudRate( ERR_MSG_UNSUPPORTED_BAUD ) ;
    }
	
    port_settings.c_cflag &= ~CSIZE ;
    port_settings.c_cflag |= CHAR_SIZE_DEFAULT ;
	
    port_settings.c_cflag &= ~(CSTOPB) ;
		
	port_settings.c_cflag &= ~(PARENB) ;
    port_settings.c_iflag = IGNPAR ;
	
	port_settings.c_cflag &= ~(CRTSCTS) ;
	
	port_settings.c_lflag = 0;
	port_settings.c_oflag = 0;
	
    port_settings.c_cc[ VMIN  ] = 0 ;
    port_settings.c_cc[ VTIME ] = 0 ;
    
    /*
     * Flush the input buffer associated with the port.
     */
    if ( tcflush( mFileDescriptor,
                  TCIFLUSH ) < 0 )
    {
        throw OpenFailed( strerror(errno) ) ;
    }
    /*
     * Write the new settings to the port.
     */
    if ( tcsetattr( mFileDescriptor,
                    TCSANOW,
                    &port_settings ) < 0 )
    {
        throw OpenFailed( strerror(errno) ) ;
    }
	
    mIsOpen = true ;
		
    return ;
}

读串口

int SerialPort::Read( vector<BYTE>&        ds,
                      const unsigned int num_of_bytes,
                      const unsigned int ms_timeout )
    throw( NotOpen, SerialError )
{

    if ( ! this->IsOpen() )
    {
        throw NotOpen( ERR_MSG_PORT_NOT_OPEN ) ;
    }
	
	struct timeval tv;
	tv.tv_sec = ms_timeout / 1000;
	tv.tv_usec = (ms_timeout % 1000) * 1000;
	fd_set readfds;
    fd_set exceptfds;
    FD_ZERO(&readfds);
    FD_ZERO(&exceptfds);
    FD_SET(mFileDescriptor, &readfds);
    FD_SET(mFileDescriptor, &exceptfds);
	int retval = select(mFileDescriptor + 1, &readfds, NULL, &exceptfds, &tv);
	if ( retval == 0 ) 
	{
		return -1;
	}

	if ( retval < 0 ) 
	{
		throw SerialError( strerror(errno) );
	}

	if ( FD_ISSET(mFileDescriptor, &exceptfds) ) 
	{
        throw SerialError( ERR_MSG_SERIAL_FD_EXCEPTION );
    }

	if ( FD_ISSET(mFileDescriptor, &readfds) ) 
	{
        // Empty the data buffer.
        ds.clear() ;
        ds.resize( num_of_bytes ) ;
        int n = read(mFileDescriptor, &ds[0], num_of_bytes);
        ds.resize( n );      
        return n;
    }
    return -1;
}

 

写串口

void SerialPort::Write( const vector<BYTE>&  ds,const unsigned int ms_timeout )
    throw( NotOpen, SerialError )
{
	if ( ds.size() <= 0 ) return;
    if ( ! this->IsOpen() )
    {
        throw NotOpen( ERR_MSG_PORT_NOT_OPEN ) ;
    }
	struct timeval tv;
	tv.tv_sec = ms_timeout / 1000;
	tv.tv_usec = (ms_timeout % 1000) * 1000;
	fd_set writefds;
    fd_set exceptfds;
    FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);
    FD_SET(mFileDescriptor, &writefds);
    FD_SET(mFileDescriptor, &exceptfds);
	int retval = select(mFileDescriptor + 1, NULL, &writefds, &exceptfds, &tv);
	if ( retval == 0 ) 
	{
		throw SerialError( ERR_MSG_SERIAL_CANNOT_WRITE );
	}
    if ( retval <= 0) 
    {
        throw SerialError( strerror(errno) );
    }
    if ( FD_ISSET(mFileDescriptor, &exceptfds) ) 
    {
        throw SerialError( ERR_MSG_SERIAL_FD_EXCEPTION );
    }
	if ( FD_ISSET(mFileDescriptor, &writefds) ) 
	{
		int written_bytes = 0 ;
		int remain_bytes = ds.size();
		while ( remain_bytes > 0 ) 
		{
			int n = write( mFileDescriptor, &ds[written_bytes], remain_bytes );
			if ( n <= 0 ) 
			{
				throw SerialError(strerror(errno)) ;
			}
			remain_bytes -= n;
			written_bytes += n;
		}
	}
}

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值