linux下实现串口通讯

来源:互联网 发布:艾滋病感染概率 知乎 编辑:程序博客网 时间:2024/06/05 05:36

1、关键结构体

   (1)struct termios
                {

                      unsigned short c_iflag;             /* 输入模式标志*/
                      unsigned short c_oflag;          /* 输出模式标志*/
                      unsigned short c_cflag;           /* 控制模式标志*/
                      unsigned short c_lflag;            /*区域模式标志或本地模式标志或局部模式*/
                      unsigned char c_line;               /*行控制line discipline */
                      unsigned char c_cc[NCC];       /* 控制字符特性*/
                };

               (一)c_iflag 标志常量:
                Input mode ( 输入模式)
               
                input mode可以在输入值传给程序之前控制其处理的方式。
                其中输入值可能是由序列埠或键盘的终端驱动程序所接收到的字元。
               
               
                我们可以利用termios结构的c_iflag的标志来加以控制,其定义的方式皆以OR来加
                以组合。
               
               
                      *  
                              * IGNBRK :忽略输入中的 BREAK 状态。 (忽略命 令行中的中
                                断)
                              * BRKINT :(命令行出 现中断时,可产生一插断)如果设置了
                                IGNBRK,将忽略 BREAK。如果没有设置,但是设置了 BRKINT,
                                那么 BREAK 将使得输入和输出队列被刷新,如果终端是一个前
                                台进程组的控制终端,这个进程组中所有进程将收到 SIGINT 信
                                号。如果既未设置 IGNBRK 也未设置 BRKINT,BREAK 将视为与
                                NUL 字符同义,除非设置了 PARMRK,这种情况下它被视为序列
                                377 � �。 
                              * IGNPAR :忽略桢错误和奇偶校验错。 
                              * PARMRK :如果没有设置 IGNPAR,在有奇偶校验错或桢错误的字
                                符前插入 377 �。如果既没有设置 IGNPAR 也没有设置
                                PARMRK,将有奇偶校验错或桢错误的字符视为 �。 
                              * INPCK :启用输入奇偶检测。 
                              * ISTRIP :去掉第八位。 
                              * INLCR :将输入中的 NL 翻译为 CR。(将收到 的换行符号转换
                                为Return) 
                              * IGNCR :忽略输入中的回车。 
                              * ICRNL :将输入中的回车翻译为新行 (除非设置了 IGNCR)(否则
                                当输入信号有 CR 时不会终止输入)。 
                              * IUCLC :(不属于 POSIX) 将输入中的大写字母映射为小写字
                                母。 
                              * IXON :启用输出的 XON/XOFF 流控制。  
                              * IXANY :(不属于 POSIX.1;XSI) 允许任何字符来重新开始输
                                出。(?) 
                              * IXOFF :启用输入的 XON/XOFF 流控制。 
                              * IMAXBEL:(不属于 POSIX) 当输入队列满时响零。Linux 没有实
                                现这一位,总是将它视为已设置。

 

               (二) c_oflag 标志常量:Output mode ( 输 出模式)
                Output mode主要负责控制输出字元的处理方式。输出字元在传送到序列埠或显示
                器之前是如何被程序来处理。
                输出模式是利用termios结构的c_oflag的标志来加以控制,其定义的方式皆以OR来
                加以组合。
                      *  
                              * OPOST :启用具体实现自行定义的输出处理。 
                              * OLCUC :(不属于 POSIX) 将输出中的小写字母映射为大写字
                                母。 
                              * ONLCR :(XSI) 将输出中的新行符映射为回车-换行。 
                              * OCRNL :将输出中的回车映射为新行符 
                              * ONOCR :不在第 0 列输出回车。 
                              * ONLRET :不输出回车。 
                              * OFILL :发送填充字符作为延时,而不是使用定时来延时。 
                              * OFDEL :(不属于 POSIX) 填充字符是 ASCII DEL (0177)。如果
                                不设置,填充字符则是 ASCII NUL。 
                              * 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 标志常量:Control mode ( 控制模式)
                Control mode主要用于控制终端设备的硬件设置。利用termios结构的c_cflag的标
                志来加以控制。控制模式用在序列线连接到数据设备,也可以用在与终 端设备的
                交谈。
                一般来说,改变终端设备的组 态要比使用termios的控制模式来改变行(lines)的
                行为来得容易。
                      *  
                              * CBAUD :(不属于 POSIX) 波特率掩码 (4+1 位)。 
                              * CBAUDEX :(不属于 POSIX) 扩展的波特率掩码 (1 位),包含在
                                CBAUD 中。 
                              * (POSIX 规定波特率存储在 termios 结构中,并未精确指定它的
                                位置,而是提供了函数 cfgetispeed() 和 cfsetispeed() 来存
                                取它。一些系统使用 c_cflag 中 CBAUD 选择的位,其他系统使
                                用单独的变量,例如 sg_ispeed 和 sg_ospeed 。) 
                              * CSIZE:字符长度掩码(传送或接收字元时用的位数)。 取值为
                                CS5(传送或接收字元时用5bits), CS6, CS7, 或 CS8。 
                              * CSTOPB :设置两个停止位,而不是一个。 
                              * CREAD :打开接受者。 
                              * PARENB :允许输出产生奇偶信息以及输入的奇偶校验(启用同
                                位产生与侦测)。 
                              * PARODD :输入和输出是奇校验(使用奇同位而非偶同位)。 
                              * HUPCL :在最后一个进程关闭设备后,降低 modem 控制线 (挂
                                断)。(?) 
                              * CLOCAL :忽略 modem 控制线。 
                              * LOBLK :(不属于 POSIX) 从非当前 shell 层阻塞输出(用于
                                shl )。(?) 
                              * CIBAUD :(不属于 POSIX) 输入速度的掩码。CIBAUD 各位的值与
                                CBAUD 各位相同,左移了 IBSHIFT 位。 
                              * CRTSCTS :(不属于 POSIX) 启用 RTS/CTS (硬件) 流控制。

   2、串口通讯的关键函数

