Linux下串列埠程式設計基礎

Quartz010發表於2016-12-19

串列埠知識
序列介面 (SerialInterface) 是指資料一位一位地順序傳送,其特點是通訊線路簡單,只要一對傳輸線就可以實現雙向通訊(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用於遠距離通訊,但傳送速度較慢。
1. 波特率
表示每秒傳輸的位元數,串列埠通訊的雙方必須保持一致才能通訊資料位,若波特率為115200,它表示什麼呢?
對於傳送斷,即每秒鐘傳送115200bit。
對於接收端,115200波特率意味著串列埠通訊在資料線上的取樣率為115200Hz.
2. 資料位
這是衡量通訊中實際資料位的引數。當計算機傳送一個資訊包,實際的資料不會是8位的,標準的值是5、6、7和8位。如何設定取決於你想傳送的資訊。比如,標準的ASCII碼是0~127(7位)。擴充套件的ASCII碼是0~255(8位)。如果資料使用簡單的文字(標準 ASCII碼),那麼每個資料包使用7位資料。每個包是指一個位元組,包括開始/停止位,資料位和奇偶校驗位。由於實際資料位取決於通訊協議的選取,術語“包”指任何通訊的情況。7位或8位資料中不僅僅是資料,還包括開始/停止位,資料位以及奇偶校驗位等
3. 停止位
用於表示單個包的最後一位。典型的值為1,1.5和2位。由於資料是在傳輸線上定時的,並且每一個裝置有其自己的時鐘,很可能在通訊中兩臺裝置間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鐘同步的機會。適用於停止位的位數越多,不同時鐘同步的容忍程度越大,但是資料傳輸率同時也越慢。
4. 奇偶校驗
一般奇偶校驗位的應用是由硬體完成的,軟體只需要在初始化的時候對MCU的串列埠外設設定一下
奇偶校驗的形式一般是在資料位後面跟一個奇偶校驗位,如果資料位是8位的,那麼加上校驗位就是9位資料;如果資料位是7位的,那麼加上校驗位就是8位資料,校驗位和資料之間沒有任何意義上的關聯,它不是資料的一部分,它只是關心資料中的“1”的個數是否為奇數(奇校驗),或者偶數(偶校驗)。奇校驗,奇校驗就是控制器在傳送的一個位元組或者一個資料幀裡面含有的“1”的個數進行奇數個的修正調整,這裡採用8位資料位+1位奇校驗位的形式舉個簡單的例子,A傳送資料0x35到B,後面緊跟一個奇校驗位X, 0x35的二進位制 = 0011 0101,可以看出8個資料位中一共有4個‘1’,那此時硬體會根據“1”的個數來設定X,這裡會將奇校驗位X調整為“1”,為什麼呢? 因為資料位的“1”的個數是偶數,而我們用的是奇校驗,所以為了達到有奇數個“1”的目的,調整奇偶校驗位X為“1”,那麼這9個位發出去就有奇數個“1”啦,當B接收的時候,B的硬體同樣會對接收到“資料+X”中“1”的個數進行計數判斷,如果是奇數個“1”,則認為資料接收正確,否則認為資料錯誤。而當A傳送資料0x25( 0010 0101)到B的時候,則X應該就是“0”了,因為要保證傳送出去的資料位+X位一共有奇數個“1”。
串列埠程式設計
串列埠程式設計的步驟:

a) 開啟串列埠
這裡是串列埠操作需要的一些標頭檔案

#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>      /*錯誤號定義*/

Linux下一切皆檔案,所以串列埠操作也是對檔案進行操作的
Linux的串列埠檔案位於 /dev下的

/dev/ttyS0     /* 串列埠0  */
/dev/ttyS1          /* 串列埠1  */

這裡我們通過標準的檔案開啟操作嘗試開啟串列埠 1
在這之前先建立一個 .c 檔案 這裡是 uatr2.c

#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>      /*錯誤號定義*/



int main()
{
  int fd;
  /*以讀寫方式開啟串列埠*/
  fd = open( "/dev/ttyS1", O_RDWR);
  if (-1 == fd)
  /* 不能開啟串列埠一*/
    perror(" 提示錯誤!");
  else
    printf("success\n");

  close(fd);

}

