天天看點

C——Linux下的序列槽程式設計

之前在學習安信可A7子產品時,是在PC上使用序列槽調試助手做了GPS的坐标資料資訊的采集,同時分析了一些語句的含義。在這過程中,涉及到對嵌入式開發人員一個非常重要的知識:序列槽通信。在前篇也說到,我們将會自己寫程式來對GPS資料進行解析,而這些資料正是靠序列槽來傳輸的。是以,本篇博文将進行關于序列槽通信的學習。

一、序列槽接頭

首先我們得知道序列槽長什麼樣,常用的序列槽接頭有兩種,一種是9針序列槽(簡稱DB-9),一種是25針序列槽(簡稱DB-25)。每種接頭都有公頭和母頭之分,其中帶針狀的接頭是公頭,而帶孔狀的接頭是母頭。

以DB9為例,如圖:

C——Linux下的序列槽程式設計

各個針腳功能說明:

C——Linux下的序列槽程式設計

在TXD和RXD資料線上:

  (1)邏輯1為-3~-15V的電壓

  (2)邏輯0為3~15V的電壓

在RTS、CTS、DSR、DTR和DCD等控制線上:

  (1)信号有效(ON狀态)為3~15V的電壓

  (2)信号無效(OFF狀态)為-3~-15V的電壓

這是由通信協定RS-232C規定的(請看後文)。

注:一般我們需要的就是2,3,5接口,連接配接時是TXD接RXD,RXD接TXD,GND接GND。自己的TXD口接RXD口,自發自收,測試序列槽是否正常。

二、序列槽通信基礎知識

OK,知道了序列槽的樣子了,接下來就要更進一步,學習序列槽通信的基礎知識了。

1、什麼是序列槽通信?

序列槽通信(Serial Communication),是指外設和計算機間,通過資料信号線、地線等,按位進行傳輸資料的一種通訊方式。

序列槽是一種接口标準,它規定了接口的電氣标準,沒有規定接口插件電纜以及使用的協定。

2、序列槽通信協定

在序列槽通信中,常用的協定包括RS-232、RS-422和RS-485。

•RS-232:标準序列槽,最常用的一種串行通訊接口。有三種類型(A,B和C),它們分别采用不同的電壓來表示on和off。最被廣泛使用的是RS-232C,它将mark(on)比特的電壓定義為-3V到-12V之間,而将space(off)的電壓定義到+3V到+12V之間。傳送距離最大為約15米,最高速率為20kb/s。RS-232是為點對點(即隻用一對收、發裝置)通訊而設計的,其驅動器負載為3~7kΩ。是以RS-232适合本地裝置之間的通信。

•RS-422:最大傳輸距離為1219米,最大傳輸速率為10Mb/s。其平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能達到最大傳輸距離。隻有在很短的距離下才能獲得最高速率傳輸。一般100米長的雙絞線上所能獲得的最大傳輸速率僅為1Mb/s。

•RS-485:從RS-422基礎上發展而來的,最大傳輸距離約為1219米,最大傳輸速率為10Mb/s。平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能使用規定最長的電纜長度。隻有在很短的距離下才能獲得最高速率傳輸。一般100米長雙絞線最大傳輸速率僅為1Mb/s。

3、同步通信?異步通信?

同步通信:是一種比特同步通信技術,要求發收雙方具有同頻同相的同步時鐘信号,隻需在傳送封包的最前面附加特定的同步字元,使發收雙方建立同步,此後便在同步時鐘的控制下逐位發送/接收。如:SPI總線。

異步通信:指兩個互不同步的裝置通過計時機制或其他技術進行資料傳輸。也就是說,雙方不需要共同的時鐘。發送方可以随時傳輸資料,而接收方必須在資訊到達時準備好接收。如:序列槽(UART)。

接下來在後續的序列槽程式設計中讨論的都是異步通信,是以對同步通信不做過多的贅述了。

這裡提一下UART和USART,實際上,從字面意思即可了解: UART:universal asynchronous receiver and transmitter(通用異步收/發器)。 USART:universal synchronous asynchronous receiver and transmitter(通用同步/異步收/發器)。 USART在UART基礎上增加了同步功能,即USART是UART的增強型。 我常使用的S3C2440上就是支援的UART。

3、通信方式

•單工模式(Simplex Communication):單向的資料傳輸。通信雙方中,一方為發送端,一方則為接收端。資訊隻能沿一個方向傳輸,使用一根傳輸線。雙方是固定的。

•半雙工模式(Half Duplex):通信使用同一根傳輸線,既可以發送資料又可以接收資料,但不能同時進行發送和接收。資料傳輸允許資料在兩個方向上傳輸,但是,在任何時刻隻能由其中的一方發送資料,另一方接收資料。

•全雙工模式(Full Duplex)通信允許資料同時在兩個方向上傳輸。是以,全雙工通信是兩個單工通信方式的結合,它要求發送裝置和接收裝置都有獨立的接收和發送能力。在全雙工模式中,每一端都有發送器和接收器,有兩條傳輸線,資訊傳輸效率高。

