串口通信協議和Linux下的串口編程

一、串口通信介紹:
串口通信(Serial Communications)的概念非常簡單,串口按位(bit)發送和接收字節,儘管比按位字節(byte)的並行通信慢,但是串口可以使用一根線發送數據的同時用另一根線接收數據。串口通信屬於異步串行通信方式。串口是一種接口標準,它規定了接口的電氣標準,沒有規定接口插件電纜以及使用的協議。

二、串口接頭:
常見的串口接頭有兩種,一種是9針串口(簡單DB-9),一種是25針串口(簡稱DB-25)。

以DB9爲例爲例,如圖:
在這裏插入圖片描述

母頭:泛指所有帶孔狀的接頭(5針朝下,從左到右依次是1~9)
公頭:泛指所有帶針狀的接頭(5針朝下,從右到左依次是1~9)

各引腳的功能如下圖
在這裏插入圖片描述
三、TTL電平和RS232電平:

1、TTL(Transistor-Transistor Logic),即晶體管-晶體管邏輯的簡稱,它是計算機處理器控制的設備內部各部分之間通信的標準技術。TTL電平信號應用廣泛,是因爲其數據表示採用二進制規定,+5V等價於邏輯”1”,0V等價於邏輯”0”。
數字電路中,由TTL電子元器件組成電路的電平是個電壓範圍,規定:
輸出高電平>=2.4V,輸出低電平<=0.4V;
輸入高電平>=2.0V,輸入低電平<=0.8V。

2、RS232電平
RS232電平是串口的一個標準。

在TXD和RXD數據線上:
  (1)邏輯1爲-3~-15V的電壓
  (2)邏輯0爲3~15V的電壓
在RTS、CTS、DSR、DTR和DCD等控制線上:
  (1)信號有效(ON狀態)爲3~15V的電壓
  (2)信號無效(OFF狀態)爲-3~-15V的電壓
這是由通信協議RS-232規定的。
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-232 是用正負電壓來表示邏輯狀態,與晶體管-晶體管邏輯集成電路(TTL)以高低電平表示邏輯狀態的規定正好相反。而我們 STM32 芯片使用的就是 TTL 電平,所以要實現 STM32 與計算機的串口通信,需要進行 TTL與 RS-232C 電平轉換,通常使用的電平轉換芯片是 MAX3232

四、串口通信

兩臺計算機進行通信時,最少可以只要三根線,分別爲RXD,TXD GND。

在這裏插入圖片描述
串口通信屬於異步串行通信方式
異步通信是指發送和接收端使用的是各自的時鐘,並且它是一種不連續的傳輸通信方式,一次通信只能傳輸一個字符數據(字符幀)。字符幀之間的間隙可以是任意的,下圖是異步串行通信幀格式:
在這裏插入圖片描述
起始位:接收方探測, 默認總線空閒時是高電平,一旦來一個位的低電平,說明一幀數據開始了。緊接着就是數據位,一幀數據有多少個數據位,由通信雙方定義。

數據位:在起始位之後,發送端發出的就是數據位,數據位的位數沒有嚴格限制(5-8位都可以)。低位在前,高位在後。由低位向高位逐位發送。

校驗位:數據位發完了,一般會跟奇偶校驗位(奇校驗、偶校驗、無校驗),驗證收發雙方的數據是否正常。

奇校驗: 數據位加上校驗位保證1的個數爲奇數;

偶校驗: 數據位加上校驗位保證1的個數爲偶數

停止位:數據發完之後,會發一個停止位,停止位的寬度一般是: 1, 1.5, 2

空閒位:空閒位是指從一個字符的停止位結束到下一個字符的起始位開始,表示線路處於空閒狀態,必須由高電平來填充。

五、Linux串口編程

在Linux系統中,一切皆文件,所以串口設備也是一類文件,學習過Linux驅動程序的學員都知道,Linux有三類設備:字符設備,塊設備,網絡設備。那麼串口設備屬於字符設備。所以串口設備的命名一般爲/dev/ttySn(n = 0、1、2…),如果該串口爲USB轉串口,可能名稱爲/dev/ttyUSBn(n = 0、1、2…),不同的平臺下串口的名稱是不同的,且串口的名稱也是可以更改的。如何更改?在板卡對應的Linux驅動中更改。

在Linux下操作串口,那麼也就是跟操作一個文件一樣,既然是文件,也就可以使用標準的文件操作API來操作。