編譯 .c 檔案

gcc -o uart2.o uart2.c

執行

./uart2.o

這裡輸出success 說明串列埠能成功開啟

呼叫open()函式來代開串列埠裝置,對於串列埠的開啟操作,必須使用O_NOCTTY引數。
O_NOCTTY:表示開啟的是一個終端裝置,程式不會成為該埠的控制終端。如果不使用此標誌,任務一個輸入(eg:鍵盤中止訊號等)都將影響程式。
O_NDELAY:表示不關心DCD訊號線所處的狀態(埠的另一端是否啟用或者停止)。

1、 串列埠波特率設定
串列埠的設定主要是設定 struct termios 結構體的各成員值。

struct termio
{        unsigned short  c_iflag;                 /* 輸入模式標誌 */        
        unsigned short  c_oflag;                 /* 輸出模式標誌 */        
        unsigned short  c_cflag;                 /* 控制模式標誌*/        
        unsigned short  c_lflag;                 /* local mode flags */        
        unsigned char  c_line;                 /* line discipline */        
        unsigned char  c_cc[NCC];    /* control characters */
};

設定這個結構體很複雜,這裡就只說說常見的一些設定:
波特率設定
下面是修改波特率的程式碼:

struct  termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200);     /*設定為19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);

設定波特率的例子函式:

/**
*@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 fd1");  
        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;
        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] = 150; /* 設定超時15 seconds*/   
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)   
{
        perror("SetupSerial 3");   
        return (FALSE);  
}
return (TRUE);  
}

4、 讀寫串列埠
設定好串列埠之後,讀寫串列埠就很容易了,把串列埠當作檔案讀寫就是。
傳送資料

char  buffer[1024];
int    Length;
int    nByte;nByte = write(fd, buffer ,Length);

讀取串列埠資料
使用檔案操作read函式讀取,如果設定為原始模式(Raw Mode)傳輸資料,那麼read函式返回的字元數是實際串列埠收到的字元數。
可以使用操作檔案的函式來實現非同步讀取,如fcntl,或者select等來操作。

char  buff[1024];
int    Len;
int  readByte = read(fd,buff,Len);

5、 關閉串列埠
關閉串列埠就是關閉檔案。
close(fd);

下面是串列埠1的一個簡單的讀取與傳送的例子

#include     <stdio.h>      /*標準輸入輸出定義*/
#include     <stdlib.h>     /*標準函式庫定義*/
#include     <unistd.h>     /*Unix標準函式定義*/
#include     <sys/types.h>  /**/
#include     <sys/types.h>  /**/
#include     <sys/stat.h>   /**/
#include     <fcntl.h>      /*檔案控制定義*/
#include     <termios.h>    /*PPSIX終端控制定義*/
#include     <errno.h>      /*錯誤號定義*/

#define FALSE 0
#define TRUE 1

/***@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 fd1");
        return;
        }
   tcflush(fd,TCIOFLUSH);
   }
}
/**
{
int     fd = open( Dev, O_RDWR | O_NOCTTY | O_NDELAY);         //| O_NOCTTY | O_NDELAY
        if (-1 == fd)
                { /*設定資料位數*/
                        perror("Can't Open Serial Port");
                        return -1;
                }
        else
        return fd;

}
/**
*@breif         main()
*/
int main(int argc, char **argv)
{
        int fd , n;
        int nread;
        char buff[512];
        char *dev ="/dev/ttyS1";
        fd = OpenDev(dev);
        if (fd>0)
    set_speed(fd,19200);
        else
                {
                printf("Can't Open Serial Port!\n");
                exit(0);
                }
  if (set_Parity(fd,8,1,'N')== FALSE)
  {
    printf("Set Parity Error\n");
    exit(1);
  }
  while(1)
        {
                while((nread = read(fd,buff,512))>0)
                {
                printf("\nLen %d\n",nread);
                buff[nread+1]='\0';
                printf("\n%s",buff);

                n = write(fd, "I get\r", 4)        //如果收到資料則向串列埠傳送I Get
if (n < 0)
                                fputs("write() of 4 bytes failed!\n", stderr);

                }
        }
    //close(fd);
    //exit(0);
}

相關文章