4、資料格式

我們有必要先弄清楚異步通信的資料格式。

C——Linux下的序列槽程式設計

(1)起始位:起始位必須是持續一個比特時間的“0”,标志傳輸一個字元的開始。

(2)資料位:資料位緊跟在起始位之後,是通信中的真正有效資訊。資料位的位數可以由通信雙方共同約定,一般可以是5位、7位或8位。傳輸資料時先傳送字元的低位,後傳送字元的高位。

(3)奇偶校驗位:奇偶校驗位僅占一位,用于進行奇校驗或偶校驗,奇偶檢驗位不是必須有的。如果是奇校驗,需要保證傳輸的資料總共有奇數個“1”;如果是偶校驗,需要保證傳輸的資料總共有偶數個“1”。

  舉例來說,假設傳輸的資料位為01001100,如果是奇校驗,則奇校驗位為0(要確定總共有奇數個1),如果是偶校驗,則偶校驗位為1(要確定總共有偶數個1)。

  由此可見,奇偶校驗位僅是對資料進行簡單的置邏輯高位或邏輯低位,不會對資料進行實質的判斷,這樣做的好處是接收裝置能夠知道一個位的狀态,有可能判斷是否有噪聲幹擾了通信以及傳輸的資料是否同步。

(4)停止位:停止位可以是是1位、1.5位或2位,可以由軟體設定。它一定是“1”,标志着傳輸一個字元的結束。

(5)空閑位:空閑位是指從一個字元的停止位結束到下一個字元的起始位開始,表示線路處于空閑狀态,必須由高電平來填充。

好了,一些基礎知識暫時先到這裡,更深入的知識得自己自行了解了,接下來便是重頭戲,在Linux下的序列槽程式設計了。

===========================================

這裡同時可以參考我之前的博文:STM8序列槽 (有相關經驗的話)。對比一下兩者的差別來進行學習。

===========================================

1、首先是操作序列槽需要包含的頭檔案:

#include <stdio.h>   /*标準輸入輸出的定義*/
#include <errno.h>  /*錯誤号定義*/
#include <sys/stat.h>
#include <fcntl.h>  /*檔案控制定義*/
#include <termios.h>    /*PPSIX 終端控制定義*/
#include <stdlib.h> /*标準函數庫定義*/
#include <sys/types.h>
#include <unistd.h> /*UNIX 标準函數定義*/           

2、序列槽相關操作

打開序列槽:

我們都知道,在Linux下,除了網絡裝置,其餘的都是檔案的形式。序列槽裝置也一樣在/dev下。

C——Linux下的序列槽程式設計

是以我們可以通過open系統調用/函數來通路它。

示例:

fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY);

O_NOCTTY:可以告訴Linux這個程式不會成為這個端口上的“控制終端”.如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信号等等,會影響到你的程序。

O_NDELAY:标志則是告訴Linux,這個程式并不關心DCD信号線的狀态——也就是不關心端口另一端是否已經連接配接。

讀寫序列槽:

與普通檔案一樣,使用read,write函數。

示例:

read(fd,buff,8);

write(fd,buff,8);

序列槽屬性設定:

最基本的設定序列槽包括波特率設定,效驗位和停止位設定。這由通信雙方協定。

很多系統都支援POSIX終端(序列槽)接口.程式可以利用這個接口來改變終端的參數,比如,波特率,字元大小等等.要使用這個端口的話,你必須将

<termios.h>

頭檔案包含到你的程式中。這個頭檔案中定義了終端控制結構體和POSIX控制函數。

最重要的就是這個結構體:

struct termios
      {
      tcflag_t  c_iflag;  //輸入選項
      tcflag_t  c_oflag;  //輸出選項
      tcflag_t  c_cflag;  //控制選項
      tcflag_t  c_lflag;  //行選項
      cc_t      c_cc[NCCS]; //控制字元
      };            

其中我們更關注的是

c_cflag

控制選項。其中包含了波特率、資料位、校驗位、停止位的設定。

它可以支援很多常量名稱其中設定資料傳輸率為相應的資料傳輸率前要加上“B”。

c_cflag

成員不能直接對其初始化,而要将其通過與、或操作使用其中的某些選項。

設定序列槽屬性主要是配置termios結構體中的各個變量,大緻流程如下:

1.使用函數tcgetattr儲存原序列槽屬性

struct termios newtio,oldtio; tcgetattr(fd,&oldtio);

2.通過位掩碼的方式激活本地連接配接和接受使能選項:CLOCAL和CREAD

newtio.c_cflag | = CLOCAL | CREAD;

3.使用函數cfsetispeed和cfsetospeed設定資料傳輸率

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

4.通過位掩碼設定字元大小。

newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS8;

5.設定奇偶效驗位需要用到兩個termios中的成員:c_cflag和c_iflag。首先要激活c_cflag中的校驗位使能标志PARENB和是否進行奇偶效驗,同時還要激活c_iflag中的奇偶效驗使能。

