Linux串口读写

来源:互联网 发布:心电图模拟软件 编辑:程序博客网 时间:2024/05/16 00:54

Linux串口读写

串口简介

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

Linux操作系统从一开始就对串行口提供了很好的支持

计算机串口的引脚说明

序号

信号名称

符号

流向

功能

2

发送数据

TXD

DTEDCE

DTE发送串行数据

3

接收数据

RXD

DTEDCE

DTE接收串行数据

4

请求发送

RTS

DTEDCE

DTE请求DCE将线路切换到发送方式

5

允许发送

CTS

DTEDCE

DCE告诉DTE线路已接通可以发送数据

6

数据设备准备好

DSR

DTEDCE

DCE准备好

7

信号地

 

 

   信号公共地

8

载波检测

DCD

DTEDCE

表示DCE接收到远程载波

20

数据终端准备好

DTR

DTEDCE

DTE准备好

22

振铃指示

RI

DTEDCE

表示DCE与线路接通,出现振铃

 

串口操作

串口操作需要的头文件

#include    <stdio.h>     /*标准输入输出定义*/

#include    <stdlib.h>    /*标准函数库定义*/

#include    <unistd.h>    /*Unix标准函数定义*/

#include    <sys/types.h> /*数据类型,比如一些XXX_t的那种*/

#include    <sys/stat.h>  /*定义了一些返回值的结构,没看明白*/

#include    <fcntl.h>     /*文件控制定义*/

#include    <termios.h>   /*PPSIX终端控制定义*/

#include    <errno.h>     /*错误号定义*/

 

打开串口

Linux下串口文件是位于/dev下的

串口一/dev/ttyS0

串口二/dev/ttyS1

打开串口是通过使用标准的文件打开函数操作:

intfd;

/*以读写方式打开串口*/

fd=open("/dev/ttyS0",O_RDWR);

if(-1 == fd){

/*不能打开串口一*/

perror("提示错误!");

}

 

设置串口

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

串口的设置主要是设置structtermios结构体的各成员值。

structtermio

{  unsignedshort c_iflag;/*输入模式标志*/ 

   unsignedshort c_oflag;    /*输出模式标志*/ 

   unsignedshort c_cflag;    /*控制模式标志*/  

   unsignedshort c_lflag;    /*local mode flags */  

   unsignedchar c_line;         /*line discipline */

   unsignedchar c_cc[NCC];   /*control characters */

};

 

设置这个结构体很复杂,我这里就只说说常见的一些设置:

波特率设置

下面是修改波特率的代码:

struct termios Opt;

tcgetattr(fd,&Opt);/*获得当前设备模式,与终端相关的参数。fd=0标准输入*/

cfsetispeed(&Opt,B19200);/*设置结构termios输入波特率为19200Bps*/

cfsetospeed(&Opt,B19200);  /*fd应该是文件描述的意思*/

tcsetattr(fd,TCANOW,&Opt);/*设置终端参数,TCANOW修改立即发生*/

 

设置波特率的例子函数:

/**

*@brief 设置串口通信速率

*@param fd    类型int 打开串口的文件句柄

*@param speed 类型int 串口速度

*@return void

*/

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, };

