串口termios函數

https://blog.csdn.net/williamwang2013/article/details/8560552

linux使用terminfo數據庫來描述終端能力以及調用對應功能的方法
POSIX定義了完成終端I/O的標準方法:TERMIOS函數族

#include <termios.h>
#include <unistd.h>
struct termios{
              tcflag_t c_iflag;      /* input modes */
              tcflag_t c_oflag;      /* output modes */
              tcflag_t c_cflag;      /* control modes */
              tcflag_t c_lflag;      /* local modes */
              cc_t c_cc [NCCS];      /* control chars */
}

其具體意義如下。

c_iflag:輸入模式標誌,控制終端輸入方式,具體參數如表6.3所示。

表6.3 c_iflag參數表

鍵 值 說 明
IGNBRK 忽略BREAK鍵輸入
BRKINT 如果設置了IGNBRK,BREAK鍵的輸入將被忽略,如果設置了BRKINT ,將產生SIGINT中斷
IGNPAR 忽略奇偶校驗錯誤
PARMRK 標識奇偶校驗錯誤
INPCK 允許輸入奇偶校驗
ISTRIP 去除字符的第8個比特
INLCR 將輸入的NL(換行)轉換成CR(回車)
IGNCR 忽略輸入的回車
ICRNL 將輸入的回車轉化成換行(如果IGNCR未設置的情況下)
IUCLC 將輸入的大寫字符轉換成小寫字符(非POSIX)
IXON 允許輸入時對XON/XOFF流進行控制
IXANY 輸入任何字符將重啓停止的輸出
IXOFF 允許輸入時對XON/XOFF流進行控制
IMAXBEL 當輸入隊列滿的時候開始響鈴,Linux在使用該參數而是認爲該參數總是已經設置

c_oflag:輸出模式標誌,控制終端輸出方式,具體參數如表6.4所示。

表6.4 c_oflag參數

鍵 值 說 明
OPOST 處理後輸出
OLCUC 將輸入的小寫字符轉換成大寫字符(非POSIX)
ONLCR 將輸入的NL(換行)轉換成CR(回車)及NL(換行)
OCRNL 將輸入的CR(回車)轉換成NL(換行)
ONOCR 第一行不輸出回車符
ONLRET 不輸出回車符
OFILL 發送填充字符以延遲終端輸出
OFDEL 以ASCII碼的DEL作爲填充字符,如果未設置該參數,填充字符將是NUL(‘\0’)(非POSIX)
NLDLY 換行輸出延時,可以取NL0(不延遲)或NL1(延遲0.1s)
CRDLY 回車延遲,取值範圍爲:CR0、CR1、CR2和 CR3
TABDLY 水平製表符輸出延遲,取值範圍爲:TAB0、TAB1、TAB2和TAB3
BSDLY 空格輸出延遲,可以取BS0或BS1
VTDLY 垂直製表符輸出延遲,可以取VT0或VT1
FFDLY 換頁延遲,可以取FF0或FF1

c_cflag:控制模式標誌,指定終端硬件控制信息,具體參數如表6.5所示。

表6.5 c_oflag參數

鍵 值 說 明
CBAUD 波特率(4+1位)(非POSIX)
CBAUDEX 附加波特率(1位)(非POSIX)
CSIZE 字符長度,取值範圍爲CS5、CS6、CS7或CS8
CSTOPB 設置兩個停止位
CREAD 使用接收器
PARENB 使用奇偶校驗
PARODD 對輸入使用奇偶校驗,對輸出使用偶校驗
HUPCL 關閉設備時掛起
CLOCAL 忽略調制解調器線路狀態
CRTSCTS 使用RTS/CTS流控制

c_lflag:本地模式標誌,控制終端編輯功能,具體參數如表6.6所示。

表6.6 c_lflag參數

鍵 值 說 明
ISIG 當輸入INTR、QUIT、SUSP或DSUSP時,產生相應的信號
ICANON 使用標準輸入模式
XCASE 在ICANON和XCASE同時設置的情況下,終端只使用大寫。如果只設置了XCASE,則輸入字符將被轉換爲小寫字符,除非字符使用了轉義字符(非POSIX,且Linux不支持該參數)
ECHO 顯示輸入字符
ECHOE 如果ICANON同時設置,ERASE將刪除輸入的字符,WERASE將刪除輸入的單詞
ECHOK 如果ICANON同時設置,KILL將刪除當前行
ECHONL 如果ICANON同時設置,即使ECHO沒有設置依然顯示換行符
ECHOPRT 如果ECHO和ICANON同時設置,將刪除打印出的字符(非POSIX)
TOSTOP 向後臺輸出發送SIGTTOU信號

