(轉載)關於Linux下串口通信的一點心得

關於Linux下串口通信的一點心得

1.          打開串口

       與其他的關於設備編程的方法一樣,在 Linux 下,操作、控制串口也是通過操作起設備文件進行的。在 Linux 下,串口的設備文件是 /dev/ttyS0  /dev/ttyS1 等。因此要讀些串口,我們首先要打開串口:

       char *dev  = "/dev/ttyS0"; // 串口 1

       int    fd = open( dev, O_RDWR );

        //| O_NOCTTY | O_NDELAY      

       if (-1 == fd)   

                        

              perror("Can't Open Serial Port");

              return -1;       

       }    

       else 

              return fd;

      

2.          設置串口速度

       打開串口成功後,我們就可以對其進行讀寫了。首先要設置串口的波特率:

       int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,

                      B38400, B19200, B9600, B4800, B2400, B1200, B300, };

int name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300, 38400, 

                                   19200,  9600, 4800, 2400, 1200,  300, };

void set_speed(int fd, int speed){

       int   i;

       int   status;

       struct termios   Opt;

       tcgetattr(fd, &Opt);

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

              if  (speed == name_arr[i]) {    

                     tcflush(fd, TCIOFLUSH);    

                     cfsetispeed(&Opt, speed_arr[i]); 

                     cfsetospeed(&Opt, speed_arr[i]);  

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

                     if  (status != 0) {       

                            perror("tcsetattr fd"); 

                            return;    

                     }   

                     tcflush(fd,TCIOFLUSH);  

              } 

       }

}

3.          設置串口信息

這主要包括:數據位、停止位、奇偶校驗位這些主要的信息。

       /**

*@brief   設置串口數據位,停止位和效驗位

*@param  fd     類型   int  打開的串口文件句柄

*@param  databits 類型   int 數據位    取值  或者 8

*@param  stopbits 類型   int 停止位    取值爲 或者 2

*@param  parity  類型   int  效驗類型 取值爲 N,E,O,,S

*/

int set_Parity(int fd,int databits,int stopbits,int parity)

{

       struct termios options;

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

              perror("SetupSerial 1");    

              return(FALSE); 

       }

       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:    

              options.c_cflag |= CS8;

              break;  

       default:   

              fprintf(stderr,"Unsupported data 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':  /*as no parity*/  

           options.c_cflag &= ~PARENB;

              options.c_cflag &= ~CSTOPB;break; 

       default:  

              fprintf(stderr,"Unsupported parity/n");   

              return (FALSE); 

       } 

/* 設置停止位 */ 

switch (stopbits)

{  

       case 1:   

              options.c_cflag &= ~CSTOPB; 

              break; 

       case 2:   

              options.c_cflag |= CSTOPB; 

          break;

       default:   

               fprintf(stderr,"Unsupported stop bits/n"); 

               return (FALSE);

}

/* Set input parity option */

if (parity != 'n')  

       options.c_iflag |= INPCK;

tcflush(fd,TCIFLUSH);

options.c_cc[VTIME] = 0; /* 設置超時 0 seconds*/  

options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/

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

{

       perror("SetupSerial 3");  

       return (FALSE); 

}

return (TRUE); 

}

在上述代碼中,有兩句話特別重要:

options.c_cc[VTIME] = 0; /* 設置超時 0 seconds*/  

options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/

這兩句話決定了對串口讀取的函數 read() 的一些功能。我將着重介紹一下他們對 read() 函數的影響。

       對串口操作的結構體是

Struct{

       tcflag_t   c_iflag;    /* 輸入模式標記 */

       tcflag_t   c_oflag;   /* 輸出模式標記 */

       tcflag_t   c_cflag;   /* 控制模式標記 */

       tcflag_t   c_lflag;    /* 本地模式標記 */

       cc_t        c_line;     /* 線路規程 */

       cc_t        c_cc[NCCS];  /* 控制符號 */

} 

其中 cc_t       c_line 只有在一些特殊的系統程序 ( 比如,設置通過 tty 設備來通信的網絡協議 ) 中才會用。在數組 c_cc 中有兩個下標 (VTIME  VMIN) 對應的元素不是控制符,並且只是在原始模式下有效。只有在原始模式下,他們決定了 read() 函數在什麼時候返回。在標準模式下,除非設置了 O_NONBLOCK 選項,否則只有當遇到文件結束符或各含的字符都已經編輯完畢後才返回。

