1. 程式人生 > >關於Linux下串列埠通訊的一點心得

關於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 資料位取值為 7 或者8

*@param  stopbits 型別  int 停止位取值為 1 或者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中有兩個下標(VTIMEVMIN)對應的元素不是控制符,並且只是在原始模式下有效。只有在原始模式下,他們決定了read()函式在什麼時候返回。在標準模式下,除非設定了O_NONBLOCK選項,否則只有當遇到檔案結束符或各行的字元都已經編輯完畢後才返回。

控制符VTIMEVMIN之間有著複雜的關係。VTIME定義要求等待的零到幾百毫秒的時間量(通常是一個8位的unsigned char變數,取值不能大於cc_t)VMIN定義了要求等待的最小位元組數(不是要求讀的位元組數——read()的第三個引數才是指定要求讀的最大位元組數),這個位元組數可能是0

l         如果VTIME0VMIN定義了要求等待讀取的最小位元組數。函式read()只有在讀取了VMIN個位元組的資料或者收到一個訊號的時候才返回。

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

l         如果VTIMEVMIN都不取0VTIME定義的是當接收到第一個位元組的資料後開始計算等待的時間量。如果當呼叫read函式時可以得到資料,計時器馬上開始計時。如果當呼叫read函式時還沒有任何資料可讀,則等接收到第一個位元組的資料後,計時器開始計時。函式read可能會在讀取到VMIN個位元組的資料後返回,也可能在計時完畢後返回,這主要取決於哪個條件首先實現。不過函式至少會讀取到一個位元組的資料,因為計時器是在讀取到第一個資料時開始計時的。

l         如果VTIMEVMIN都取0,即使讀取不到任何資料,函式read也會立即返回。同時,返回值0表示read函式不需要等待檔案結束標誌就返回了。

這就是這兩個變數對read函式的影響。我使用的讀卡器每次傳送的資料是13個位元組,一開始,我把它們設定成

options.c_cc[VTIME] = 150

options.c_cc[VMIN] = 0;

結果,每次讀取的資訊只有8個位元組,剩下的5個位元組要等到下一次打卡時才能收到。就是由於這個原因造成的。根據上面規則的第一條,我把VTIME0VMIN=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);

}