voidset_speed(intfd,intspeed){

   int  i;

   int  status;

   structtermios   Opt;

   tcgetattr(fd,&Opt);

   for( i= 0;  i < sizeof(speed_arr)/sizeof(int); i++) {

      if (speed == name_arr[i]) {

/**

 *tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓存(用户程序已经写,但尚未发送)queue参数应是下列三个常数之一:

*TCIFLUSH刷清输入队列。

 *TCOFLUSH刷清输出队列。

*TCIOFLUSH刷清输入、输出队列。

 */

   tcflush(fd,TCIOFLUSH);//设置前flush    

   cfsetispeed(&Opt,speed_arr[i]); 

  cfsetospeed(&Opt,speed_arr[i]);  

   //通过tcsetattr函数把新的属性设置到串口上。

   //tcsetattr(串口描述符,立即使用或者其他标示,指向termios的指针)

   status= tcsetattr(fd, TCSANOW, &Opt); 

   if (status != 0)

{       

      perror("tcsetattrfd1"); 

     return;    

    }   

    tcflush(fd,TCIOFLUSH); //设置后flush

  

}

}

 

效验位和停止位的设置:

无效验

8

Option.c_cflag&= ~PARENB;
Option.c_cflag&= ~CSTOPB;
Option.c_cflag&= ~CSIZE;
Option.c_cflag|= ~CS8;

奇效验(Odd)

7

Option.c_cflag|= ~PARENB;
Option.c_cflag&= ~PARODD;
Option.c_cflag&= ~CSTOPB;
Option.c_cflag&= ~CSIZE;
Option.c_cflag|= ~CS7;

偶效验(Even)

7

Option.c_cflag&= ~PARENB;
Option.c_cflag|= ~PARODD;
Option.c_cflag&= ~CSTOPB;
Option.c_cflag&= ~CSIZE;
Option.c_cflag|= ~CS7;

Space效验

7

Option.c_cflag&= ~PARENB;
Option.c_cflag&= ~CSTOPB;
Option.c_cflag&= &~CSIZE;
Option.c_cflag|= CS8;

设置效验的函数:

/**

 *@brief  设置串口数据位,停止位和效验位

 *@param fd    类型 int 打开的串口文件句柄

 *@param databits类型 int数据位  取值7或者8

 *@param stopbits类型 int停止位  取值为1或者2

 *@param parity 类型 int 效验类型取值为N,E,O,,S

 */

intset_Parity(intfd,intdatabits,intstopbits,intparity)

{

   structtermios options;

   if(tcgetattr(fd, &options) != 0)

   {

      perror("SetupSerial1");

      return(FALSE);

   }

   options.c_cflag&= ~CSIZE;

   switch(databits)

   /*设置数据位数*/

   {

   case7:

      options.c_cflag|= CS7;

      break;

   case8:

      options.c_cflag|= CS8;

      break;

   default:

      fprintf(stderr,"Unsupporteddata size\n");

      return(FALSE);

   }

   switch(parity)

   {

   case'n':

   case'N':

      options.c_cflag&= ~PARENB;/*Clear parity enable */

      options.c_iflag&= ~INPCK;/*Enable parity checking */

      break;

   case'o':

   case'O':

      options.c_cflag|= (PARODD | PARENB);/*设置为奇效验*/

      options.c_iflag|= INPCK;/*Disnable parity checking */

      break;

   case'e':

   case'E':

      options.c_cflag|= PARENB;/*Enable parity */

      options.c_cflag&= ~PARODD;/*转换为偶效验*/

      options.c_iflag|= INPCK;/*Disnable parity checking */

      break;

   case'S':

   case's':/*asno parity*/

      options.c_cflag&= ~PARENB;

      options.c_cflag&= ~CSTOPB;

      break;

   default:

      fprintf(stderr,"Unsupportedparity\n");

      return(FALSE);

   }

   /*设置停止位*/

   switch(stopbits)

   {

   case1:

      options.c_cflag&= ~CSTOPB;

      break;

   case2:

      options.c_cflag|= CSTOPB;

      break;

   default:

      fprintf(stderr,"Unsupportedstop bits\n");

      return(FALSE);

   }

   /*Set input parity option */

   if(parity != 'n')

      options.c_iflag|= INPCK;

   tcflush(fd,TCIFLUSH);

   options.c_cc[VTIME]= 150;/*设置超时15seconds*/

   options.c_cc[VMIN]= 0;/*Update the options and do it NOW */

   if(tcsetattr(fd, TCSANOW, &options) != 0)

   {

      perror("SetupSerial3");

      return(FALSE);

   }

   return(TRUE);

}

 

需要注意的是:

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

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/

options.c_oflag &= ~OPOST;  /*Output*/

 

读写串口

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

  • 发送数据

char buffer[1024];

int   Length;

int   nByte;

nByte= write(fd, buffer ,Length)

 

  • 读取串口数据

使用文件操作read函数读取,如果设置为原始模式(RawMode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

char buff[1024];

int   Len;

int readByte = read(fd,buff,Len);

 

关闭串口

关闭串口就是关闭文件。

close(fd);

 



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

/**********************************************************************

 *代码说明:使用串口二测试的,发送的数据是字符,但是没有发送字符串结束符号,

 *所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。

 **********************************************************************/

#defineFALSE  -1

#defineTRUE   0

/*********************************************************************/

intOpenDev(char*Dev)

{

   //Dev就是设备,设备就是文件,就是给出该设备文件的路径

   intfd = open(Dev,O_RDWR ); //|O_NOCTTY | O_NDELAY

   if(-1 == fd)

   {

      perror("Can'tOpen Serial Port");

      return-1;

   }

   else

      returnfd;

}

intmain(intargc,char**argv)

{

   intfd;

   intnread;

   charbuff[512];

   char*dev = "/dev/ttyS1";//串口二

   fd = OpenDev(dev);

   set_speed(fd, 19200);

   if(set_Parity(fd, 8, 1, 'N')== FALSE)

   {

      printf("SetParity Error\n");

      exit(0);

   }

   while(1)//循环读取数据

   {

      while((nread = read(fd,buff, 512))>0)

      {

          printf("\nLen%d\n",nread);

          buff[nread+1]='\0';

          printf("\n%s",buff);

      }

   }

   //close(fd); 

   //exit (0);



1
、虚拟机下使用串口的方法
使用vmwave,默认串口设备是没有添加的,通过vmwave将设备加入即可正常使用串口。虚拟机串口打开后,可能会占用windows下的串口。另外,虚拟机的串口收发比正常的速度的确要慢许多。



2
、消除Linux串口收发的一些规则

Linux
串口收发有许多模式,如:

1)接收返回模式: 如果串口没有接收到数据,read()函数不返回。

2)数据接收\n才返回接收的数据,否则read()函数返回0

3)特殊字符解析问题,部分特殊字符接收/发送时,会被屏蔽或者转义。如发送0x0A接收变为0x0A0x0A0x0D被屏蔽等。