在進行文件操作時,我們先看一下操作串口需要包含的有文件:

#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 標準函數定義*/

1、打開串口

fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY);
open的第二個參數可以設置爲如下:

O_RDONLY:以只讀方式打開文件

O_WRONLY:以只寫方式打開文件

O_RDWR:以讀寫方式打開文件
O_RDONLY和O_WRONLY和O_RDWR三個中必選一個

O_APPEND:寫入數據時添加到文件末尾

O_CREATE:如果文件不存在則產生該文件,使用該標誌需要設置訪問權限位mode_t

O_EXCL:指定該標誌,並且指定了O_CREATE標誌,如果打開的文件存在則會產生一個錯誤

O_TRUNC:如果文件存在並且成功以寫或者只寫方式打開,則清除文件所有內容,使得文件長度變爲0

O_NOCTTY:如果打開的是一個終端設備,這個程序不會成爲對應這個端口的控制終端,如果沒有該標誌,任何一個輸入,例如鍵盤中止信號等,都將影響進程。

O_NONBLOCK:該標誌與早期使用的O_NDELAY標誌作用差不多。程序不關心DCD信號線的狀態,如果指定該標誌,進程將一直在休眠狀態,直到DCD信號線爲0。

2、寫串口

write(fd, buf,sizeof(buf) );

和寫入其他設備文件的方式相同,write函數也會返回發送數據的字節數或者發生錯誤的時候返回-1。通常,發送數據最常見的錯誤就是EIO,當調制解調器或者數據鏈路將Data Carrier Detect(DCD)信號線弄掉了,就會發生這個錯誤,而且,直至關閉端口這個情況會一直持續。

3、讀串口

read( fd, buf, sieof(buf));

當端口在raw data mode操作模式下,那麼read系統調用將返回從串口輸入緩衝區中實際得到的字節數,如果沒有數據可讀,那麼該系統調用將會被阻塞(block)直到有數據爲止,如果超過一定時間仍然沒有數據可讀,那麼將返回一個錯誤(讀錯誤)。read函數也可以立即返回(即在沒有數據可讀的情況下也能立即返回,而不是被阻塞),需要做如下工作:
fcntl(fd, F_SETFL, FNDELAY);

FNDELAY選項的意思是read函數在沒有數據可讀的情況下立即返回0。需要重新回到阻塞模式(blocking)下,那麼在調用fcntl()的時候不要加上FNDELAY這個選項:
fcntl(fd, F_SETFL, 0);

4、關閉串口

關閉串口使用close系統調用,例如:
close(fd);

5、串口屬性配置

如果不設置串口的波特率,數據位,停止位,校驗位的情況下,Linux下默認設置的屬性值爲:

波特率:9600

數據位:8

校驗位:n(表示無)

停止位:1

在不設置串口屬性值的情況下,也可以讀寫串口值。

但是在實際產品開發過程中,還是會根據不同的應用場景來設置串口的屬性。很多系統都支持POSIX終端(串口)接口,程序可以利用這個接口來改變終端的參數,比如波特率,字符大小等,要利用這個端口的話,你必須將<termios.h>頭文件包含到你的程序中,這個頭文件中定義了終端控制結構體和POSIX控制函數。

設置串口屬性先要tcgetattr()用來取得設備終端屬性,然後中間設置波特率,設置停止位,校驗位,數據位等,最後用tcsetattr()設置終端的屬性。調用這兩個函數的時候,需要提供一個包含着所有串口選項的tremios結構體:

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

通過termios結構體的c_cflag成員可以控制串口波特率、數據位、校驗位、停止位以及硬件流控制等等,位成員有:
在這裏插入圖片描述在這裏插入圖片描述
1)設置串口波特率
使用函數cfsetispeed設置輸入波特率cfsetospeed設置輸出波特率

struct termios options;

tcgetattr(fd, &options);

cfsetispeed(&options, B19200);

cfsetospeed(&options, B19200);

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

options.c_cflag | = CLOCAL | CREAD;

3)設置數據位

數據位指的是每字節中實際數據所佔的比特數。要修改數據位可以通過修改termios結構體中c_cflag成員來實現。CS5、CS6、CS7和CS8分別表示數據位爲5、6、7和8。值得注意的是,在設置數據位時,必須先使用CSIZE做位屏蔽。

options.c_cflag &= ~CSIZE;

options.c_cflag |= CS8;

4)設置校驗位

