C——Linux下的串口編程

之前在學習安信可A7模塊時,是在PC上使用串口調試助手做了GPS的座標數據信息的採集,同時分析了一些語句的含義。在這過程中,涉及到對嵌入式開發人員一個非常重要的知識:串口通信。在前篇也說到,我們將會自己寫程序來對GPS數據進行解析,而這些數據正是靠串口來傳輸的。所以,本篇博文將進行關於串口通信的學習。

一、串口接頭

首先我們得知道串口長什麼樣,常用的串口接頭有兩種,一種是9針串口(簡稱DB-9),一種是25針串口(簡稱DB-25)。每種接頭都有公頭和母頭之分,其中帶針狀的接頭是公頭,而帶孔狀的接頭是母頭。
以DB9爲例,如圖:
這裏寫圖片描述
各個針腳功能說明:
這裏寫圖片描述
在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、數據格式

我們有必要先弄清楚異步通信的數據格式。
這裏寫圖片描述
(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下。
這裏寫圖片描述
所以我們可以通過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的數據解析,便能更好從整體上理解、學習了。

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