4)接收反馈:如串口接收到数据,立即将该数据发送出去。

(上面是我遇到的一些问题,可能表述不很清楚,呵呵。如果用于收发txt文件,一般不大注意。)



3
、解决问题的方法是,消除这些默认规则,关键是structtermios的参数影响。

structtermios  {

tcflag_t c_iflag;              /**//*
输入模式旗标*/

tcflag_tc_oflag;              /**//*
输出模式旗标*/

tcflag_tc_cflag;              /**//*
控制模式旗标*/

tcflag_tc_lflag;              /**//*
区域模式旗标*/

cc_tc_line;                   /**//*
行控制(linediscipline) */

cc_t c_cc[NCCS];          /**//*
控制特性*/

};





由于研究不深,如果要消除所有上面的规则,我是如下处理的

structtermios options;

串口打开方式:

open("dev/ttyS0" , O_RDWR|O_NOCTTY| O_NDELAY);

消除收发模式规则:

options.c_lflag       = 0;

options.c_oflag       = 0;

options.c_iflag       = 0;



消除字符屏蔽规则:

options.c_cc[VINTR]   = 0;       /**//* Ctrl-c*/

options.c_cc[VQUIT]     = 0;  /**//* Ctrl- */

options.c_cc[VERASE]    = 0;  /**//* del */

options.c_cc[VKILL]    = 0;  /**//* @ */

options.c_cc[VEOF]     = 0;  /**//* Ctrl-d */

options.c_cc[VTIME]    = 1;  /**//*  */

options.c_cc[VMIN]     =0;   /**//*  */

options.c_cc[VSWTC]   = 0;   /**//* '' */

options.c_cc[VSTART]  = 0;   /**//* Ctrl-q */