奇偶校驗可以選擇偶校驗、奇校驗等方式,也可以不使用校驗。如果要設置爲偶校驗的話,首先要將termios結構體中c_cflag設置PARENB標誌,並清除PARODD標誌。如果要設置奇校驗,要同時設置termios結構體中c_cflag設置PARENB標誌和PARODD標誌。激活c_iflag中的奇偶效驗使能,如果不想使用任何校驗的話,清除termios結構體中c_cflag的PARENB位。

設置奇校驗

options.c_cflag |= PARENB;

options.c_cflag |= PARODD;

options.c_iflag |= (INPCK | ISTRIP); //INPCK 打開輸入奇偶校驗,ISTRIP 去掉字符第8位

設置偶校驗

options.c_iflag |= (INPCK|ISTRIP);

options.c_cflag |= PARENB;

options.c_cflag |= ~PARODD;

設置無校驗位

options.c_cflag &= ~PARENB;

5)設置停止位

用CSTOPB只能設置1位或2位,通過設置c_cflag中的CSTOPB設置停止位。若停止位爲1,則清除CSTOPB;若停止位爲2,則激活CSTOPB。

一位停止位

options.c_cflag &=~CSTOP;

兩位停止位

options.c_cflag |=CSTOPB;

6)設置最少字符和等待時間

在對接收字符和等待時間沒有特別要求的情況下,可以將其設置位0.

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

7)調用函數”tcflush(fd,queue_selector)”來處理要寫入引用的對象

queue_selector可能的取值有以下幾種

TCIFLUSH:刷新收到的數據但是不讀

TCOFLUSH:刷新寫入的數據但是不傳送

TCIOFLUSH:同時刷新收到的數據但是不讀,並且刷新寫入的數據但是不傳送。

7)最後tcsetattr()設置終端的屬性

*int tcsetattr(int fd, int optional_actions, const struct termios termios_p);

第一個參數:fd爲打開的終端文件描述符

第二個參數:optional_actions用於控制修改起作用的時間。
可以取如下值:
optional_actions可以取如下的值:

TCSANOW:不等數據傳輸完畢就立即改變屬性。

TCSADRAIN:等待所有數據傳輸結束才改變屬性。

TCSAFLUSH:等待所有數據傳輸結束,清空輸入輸出緩衝區才改變屬性。

第三個參數:結構體termios_p中保存了要修改的參數。

調用該函數出現的錯誤信息:

EBADF:非法的文件描述符。

EINTR:tcsetattr函數調用被信號中斷。

EINVAL:參數optional_actions使用了非法值,或參數termios中使用了非法值。

六、簡單的Linux串口編程

讀串口程序

#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> //標準函數庫定義
#include <unistd.h> //unix標準函數定義
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定義
#include <termios.h> //POSIX終端控制定義

int main(int argc, char **argv)
{
        int fd = -1;
        int rv = -1;
        char buf[128];

        struct termios options;//該結構體包含串口的選項
        /*struct termios
         {
            tcflag_t  c_iflag;  //輸入選項
            tcflag_t  c_oflag;  //輸出選項
            tcflag_t  c_cflag;  //控制選項
            tcflag_t  c_lflag;  //行選項
            cc_t      c_cc[NCCS]; //控制字符
         }*/

        fd_set rset;

        fd = open("/dev/ttyUSB3",O_RDWR|O_NOCTTY|O_NDELAY);//打開串口設備
        if(fd < 0)
        {
                printf("open ttyUSB3 failure:%s\n",strerror(errno));
                close(fd);
                return -1;
        }

        printf("open ttyUSB3 successfuly\n");

        memset(&options, 0, sizeof(options));

        rv = tcgetattr(fd, &options);//獲取原有的串口屬性的配置

        if(rv != 0 )
        {
                printf("tcgetattr failure:%s\n", strerror(errno));
                return -2;
        }
        options.c_cflag|=(CLOCAL|CREAD);//CREAD開啓串行數據接收,CLOCAL並打開本地連接模式
        //options.c_cflag &= ~(ECHO |ICANON |ECHOE);
        options.c_cflag &= ~CSIZE;//先使用CSIZE做位屏蔽
        options.c_cflag |= CS8;//設置8位數據位
        options.c_cflag &= ~PARENB;//無校驗位

        /*設置115200波特率*/
        cfsetispeed(&options,B115200);//設置輸入波特率
        cfsetospeed(&options,B115200);//設置輸出波特率

        options.c_cflag &= ~CSTOPB;//設置一位停止位
        options.c_cc[VTIME] = 0;//非規範模式讀取時的超時時間
        options.c_cc[VMIN] = 0;//非規範模式讀取時的最小字符數

        tcflush(fd, TCIFLUSH);//tcflush清空終端未完成的輸入/輸出請求及數據;TCIFLUSH表示清空正收到的數據,且不>
讀取出來

        rv=tcsetattr(fd, TCSANOW, &options);//設置終端的屬性
        if(rv != 0)
        {
                printf("tcsetattr failure:%s\n",strerror(errno));
                close(fd);
                return -3;
        }


        while(1)
        {
                FD_ZERO(&rset);
                FD_SET(fd, &rset);

                rv = select(fd+1, &rset, NULL, NULL,NULL);
                if(rv < 0)
                {
                        printf("select failure:%s\n",strerror(errno));
                        close(fd);
                }
                if(rv ==0)
                {
                        printf("select timeout\n");
                        close(fd);
                        }

                memset(buf, 0, sizeof(buf));

                rv = read(fd, buf, sizeof(buf));
                if(rv <0)
                {
                        printf("read from buf failure:%s\n",strerror(errno));
                        close (fd);
                }
                printf("read from buf %d  %s bytes\n",rv, buf);
                sleep(3);
        }

        close(fd);
        return 0;
}