(1)tcgetattr

                        1.原型
                        int tcgetattr(int fd,struct termois & termios_p);
                        2.功能
                        取得终端介质(fd)初始值,并把其值 赋给temios_p;函数可以从后台进
                        程中调用;但是,终端属性可能被后来的前 台进程所改变。

(2)tcflush

   

     tcflush函数刷清(扔掉)输入缓存(终端驱动法度已接管到,但用户法度尚未读)或输出缓存(用户法度已经写,但尚未发送).

  int tcflush(int filedes,int quene)
  quene数该当是下列三个常数之一:
    *TCIFLUSH  刷清输入队列
    *TCOFLUSH  刷清输出队列
    *TCIOFLUSH 刷清输入、输出队列

(3)tcsetattr

                     1.原型
                        int tcsetattr(int fd,int actions,const struct    termios
                        *termios_p);
                        2.功能
                        设置与终端相关的参数 (除非需要底层支持却无法满足),使用
                        termios_p 引用的 termios 结构。optional_actions (tcsetattr函数
                        的第二个参数)指定了什么时候改变会起作用:
                              * TCSANOW:改变立即发生 
                              * TCSADRAIN:改变在所有写入 fd 的输出都被传输后生效。这个
                                函数应当用于修改影响输出的参数时使用。(当前输出完成时将
                                值改变) 
                              * TCSAFLUSH :改变在所有写入 fd 引用的对象的输出都被传输后
                                生效,所有已接受但未读入的输入都在改变发生前丢弃(同
                                TCSADRAIN,但会舍弃当前所有值)。

3、串口通讯波特率设置

波特率的设置定义在<asm/termbits.h>,其包含在头文件<termios.h>里。

常用的波特率常数如下:
B0   ----- 0             B1800   ----- 1800
B50  ----- 50            B2400   ----- 2400
B75  ----- 75            B4800   ----- 4800
B110 ----- 110           B9600   ----- 9600
B134 ----- 134.5         B19200  ----- 19200
B200 ----- 200           B38400  ----- 38400
B300 ----- 300           B57600  ----- 57600
B600 ----- 600           B76800  ----- 76800
B1200----- 1200          B115200 ----- 115200

假定程序中想要设置通讯的波特率,使用cfsetispeed( )和cfsetospeed( )函数来操作,获取波特率信息是通过cfgetispeed()和cfgetospeed()函数来完成的。比如可以这样来指定串口通讯的波特率:

#i nclude <stdio.h>    //头文件定义

.......