控制符 VTIME  VMIN 之間有着複雜的關係。 VTIME 定義要求等待的零到幾百號妙的是間量 ( 通常是一個 8位的 unsigned char 變量,取值不能大於 cc_t)  VMIN 定義了要求等待的最小字節數 ( 不是要求讀的字節數—— read() 的第三個參數纔是指定要求讀的最大字節數 ) ,這個字節數可能是 0 

l          如果 VTIME  0  VMIN 定義了要求等待讀取的最小字節數。函數 read() 只有在讀取了 VMIN 個字節的數據或者收到一個信號的時候才返回。

l          如果 VMIN  0  VTIME 定義了即使沒有數據可以讀取, read() 函數返回前也要等待幾百毫秒的時間量。這時, read() 函數不需要像其通常情況那樣要遇到一個文件結束標誌才返回 0 

l          如果 VTIME  VMIN 等不取 0  VTIME 定義的時當接收到底一個自己的數據後開始計算等待的時間量。如果當調用 read 函數時可以得到數據,計時器馬上開始計時。如果但調用 read 函數時還沒有任何數據可讀,則等接收到底一個字節的數據後,計時器開始計時。函數 read 可能會在讀取到 VMIN 個字節的數據後返回,也可能在計時完畢後返回,這主要取決於哪個條件首先實現。不過函數至少會讀取到一個字節的數據,因爲計時器是在讀取到第一個數據時開始計時的。

l          如果 VTIME  VMIN 都取 0 ,即使讀取不到任何數據,函數 read 也會立即返回。同時,返回值 0 表示 read 函數不需要等待文件結束標誌就返回了。

這就是這兩個變量對 read 函數的影響。我使用的讀卡器每次傳送的數據是 13 個字節,一開始,我把它們設置成

options.c_cc[VTIME] = 150

options.c_cc[VMIN] = 0;

結果,每次讀取的信息只有 8 個字節,剩下的 5 個字節要等到下一次打卡時才能收到。就是由於這個原因造成的。根據上面規則的第一條,我把 VTIME  0  VMIN=13 ,也就是正好等於一次需要接收的字節數。這樣就實現了一次讀取 13 個字節值。同時,得出這樣的結論,如果讀卡器送出的數據爲 n 個字節,那麼就把 VMIN=n ,這樣一次讀取的信息正好爲讀卡器送出的信息,並且讀取的時候不需要進行循環讀取。

 

4.          讀取數據

有了上面的函數後,我們設置了串口的基本信息,根據我們自己的實際情況,設置了相應的參數,就可以讀取數據了。

void getcardinfo(char *buff){

         int fd;

         int nread,count=0;

         char tempbuff[13];

         char *dev  = "/dev/ttyS0"; // 串口 1

         fd = OpenDev(dev);

         set_speed(fd,9600);

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

                   printf("Set Parity Error/n");

                   //return -1;

         }

         while (1) // 循環讀取數據

         {  

                   count=0;

                   //sleep(5000);

                   while(1)

                   {

                            if((nread = read(fd, tempbuff, 13))>0)

                            {

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

                                     memcpy(&buff[count],tempbuff,nread);

                                     count+=nread;

                            }

                            if(count==13)

                            {

                                     buff[count+1] = '/0';  

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

                                     break;

                            }

                   }

                   //break;

         }

         //return buff;

         close(fd);

         pthread_exit(NULL);

         //close(fd); 

         // exit (0);

}

這是我原來的程序,其實把 VMIN 設置以後,可以改成:

void getcardinfo(char *buff){

       int fd;

       int nread,count=0;

       char tempbuff[13];

       char *dev  = "/dev/ttyS0"; // 串口 1

       fd = OpenDev(dev);

       set_speed(fd,9600);

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

              printf("Set Parity Error/n");

              //return -1;

       }

       nread = read(fd, buff, 13)

       close(fd);

}

 

5.          程序完整代碼:

#include     <stdio.h>      /* 標準輸入輸出定義 */