c_cc[NCCS]:控制字符,用於保存終端驅動程序中的特殊字符,如輸入結束符等。c_cc中定義瞭如表6.7所示的控制字符。

表6.7 c_cc支持的控制字符

說 明
VINTR Interrupt字符
VEOL 附加的End-of-file字符
VQUIT Quit字符
VTIME 非規範模式讀取時的超時時間
VERASE Erase字符
VSTOP Stop字符
VKILL Kill字符
VSTART Start字符
VEOF End-of-file字符
VSUSP Suspend字符
VMIN 非規範模式讀取時的最小字符數

1.模式

Cbreak模式

除了"Del"和"Ctrl"鍵外,接受其他所有字符輸入, raw()和cbreak()兩個函數都可以禁止行緩衝(line buffering)。區別是:在raw()函數模式下,處理掛起(CTRLZ)、 中斷或退出(CTRLC) 等控制字符時,將直接傳送給程序去處理而不產生終端信號;而在 cbreak()模式下,控制字符將被終端驅動程序解釋成其它字符。

Raw模式

可以禁止行緩衝(line buffering),處理掛起(CTRLZ)、中斷或退出(CTRLC)等控制字符時,將直接傳送給程序去處理而不產生終端信號
詳情查看curses庫中的cbreak()和 raw()函數。
另外可以用命令直接操作
stty -a 這個命令用來查看當前終端的設置情況
stty sane 如果不小心設錯了終端模式,可用這個命令恢復,另一種恢復辦法是在設置之前保存當前stty設置,在需要時再讀出
stty -g > save_stty 將當前設置保存到文件save_atty中
stty $(cat save_stty) 讀出save_atty文件,恢復原終端設置

2.獲取和設置終端屬性

int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, struct termios *termios_p);
其中optional_actions決定什麼時候起作用,可取如下值
TCSANOW:不等數據傳輸完畢就立即改變屬性。
TCSADRAIN:等待所有數據傳輸結束才改變屬性。
TCSAFLUSH:清空輸入輸出緩衝區才改變屬性。

注意:當進行多重修改時,應當在這個函數之後再次調用 tcgetattr() 來檢測是否所有修改都成功實現。

3.波特率函數

獲取波特率

speed_t cfgetospeed(struct termios *termios_p);
speed_t cfgetispeed(struct termios *termios_p);

設置波特率

int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetispeed(struct termios *termios_p, speed_t speed);

speed取值必須是以下常量之一:
B0 B50 B75 B110 B134 B150 B200 B300 B600 B1200 B1800 B2400 B4800 B9600 B19200 B38400 B57600 B115200 B230400
其中:零值 B0 用來中斷連接。如果指定了 B0,不應當再假定存在連接。通常,這樣將斷開連接。CBAUDEX 是一個掩碼,指示高於 POSIX.1 定義的速度的那一些 (57600 及以上)。因此,B57600 & CBAUDEX 爲非零。

4.線路控制函數

int tcdrain (int fd); //等待所有寫入fd中的數據輸出

int tcflush (int fd, int queue_selector); //丟棄要寫入fd,但尚未傳輸的數據,或者收到但是尚未讀取的數據。
取決於queue_selector 的值:
TCIFLUSH: 刷新收到的數據但是不讀
TCOFLUSH: 刷新寫入的數據但是不傳送
TCIOFLUSH: 同時刷新收到的數據但是不讀,並且刷新寫入的數據但是不傳送

int tcflow (int fd, int action); //掛起 fd 上的數據傳輸或接收。
取決於 action 的值:
TCOOFF :掛起輸出
TCOON :重新開始被掛起的輸出
TCIOFF :發送一個 STOP 字符,停止終端設備向系統傳送數據
TCION :發送一個 START 字符,使終端設備向系統傳輸數據
打開一個終端設備時的默認設置是輸入和輸出都沒有掛起。

int tcsendbreak (int fd, int duration); //傳送連續的 0 值比特流,持續一段時間,如果終端使用異步串行數據傳輸的話。
如果 duration 是 0,它至少傳輸 0.25 秒,不會超過 0.5 秒。如果 duration 非零,它發送的時間長度由實現定義。
如果終端並非使用異步串行數據傳輸,tcsendbreak() 什麼都不做。