struct termios opt;           /*定义指向termios 结构类型的指针opt*/

/***************以下设置通讯波特率****************/

cfsetispeed(&opt,B115200 ); /*指定输入波特率,115200bps*/

cfsetospeed(&opt,B115200);/*指定输出波特率,115200bps*/

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

..........

一般来说,输入、输出的波特率应该是一致的。

 

3、代码实现

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>

#include <termios.h>

#include "serial_comm.h"

int OpenDev(void)
{
 int fd = -1;
 printf("%s\n", SERIAL_DEV);
 fd = open(SERIAL_DEV, O_RDWR | O_NOCTTY | O_NDELAY);

 if ( -1 == fd)
 {
  printf("%s, %d, open failed\n", __FUNCTION__, __LINE__);
  return -1;
 }
 printf("%s, fd = %d\n", SERIAL_DEV, fd);
 return fd;
}

int SetSerialParam(int fd,int databits,int stopbits,int parity)
{

       struct termios options;

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

              printf("%s, %d, tcgetattr failed\n", __FUNCTION__, __LINE__);    

              return -1; 

       }

         cfsetispeed(&options, B115200);
         cfsetospeed(
&options, B115200);
    

       options.c_cflag &= ~CSIZE;

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

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

 
       switch (databits) /*设置数据位数*/

       {  
        case 7:          
               options.c_cflag |= CS7;
               break;
     
        case 8:    
          printf("%s, %d, 8bit\n", __FUNCTION__, __LINE__);
               options.c_cflag |= CS8;
               break;  

        default:   

              fprintf(stderr,"Unsupported data size/n");
     return -1;

       }

 switch (parity)

 {  
        case 'n':
        case 'N':   
               options.c_cflag &= ~PARENB;   /* Clear parity enable */
               options.c_iflag &= ~INPCK;     /* Enable parity checking */
      printf("%s, %d, no parity\n", __FUNCTION__, __LINE__);
               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':  /*as no parity*/  
           options.c_cflag &= ~PARENB;
           options.c_cflag &= ~CSTOPB;
     break; 

        default:  
            fprintf(stderr,"Unsupported parity/n");   
          return -1; 

       } 

 /* 设置停止位*/ 

 switch (stopbits)

 {  
       case 1:   
       printf("%s, %d, no parity\n", __FUNCTION__, __LINE__);
              options.c_cflag &= ~CSTOPB; 
              break; 

       case 2:   
              options.c_cflag |= CSTOPB; 
          break;

       default:   
        fprintf(stderr,"Unsupported stop bits/n"); 
       return -1;

 }

/* Set input parity option */
 if (parity != 'n')  
       options.c_iflag |= INPCK;
 

 /*下面是设置read读的字节个数我本身用不到干掉*/
 #if 0
 options.c_cc[VTIME] = 0; /* 设置超时0 seconds*/  

 options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/
 #endif
 printf("%s, %d, \n", __FUNCTION__, __LINE__);

 /*清空缓冲区*/
 tcflush(fd,TCIFLUSH);
 
 /*设置终端参数*/
 if (tcsetattr(fd,TCSANOW,&options) != 0)  
 {
       printf("%s, %d, tcsetattr failed\n", __FUNCTION__, __LINE__);
       return -1; 
 }
 printf("%s, %d, set serial success\n", __FUNCTION__, __LINE__);
 
 return 0; 

}


int SerialSend(int fd, char *scData, int DataLen)
{
 int  retval = -1;
 int  iRelSendlen = 0,size;
 
 if (NULL == scData || fd < 0)
 {
  printf("%s, %d, Input Param Failed\n", __FUNCTION__, __LINE__);
  return -1;
 } 
 printf("%s, %d, scData = %s, DataLen = %d\n", __FUNCTION__, __LINE__, scData, DataLen);
 while (iRelSendlen != DataLen)
 {
  size = write(fd, scData+iRelSendlen, DataLen-iRelSendlen);
  printf("%s, %d, size = %d\n", __FUNCTION__, __LINE__, size);
  if (size <= 0)
  {
   printf("%s, %d, Serial Send Failed\n", __FUNCTION__, __LINE__);
   break;
  }  
  iRelSendlen += size;   
 } 
 
 return 0;
}