options.c_cc[VSTOP]   = 0;   /**//* Ctrl-s */

options.c_cc[VSUSP]   = 0;   /**//* Ctrl-z */

options.c_cc[VEOL]    = 0;   /**//* '' */

options.c_cc[VREPRINT] = 0;  /**//* Ctrl-r */

options.c_cc[VDISCARD] = 0;  /**//* Ctrl-u */

options.c_cc[VWERASE]  = 0;  /**//* Ctrl-w */

options.c_cc[VLNEXT]   = 0;  /**//* Ctrl-v */

options.c_cc[VEOL2]    = 0;  /**//* ''*/



以上设置,在其它参数串口设置前执行,如果你需要保留部分参数,请参阅http://blog.chinaunix.net/article.php?articleId=15964&blogId=60

RedHatFeroda 4下编译通过



== = = = = = = = = =
非阻塞read== = = = = = = = = =

Q
:在调用串口read(fd,  buff,  len);,如果串口没有数据,会停在read,请问有没有办法让这个read动作中止?

A
:使用非阻塞方式select函数(I/O多工机制)或者open的时候加O_NONBLOCK参数。

intselect(int n,fd_set * readfds,fd_set * writefds,fd_set *exceptfds,struct timeval * timeout);
关于这个函数的使用我会在下篇blog中整理。

== = = = = = = = = =
串口收发源码== = = = = = = = = =

一下代码已经经过我测试,没有问题。开发环境Redhat9,运行环境s3c2410

== = = = = receive.c= = = = = =

#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>   
#include  <string.h>


#define TRUE 1

//
初始化串口选项: 

voidsetTermios(struct termios * pNewtio, intuBaudRate)
{
bzero(pNewtio, sizeof(struct termios)); /*clear struct for new port settings */
//8N1
pNewtio->c_cflag= uBaudRate | CS8 | CREAD | CLOCAL;
pNewtio->c_iflag =IGNPAR;
pNewtio->c_oflag = 0;
pNewtio->c_lflag = 0;//non ICANON

/*

initialize all controlcharacters

default values can be found in/usr/include/termios.h, and

are given in the comments, butwe don't need them here

*/

pNewtio->c_cc[VINTR] =0; /* Ctrl-c */
pNewtio->c_cc[VQUIT] = 0; /* Ctrl-\*/
pNewtio->c_cc[VERASE] = 0; /* del */
pNewtio->c_cc[VKILL]= 0; /* @ */
pNewtio->c_cc[VEOF] = 4; /* Ctrl-d*/
pNewtio->c_cc[VTIME] = 5; /* inter-character timer,timeout VTIME*0.1 */
pNewtio->c_cc[VMIN] = 0; /* blockingread until VMIN character arrives */
pNewtio->c_cc[VSWTC] =0; /* '\0' */
pNewtio->c_cc[VSTART] = 0; /* Ctrl-q*/
pNewtio->c_cc[VSTOP] = 0; /* Ctrl-s*/
pNewtio->c_cc[VSUSP] = 0; /* Ctrl-z*/
pNewtio->c_cc[VEOL] = 0; /* '\0'*/
pNewtio->c_cc[VREPRINT] = 0; /* Ctrl-r*/
pNewtio->c_cc[VDISCARD] = 0; /* Ctrl-u*/
pNewtio->c_cc[VWERASE] = 0; /* Ctrl-w*/
pNewtio->c_cc[VLNEXT] = 0; /* Ctrl-v*/
pNewtio->c_cc[VEOL2] = 0; /* '\0' */
}



#defineBUFSIZE 512