寫串口程序

#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> //標準函數庫定義
#include <unistd.h> //unix標準函數定義
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定義
#include <termios.h> //POSIX終端控制定義

#define BUF   "[email protected]"
int main(int argc, char **argv)
{
        int fd = -1;
        int rv = -1;


        struct termios options;//該結構體包含串口的選項
        /*struct termios
         {
            tcflag_t  c_iflag;  //輸入選項
            tcflag_t  c_oflag;  //輸出選項
            tcflag_t  c_cflag;  //控制選項
            tcflag_t  c_lflag;  //行選項
            cc_t      c_cc[NCCS]; //控制字符
         }*/



        fd = open("/dev/ttyUSB3",O_RDWR|O_NOCTTY|O_NDELAY);//打開串口設備
        if(fd < 0)
        {
                printf("open ttyUSB3 failure:%s\n",strerror(errno));
                close(fd);
                return -1;
        }

        printf("open ttyUSB3 successfuly\n");

        memset(&options, 0, sizeof(options));

        rv = tcgetattr(fd, &options);//獲取原有的串口屬性的配置

        if(rv != 0 )
        {
                printf("tcgetattr failure:%s\n", strerror(errno));
                return -2;
        }
        options.c_cflag|=(CLOCAL|CREAD);//CREAD開啓串行數據接收,CLOCAL並打開本地連接模式
        //options.c_cflag &= ~(ECHO |ICANON |ECHOE); 
        options.c_cflag &= ~CSIZE;//先使用CSIZE做位屏蔽
        options.c_cflag |= CS8;//設置8位數據位
        options.c_cflag &= ~PARENB;//無校驗位

        /*設置115200波特率*/
        cfsetispeed(&options,B115200);//設置輸入波特率
        cfsetospeed(&options,B115200);//設置輸出波特率

        options.c_cflag &= ~CSTOPB;//設置一位停止位

        options.c_cc[VTIME] = 0;//非規範模式讀取時的超時時間
         options.c_cc[VMIN] = 0;//非規範模式讀取時的最小字符數

        tcflush(fd, TCIFLUSH);//tcflush清空終端未完成的輸入/輸出請求及數據;TCIFLUSH表示清空正收到的數據,且不>
讀取出來

        rv=tcsetattr(fd, TCSANOW, &options);//設置終端的屬性
        if(rv != 0)
        {
                printf("tcsetattr failure:%s\n",strerror(errno));
                close(fd);
                return -3;
        }

        while(1)
        {

                rv = write(fd, BUF,strlen(BUF)) ;
                if(rv < 0)
                {
                        printf("write error:%s\n",strerror(errno));
                        close(fd);
                }
                printf("write to read successfuly %d %s \n",rv, BUF);

                sleep(3);
        }

       close(fd);
        return 0;
}

總的來說進行串口通信總有一個是主動方一個是被動方,而且二者傳輸數據時,會有一定的協商好的數據格式,二者發送接收都按照此數據格式進行。此篇記錄自己學習串口編程的學習,更多關於串口的學習,請關注我接下來的博客

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