設定奇校驗: newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); 設定偶校驗: newtio.c_iflag |= (INPCK|ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag |= ~PARODD;

6.激活c_cflag中的CSTOPB設定停止位。若停止位為1,則清除CSTOPB;若停止位為0,則激活CSTOPB。

newtio.c_cflag &= ~CSTOPB;

7.設定最少字元和等待時間。在對接收字元和等待時間沒有特别要求的情況下,可以将其設定為0。

newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0;

8.調用函數”tcflush(fd,queue_selector)”來處理要寫入引用的對象,queue_selector可能的取值有以下幾種。

TCIFLUSH:重新整理收到的資料但是不讀 TCOFLUSH:重新整理寫入的資料但是不傳送 TCIOFLUSH:同時重新整理收到的資料但是不讀,并且重新整理寫入的資料但是不傳送。

9.激活配置。在完成配置後,需要激活配置使其生效。使用tcsetattr()函數。

int tcsetattr(int filedes,int opt,const struct termios *termptr);

最後貼出序列槽配置的完整代碼:

/*********************************************************************************
  *      Copyright:  (C) 2017 TangBin<[email protected]>
  *                  All rights reserved.
  *
  *       Filename:  s_uart1.c
  *    Description:  This file 
  *                 
  *        Version:  1.0.0(06/04/2017)
  *         Author:  TangBin <[email protected]>
  *      ChangeLog:  1, Release initial version on "06/04/2017 07:51:59 PM"
  *                 
  ********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>


int set_serial(int fd,int nSpeed,int nBits,char nEvent,int nStop)
{
    struct termios newttys1,oldttys1;

     /*儲存原有序列槽配置*/
     if(tcgetattr(fd,&oldttys1)!=0) 
     {
          perror("Setupserial 1");
          return -1;
     }
     bzero(&newttys1,sizeof(newttys1));
     newttys1.c_cflag|=(CLOCAL|CREAD ); /*CREAD 開啟串行資料接收,CLOCAL并打開本地連接配接模式*/

     newttys1.c_cflag &=~CSIZE;/*設定資料位*/
     /*資料位選擇*/   
     switch(nBits)
     {
         case 7:
             newttys1.c_cflag |=CS7;
             break;
         case 8:
             newttys1.c_cflag |=CS8;
             break;
     }
     /*設定奇偶校驗位*/
     switch( nEvent )
     {
         case '0':  /*奇校驗*/
             newttys1.c_cflag |= PARENB;/*開啟奇偶校驗*/
             newttys1.c_iflag |= (INPCK | ISTRIP);/*INPCK打開輸入奇偶校驗;ISTRIP去除字元的第八個比特  */
             newttys1.c_cflag |= PARODD;/*啟用奇校驗(預設為偶校驗)*/
             break;
         case 'E':/*偶校驗*/
             newttys1.c_cflag |= PARENB; /*開啟奇偶校驗  */
             newttys1.c_iflag |= ( INPCK | ISTRIP);/*打開輸入奇偶校驗并去除字元第八個比特*/
             newttys1.c_cflag &= ~PARODD;/*啟用偶校驗*/
             break;
         case 'N': /*無奇偶校驗*/
             newttys1.c_cflag &= ~PARENB;
             break;
     }
     /*設定波特率*/
    switch( nSpeed )  
    {
        case 2400:
            cfsetispeed(&newttys1, B2400);
            cfsetospeed(&newttys1, B2400);
            break;
        case 4800:
            cfsetispeed(&newttys1, B4800);
            cfsetospeed(&newttys1, B4800);
            break;
        case 9600:
            cfsetispeed(&newttys1, B9600);
            cfsetospeed(&newttys1, B9600);
            break;
        case 115200:
            cfsetispeed(&newttys1, B115200);
            cfsetospeed(&newttys1, B115200);
            break;
        default:
            cfsetispeed(&newttys1, B9600);
            cfsetospeed(&newttys1, B9600);
            break;
    }
     /*設定停止位*/
    if( nStop == 1)/*設定停止位;若停止位為1,則清除CSTOPB,若停止位為2,則激活CSTOPB*/
    {
        newttys1.c_cflag &= ~CSTOPB;/*預設為一位停止位; */
    }
    else if( nStop == 2)
    {
        newttys1.c_cflag |= CSTOPB;/*CSTOPB表示送兩位停止位*/
    }

    /*設定最少字元和等待時間,對于接收字元和等待時間沒有特别的要求時*/
    newttys1.c_cc[VTIME] = 0;/*非規範模式讀取時的逾時時間;*/
    newttys1.c_cc[VMIN]  = 0; /*非規範模式讀取時的最小字元數*/
    tcflush(fd ,TCIFLUSH);/*tcflush清空終端未完成的輸入/輸出請求及資料;TCIFLUSH表示清空正收到的資料,且不讀取出來 */

     /*激活配置使其生效*/
    if((tcsetattr( fd, TCSANOW,&newttys1))!=0)
    {
        perror("com set error");
        return -1;
    }

    return 0;
}
           

下一篇将具體結合GPS的資料解析,便能更好從整體上了解、學習了。

繼續閱讀