int main(int argc, char **argv)
{
intfd;
int nread;
char buff[BUFSIZE];
struct termios oldtio,newtio;
struct timeval tv;
char *dev ="/dev/ttyS0";
fd_setrfds;
if ((fd = open(dev, O_RDWR | O_NOCTTY))<0)
{
printf("err:can't open serial port!\n");
return -1;
}
tcgetattr(fd,&oldtio); /* save current serial port settings*/
setTermios(&newtio, B115200);
tcflush(fd,TCIFLUSH);
tcsetattr(fd, TCSANOW,&newtio);

tv.tv_sec=30;
tv.tv_usec=0;
while(TRUE)
{
printf("wait...\n");
FD_ZERO(&rfds);
FD_SET(fd,&rfds);
if (select(1+fd, &rfds, NULL, NULL,&tv)>0)
{
printf("wait...\n");
if(FD_ISSET(fd, &rfds))
{
nread=read(fd, buff,BUFSIZE);
printf("readlength=%d\n",nread);
buff[nread]='\0';
printf("%s\n",buff);
}
}
}
tcsetattr(fd, TCSANOW,&oldtio);
close(fd);
}


== = = = send.c= = = = = =

#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>   
#include  <string.h>



//
初始化串口选项: 
voidsetTermios(struct termios * pNewtio, intuBaudRate)
{
bzero(pNewtio, sizeof(struct termios)); /*clear struct for new port settings */
//8N1
pNewtio->c_cflag= uBaudRate | CS8 | CREAD | CLOCAL;
pNewtio->c_iflag =IGNPAR;
pNewtio->c_oflag = 0;
pNewtio->c_lflag = 0;//non ICANON

/*

initialize all controlcharacters

default values can be found in/usr/include/termios.h, and

are given in the comments, butwe don't need them here

*/

pNewtio->c_cc[VINTR] =0; /* Ctrl-c */
pNewtio->c_cc[VQUIT] = 0; /* Ctrl-\*/
pNewtio->c_cc[VERASE] = 0; /* del */
pNewtio->c_cc[VKILL]= 0; /* @ */
pNewtio->c_cc[VEOF] = 4; /* Ctrl-d*/
pNewtio->c_cc[VTIME] = 5; /* inter-character timer,timeout VTIME*0.1 */
pNewtio->c_cc[VMIN] = 0; /* blockingread until VMIN character arrives */
pNewtio->c_cc[VSWTC] =0; /* '\0' */
pNewtio->c_cc[VSTART] = 0; /* Ctrl-q*/
pNewtio->c_cc[VSTOP] = 0; /* Ctrl-s*/
pNewtio->c_cc[VSUSP] = 0; /* Ctrl-z*/
pNewtio->c_cc[VEOL] = 0; /* '\0'*/
pNewtio->c_cc[VREPRINT] = 0; /* Ctrl-r*/
pNewtio->c_cc[VDISCARD] = 0; /* Ctrl-u*/
pNewtio->c_cc[VWERASE] = 0; /* Ctrl-w*/
pNewtio->c_cc[VLNEXT] = 0; /* Ctrl-v*/
pNewtio->c_cc[VEOL2] = 0; /* '\0' */
}



intmain(int argc, char **argv)
{
int fd;
int nCount, nTotal,i;
struct termios oldtio, newtio;
char *dev="/dev/ttyS0";
if ((argc!=3) || (sscanf(argv[1],"%d", &nTotal) != 1))
{
printf("err: needtow arg =%d!\n", argc );
return -1;
}

if ((fd =open(dev, O_RDWR | O_NOCTTY))<0)
{
printf("err:can't open serial port!\n");
return -1;
}

tcgetattr(fd,&oldtio); /* save current serial port settings*/
setTermios(&newtio, B115200);
tcflush(fd,TCIFLUSH);
tcsetattr(fd, TCSANOW, &newtio);
for (i=0;i<nTotal; i++)
{
nCount=write(fd, argv[2],strlen(argv[2]));
printf("senddata\n");
sleep(1);
}
tcsetattr(fd, TCSANOW,&oldtio);
close(fd);
return 0;
}






== = = = =.makefile= = = = = =



CC =gcc

all:receive send

receive: receive.c

$(CC)receive.c -o  receive

send: send.c

$(CC) send.c-o  send

clean:

-rm -rf testCOM receive send


原创粉丝点击