5.進程組控制函數

pid_t tcgetpgrp(int fd); //獲取前臺進程組的進程組ID

int tcsetpgrp(int fd, pid_t pgrpid); //設置前臺進程組的進程組ID

pid_t tcgetsid(int fd); //獲取會話首進程的進程組ID

6.cfmakeraw

設置終端的 Raw 模式 ,設置終端屬性:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

7.其他

#include <stdio.h>
char *ctermid(char *s); //決定控制終端名稱

#include <unistd.h>
int isatty(int desc); //判斷描述符是否爲終端

char *ttyname(int desc); //返回終端名稱

int getopt(int argc,char * const argv[ ],const char * optstring); //分析命令行參數
其中argc和argv是由main()傳遞的參數個數和內容,optstring 則代表欲處理的選項字符串。

修改終端控制字符示例

#include
#include
#include
#include
int main(void)
{
    //term用於存儲獲得的終端參數信息
    struct termios term;
    int err;
    
    //獲得標準輸入的終端參數,將獲得的信息保存在term變量中
    if(tcgetattr(STDIN_FILENO,&term)==-1)
    {
	    perror("Cannot get standard input description");
	    return 1;
    }
    
    //修改獲得的終端信息的結束控制字符
    term.c_cc[VEOF]=(cc_t)0x07;
    
    //使用tcsetattr函數將修改後的終端參數設置到標準輸入中
    //err用於保存函數調用後的結果
    err=tcsetattr(STDIN_FILENO,TCSAFLUSH,&term);
    
    //如果err爲-1或是出現EINTR錯誤(函數執行被信號中斷),
    //給出相關出錯信息
    if(err==-1 && err==EINTR)
    {
	    perror("Failed to change EOF character");
	    return 1;
    }
    return 0;
}

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

設置波特率的例子函數

/**
*@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(fd1, TCSANOW, &Opt);
		    if (status != 0) 
		    {
			    perror("tcsetattr fd1");
			    return;
		    }
		    
		    tcflush(fd,TCIOFLUSH);
	    }
    }
}

設置效驗的函數

/**
*@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 sizen"); 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 parityn");
		    return (FALSE);
}

設置停止位

switch (stopbits)
{
    case 1:
	    options.c_cflag &= ~CSTOPB;
	    break;
    case 2:
	    options.c_cflag |= CSTOPB;
	    break;
    default:
	    fprintf(stderr,"Unsupported stop bitsn");
	    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);
}

在上述代碼中,有兩句話特別重要:

options.c_cc[VTIME] = 0; /* 設置超時0 seconds*/  
options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/

這兩句話決定了對串口讀取的函數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中有兩個下標(VTIME和VMIN)對應的元素不是控制符,並且只是在原始模式下有效。只有在原始模式下,他們決定了read()函數在什麼時候返回。在標準模式下,除非設置了O_NONBLOCK選項,否則只有當遇到文件結束符或各行的字符都已經編輯完畢後才返回。

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

l) 如果VTIME取0,VMIN定義了要求等待讀取的最小字節數。函數read()只有在讀取了VMIN個字節的數據或者收到一個信號的時候才返回。

  1. 如果VMIN取0,VTIME定義了即使沒有數據可以讀取,read()函數返回前也要等待幾百毫秒的時間量。這時,read()函數不需要像其通常情況那樣要遇到一個文件結束標誌才返回0。

  2. 如果VTIME和VMIN都不取0,VTIME定義的是當接收到第一個字節的數據後開始計算等待的時間量。如果當調用read函數時可以得到數據,計時器馬上開始計時。如果當調用read函數時還沒有任何數據可讀,則等接收到第一個字節的數據後,計時器開始計時。函數read可能會在讀取到VMIN個字節的數據後返回,也可能在計時完畢後返回,這主要取決於哪個條件首先實現。不過函數至少會讀取到一個字節的數據,因爲計時器是在讀取到第一個數據時開始計時的。

  3. **如果VTIME和VMIN都取0,即使讀取不到任何數據,函數read也會立即返回。**同時,返回值0表示read函數不需要等待文件結束標誌就返回了。

這就是這兩個變量對read函數的影響。

注意的問題:
如果不是開發終端之類的,只是串口傳輸數據,而不需要串口來處理,那麼使用原始模式(Raw Mode)方式來通訊,設置方式如下:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
options.c_oflag &= ~OPOST; /*Output*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章