#include     <stdlib.h>     /* 標準函數庫定義 */

#include     <unistd.h>     /*Unix 標準函數定義 */

#include     <sys/types.h> 

#include     <sys/stat.h>  

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

#include     <termios.h>    /*PPSIX 終端控制定義 */

#include     <errno.h>      /* 錯誤號定義 */

 

#define FALSE  -1

#define TRUE   0

/**

*@brief  設置串口通信速率

*@param  fd     類型 int  打開串口的文件句柄

*@param  speed  類型 int  串口速度

*@return  void

*/

int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,

                   B38400, B19200, B9600, B4800, B2400, B1200, B300, };

int name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300, 38400, 

                            19200,  9600, 4800, 2400, 1200,  300, };

void set_speed(int fd, int speed){

         int   i;

         int   status;

         struct termios   Opt;

         tcgetattr(fd, &Opt);

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

                   if  (speed == name_arr[i]) {    

                            tcflush(fd, TCIOFLUSH);    

                            cfsetispeed(&Opt, speed_arr[i]); 

                            cfsetospeed(&Opt, speed_arr[i]);  

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

                            if  (status != 0) {       

                                     perror("tcsetattr fd"); 

                                     return;    

                            }   

                            tcflush(fd,TCIOFLUSH);  

                   } 

         }

}

/**

*@brief   設置串口數據位,停止位和效驗位

*@param  fd     類型   int  打開的串口文件句柄

*@param  databits 類型   int 數據位    取值  或者 8

*@param  stopbits 類型   int 停止位    取值爲 或者 2

*@param  parity  類型   int  效驗類型 取值爲 N,E,O,,S

*/

int set_Parity(int fd,int databits,int stopbits,int parity)

{

         struct termios options;

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

                   perror("SetupSerial 1");    

                   return(FALSE); 

         }

         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:    

                   options.c_cflag |= CS8;

                   break;  

         default:   

                   fprintf(stderr,"Unsupported data 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':  /*as no parity*/  

             options.c_cflag &= ~PARENB;

                   options.c_cflag &= ~CSTOPB;break; 

         default:  

                   fprintf(stderr,"Unsupported parity/n");   

                   return (FALSE); 

         } 

/* 設置停止位 */ 

switch (stopbits)

{  

         case 1:   

                   options.c_cflag &= ~CSTOPB; 

                   break; 

         case 2:   

                   options.c_cflag |= CSTOPB; 

            break;

         default:   

                    fprintf(stderr,"Unsupported stop bits/n"); 

                    return (FALSE);

}

/* Set input parity option */

if (parity != 'n')  

         options.c_iflag |= INPCK;

tcflush(fd,TCIFLUSH);

options.c_cc[VTIME] = 0; /* 設置超時 15 seconds*/  

options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/

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

{

         perror("SetupSerial 3");  

         return (FALSE); 

}

return (TRUE); 

}

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

代碼說明:使用串口一測試的,發送的數據是字符,

但是沒有發送字符串結束符號,所以接收到後,後面加上了結束符號

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

 

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

int OpenDev(char *Dev)

{

         int     fd = open( Dev, O_RDWR );

        //| O_NOCTTY | O_NDELAY         

         if (-1 == fd)        

                                

                   perror("Can't Open Serial Port");

                   return -1;            

         }      

         else  

                   return fd;

}

void getcardinfo(char *buff){

         int fd;

         int nread,count=0;

         char tempbuff[13];

         char *dev  = "/dev/ttyS0"; // 串口 1

         fd = OpenDev(dev);

         set_speed(fd,9600);

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

                   printf("Set Parity Error/n");

                   //return -1;

         }

         while (1) // 循環讀取數據

         {  

                   count=0;

                   //sleep(5000);

                   while(1)

                   {

                            if((nread = read(fd, tempbuff, 13))>0)

                            {

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

                                     memcpy(&buff[count],tempbuff,nread);

                                     count+=nread;

                            }

                            if(count==13)

                            {

                                     buff[count+1] = '/0';  

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

                                     break;

                            }

                   }

                   //break;

         }

         //return buff;

         close(fd);

         pthread_exit(NULL);

         //close(fd); 

         // exit (0);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章