int SerialRead(int fd, char *Data, int DataLen, int TimeOut, unsigned long PerTimeOut)
{
 struct timeval tn, ti, diff, td, timeout;
 unsigned int uiReadCnt=0;
 int retval,ibacklight_set_time_bak;
 

 
 if (fd < 0 || !Data)
 {
  return -1;
 }
 
 diff.tv_sec = TimeOut;
 diff.tv_usec = 0;
 
 gettimeofday(&tn, NULL); //获取当前时间
 timeradd(&tn, &diff, &ti);
 
 while (1)
 {      
  fd_set inset;
  int ireaded = 0;
  int error = -1;

  if (uiReadCnt == DataLen) {
   retval = uiReadCnt;
   break;
  }

  FD_ZERO(&inset);
  FD_SET(fd, &inset);

  if (!TimeOut)
   timeout.tv_sec = timeout.tv_usec = 0;
  else
  {
   gettimeofday(&tn, NULL);
   if (timercmp(&tn, &ti, >=))
   {
    printf("%s, %d, Timeout\n", __FUNCTION__, __LINE__);
    break;
   }
   
   diff.tv_sec  = 0;
   diff.tv_usec = PerTimeOut * 1000;
   timeradd(&tn, &diff, &td);
   
   if (timercmp(&ti, &td, >))
    timeout = diff;
   else
    timersub(&ti, &tn, &timeout);
  }
  
  // 检测接收
  error = select(fd+1, &inset, NULL, NULL, &timeout);
  if (error < 0)
  { // 失败
   printf("%s, %d, select Failed\n", __FUNCTION__, __LINE__);
   break;
  }

  if (FD_ISSET(fd, &inset))
  {
   ireaded = read(fd, Data+uiReadCnt, DataLen-uiReadCnt);
   printf("%s, %d, Data = %s\n", __FUNCTION__, __LINE__, Data);
   if (ireaded < 0)
   {
    printf("%s, %d, Read Over\n", __FUNCTION__, __LINE__);
    retval = uiReadCnt;
    break;
   }
   uiReadCnt += ireaded;
  }
  else
  {
   if (uiReadCnt > 0)
   {
    printf("%s, %d, uiReadCnt = %d\n", __FUNCTION__, __LINE__, uiReadCnt);
    retval = uiReadCnt;
    break;
   }
   else if (! TimeOut)
   {
    printf("%s, %d, uiReadCnt = %d\n", __FUNCTION__, __LINE__, uiReadCnt);
    break;
   }
  } 
 }
 
 return retval;
}

int SerialClose(int fd)
{
 int iRet = 0;
 /*先清空缓冲区,在关闭串口*/
 tcflush(fd,TCIFLUSH);
 iRet = close(fd);

 return iRet;
}

static void *Proc_SerialRead(void *arg)
{
 int fd = *(int *)arg;
 printf("%s, %d, fd = %d\n", __FUNCTION__, __LINE__, fd);
 while (1)
 {
  char acGetData[100] = {0};
  SerialRead(fd,acGetData, sizeof(acGetData), 3, 1);
  usleep(500*1000);
  printf("%s, %d, acGetData = %s\n", __FUNCTION__, __LINE__, acGetData);
 }

 return (void *)0;
}


int main(void)
{
 int iRet = -1;
 int fd = -1;
 static pthread_t  iThreadID = -1;
 
 char *scData = "111111111122314131341313413414141341341341";
 
 fd = OpenDev();
 if (-1 == fd)
 {
  printf("%s, %d, OpenDev Failed\n", __FUNCTION__, __LINE__);
  return 0;
 }
 printf("%s, %d, fd = %d\n", __FUNCTION__, __LINE__, fd);
 iRet = SetSerialParam(fd, 8, 1, 'n');
 printf("%s, %d, iRet = %d\n", __FUNCTION__, __LINE__, iRet);
 if (!pthread_create(&iThreadID, NULL, Proc_SerialRead, &fd));
 while(1)
 {
  
  SerialSend(fd, scData, strlen(scData) );
  sleep(1);
  
 }
 SerialClose(fd);
 return 0;
}

 

函数的构成模块主要有(1)打开设备(2)设置设备参数(3)读数据(4)写数据即发送数据(5)关闭设备。主函数中创建一个线程循环接收数据,主线程循环发送数据。

此代码已经编译自测过,可以正常工作。

原创粉丝点击