1. 程式人生 > >linux串列埠程式設計(termios結構體說明)

linux串列埠程式設計(termios結構體說明)

termios結構體說明

https://www.cnblogs.com/li-hao/archive/2012/02/19/2358158.html

termios結構體中,該結構體一般包括如下的成員:
tcflag_t       c_iflag;      
tcflag_t       c_oflag;      
tcflag_t       c_cflag;      
tcflag_t       c_lflag;     
cc_t            c_cc[NCCS];

  
 
 其具體意義如下
 
c_iflag:輸入模式標誌,控制終端輸入方式,具體引數如下所示。

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:       輸出模式標誌,控制終端輸出方式,具體引數如下所示。
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:控制模式標誌,指定終端硬體控制資訊,具體引數如下所示。
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:本地模式標誌,控制終端編輯功能,具體引數如下所示。
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訊號

 

與此結構體相關的函式
(一)tcgetattr()      //   tc  get attr
1.原型
int tcgetattr  (int fd,struct termois & termios_p);
2.
功能 
取得終端介質(fd)初始值,並把其值 賦給temios_p;函式可以從後臺程序中呼叫;

但是,終端屬性可能被後來的前臺程序所改變。


(二)tcsetattr()          //tc  set  attr
1.原型
int tcsetattr   (int fd,int actions,const struct    termios *termios_p);
2.功能
設定與終端相關的引數 (除非需要底層支援卻無法滿足),使用 termios_p 引用的 termios 結構。optional_actions (tcsetattr函式的第二個引數)指定了什麼時候改變會起作用: 
TCSANOW:      改變立即發生  
TCSADRAIN:    改變在所有寫入 fd 的輸出都被傳輸後生效。這個函式應當用於修改影響輸出的引數時使用。(當前輸出完成時將值改變)  
TCSAFLUSH :   改變在所有寫入 fd 引用的物件的輸出都被傳輸後生效,所有已接受但未讀入的輸入都在改變發生前丟棄(同TCSADRAIN,但會捨棄當前所有值)。 


(三)tcsendbreak()    //  tc send break
  傳送連續的 0 值位元流,持續一段時間,如果終端使用非同步序列資料傳輸的話。如果 duration 是 0,它至少傳輸 0.25 秒,不會超過 0.5 秒。如果 duration 非零,它傳送的時間長度由實現定義。 
如果終端並非使用非同步序列資料傳輸,tcsendbreak() 什麼都不做。


(四)tcdrain()            //tc drain
等待直到所有寫入 fd 引用的物件的輸出都被傳輸。


(五)tcflush()           //tc   flush
丟棄要寫入 引用的物件,但是尚未傳輸的資料,或者收到但是尚未讀取的資料,取決於 queue_selector 的值:

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

 

(六)tcflow()            //tc  flow  
掛起 fd 引用的物件上的資料傳輸或接收,取決於 action 的值:

TCOOFF :  掛起輸出  
TCOON :   重新開始被掛起的輸出  
TCIOFF :   傳送一個 STOP 字元,停止終端裝置向系統傳送資料  
TCION :     傳送一個 START 字元,使終端裝置向系統傳輸資料  
開啟一個終端裝置時的預設設定是輸入和輸出都沒有掛起。


(七)波特率函式 
被用來獲取和設定 termios 結構中,輸入和輸出波特率的值。新值不會馬上生效,直到成功呼叫了 tcsetattr() 函式。
設定速度為 B0 使得 modem "掛機"。與 B38400 相應的實際位元率可以用 setserial(8) 調整。 
輸入和輸出波特率被保存於 termios 結構中。 
cfmakeraw 設定終端屬性如下: 

             //輸入模式標誌
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;

cf get o speed

cf  set o speed 

cf  get i speed

cf set i  speed

1.cfgetospeed()         返回 termios_p 指向的 termios 結構中儲存的輸出波特率 
2.cfsetospeed()         設定 termios_p 指向的 termios 結構中儲存的輸出波特率為 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 為非零。 
3.cfgetispeed() 返回 termios 結構中儲存的輸入波特率。 
4.cfsetispeed() 設定 termios 結構中儲存的輸入波特率為 speed。如果輸入波特率被設為0,實際輸入波特率將等於輸出波特率。

 
RETURN VALUE 返回值
1.cfgetispeed()      返回 termios 結構中儲存的輸入波特率。 
2.cfgetospeed()     返回 termios 結構中儲存的輸出波特率。 
3.其他函式返回: 
  (1)0:成功 
  (2)  -1:失敗,
    並且為 errno 置值來指示錯誤。 

tcsetattr()   任何一個設定屬性設定成功,都會返回成功,但因為要設定幾個屬性,但並不確定每個個都成成功,因此,要呼叫tcgetattr 來讀取驗證。

 


注意 tcsetattr() 返回成功,如果任何所要求的修改可以實現的話。因此,當進行多重修改時,應當在這個函式之後再次呼叫 tcgetattr() 來檢測是否所有修改都成功實現

 

#if 0

#include <termios.h>
#include <unistd.h>

獲取 fd 串列埠終端的屬性,儲存到 tremios_p
int tcgetattr(int fd, struct termios *termios_p);

設定 tremios_p 串列埠終端屬性到 fd 中,使用 optional_action
int tcsetattr(int fd, int optional_actions,
			const struct termios *termios_p);
optional_actions 可取值:
TCSANOW   立即生效
TCSADRAIN 所有寫到 fd 的輸出已完成後才生效
TCSAFLUSH 所有寫到 fd 的輸出已完成,並且輸入已接收完成,
但還未被讀取

int tcsendbreak(int fd, int duration);

int tcdrain(int fd);

重新整理串列埠終端 fd。queue_selector 可取值:
TCIFLUSH   重新整理接收的資料而不讀
TCOFLUSH   重新整理寫的資料而不傳送
TCIOFLUSH  重新整理接收和寫的資料而不讀和傳送
int tcflush(int fd, int queue_selector);

int tcflow(int fd, int action);

void cfmakeraw(struct termios *termios_p);

獲取輸入波特率
speed_t cfgetispeed(const struct termios *termios_p);

獲取輸出波特率
speed_t cfgetospeed(const struct termios *termios_p);

設定輸入波特率
int cfsetispeed(struct termios *termios_p, speed_t speed);

設定輸出波特率
int cfsetospeed(struct termios *termios_p, speed_t speed);

設定輸入/輸出波特率
int cfsetspeed(struct termios *termios_p, speed_t speed);


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];   /* special characters */

非規範模式下,輸入立即可用,對 read 來說,c_cc[NCSS] 陣列的
c_cc[VMIN] 和 c_cc[VTIME] 有以下四種情況:

c_cc[VMIN] == 0, c_cc[VTIME] == 0:
輪詢讀,無資料立即返回,read 返回 0

c_cc[VMIN] >  0, c_cc[VTIME] == 0:
阻塞讀,直到讀到請求的位元組數(即 c_cc[VMIN] 個位元組)

c_cc[VMIN] == 0, c_cc[VTIME] >  0:
帶超時的讀,如果在超時時間內有資料,則返回資料,如果超時到期,
沒有資料,則返回 0

c_cc[VMIN] >  0, c_cc[VTIME] >  0:
帶超時及請求位元組的讀,在超時期內等待請求的位元組數,超時到期,
不管請求的資料是否滿足都會返回,如果請求的資料滿足,會立即返回。


原始模式的典型配置:
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;

以上函式的返回值:

除 cfgetispeed 和 cfgetospeed 返回波特率外,其它的函式:
成功返回 0,失敗返回 -1
注意:tcsetattr 函式只有請求的設定有一個生效,就會返回成功,
因此要驗證是否全部設定成功,應該使用 tcgetattr 獲取
屬性後,與設定的屬性進行對比來驗證是否全部設定成功。

#endif

程式設計:

開啟串列埠

/*

在 Linux 下串列埠檔案是位於 /dev 下的

開啟串列埠是通過使用標準的檔案開啟函式open操作:


*/
#include     <stdio.h>      
#include     <stdlib.h>    
#include     <unistd.h>    
#include     <sys/types.h>  
#include     <sys/stat.h>   
#include     <fcntl.h>     
#include     <termios.h>    
#include     <errno.h>   

static int fd;

int uart_open(int fd,const char *pathname)
{
    assert(pathname);

    fd = open(pathname,O_RDWR|O_NOCTTY| O_NONBLOCK);
    if(fd == -1)
    {
        perror("Open UART failed!");
        return -1;
    }
    return fd;
}

其中:

O_NOCTTY

如果路徑名指向終端裝置,不要把這個裝置用作控制終端

O_NONBLOCK

如果路徑名指向 FIFO/塊檔案/字元檔案,則把檔案的開啟和後繼 I/O設定為非阻塞模式(nonblocking mode)

對於串列埠的開啟操作,必須使用O_NOCTTY引數,它表示開啟的是一個終端裝置,程式不會成為該埠的控制終端。如果不使用此標誌,任務的一個輸入都會影響程序。如鍵盤上過來的Ctrl+C中止訊號等都將影響程序。

設定串列埠

串列埠初始化需要設定串列埠波特率,資料流控制,幀的格式(即資料位個數,停止位,校驗位,資料流控制)

int uart_set(int fd,int baude,int c_flow,int bits,char parity,int stop)
{
    struct termios options;

    /*獲取終端屬性*/
    if(tcgetattr(fd,&options) < 0)
    {
        perror("tcgetattr error");
        return -1;
    }

    /*設定輸入輸出波特率,兩者保持一致*/
    switch(baude)
    {
        case 4800:
            cfsetispeed(&options,B4800);
            cfsetospeed(&options,B4800);
            break;
        case 9600:
            cfsetispeed(&options,B9600);
            cfsetospeed(&options,B9600);
            break;
        case 19200:
            cfsetispeed(&options,B19200);
            cfsetospeed(&options,B19200);
            break;
        case 38400:
            cfsetispeed(&options,B38400);
            cfsetospeed(&options,B38400);
            break;
        default:
            fprintf(stderr,"Unkown baude!\n");
            return -1;
    }

    /*設定控制模式*/
    options.c_cflag |= CLOCAL;//保證程式不佔用串列埠
    options.c_cflag |= CREAD;//保證程式可以從串列埠中讀取資料

    /*設定資料流控制*/
    switch(c_flow)
    {
        case 0://不進行流控制
            options.c_cflag &= ~CRTSCTS;
            break;
        case 1://進行硬體流控制
            options.c_cflag |= CRTSCTS;
            break;
        case 2://進行軟體流控制
            options.c_cflag |= IXON|IXOFF|IXANY;
            break;
        default:
            fprintf(stderr,"Unkown c_flow!\n");
            return -1;
    }

    /*設定資料位*/
    switch(bits)
    {
        case 5:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS5;
            break;
        case 6:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS6;
            break;
        case 7:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS7;
            break;
        case 8:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS8;
            break;
        default:
            fprintf(stderr,"Unkown bits!\n");
            return -1;
    }

    /*設定校驗位*/
    switch(parity)
    {
        /*無奇偶校驗位*/
        case 'n':
        case 'N':
            options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~INPCK;//INPCK:使奇偶校驗起作用
            break;
        /*設為空格,即停止位為2位*/
        case 's':
        case 'S':
            options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位
            break;
        /*設定奇校驗*/
        case 'o':
        case 'O':
            options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag |= PARODD;//PARODD:若設定則為奇校驗,否則為偶校驗
            options.c_cflag |= INPCK;//INPCK:使奇偶校驗起作用
            options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位
            break;
        /*設定偶校驗*/
        case 'e':
        case 'E':
            options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~PARODD;//PARODD:若設定則為奇校驗,否則為偶校驗
            options.c_cflag |= INPCK;//INPCK:使奇偶校驗起作用
            options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位
            break;
        default:
            fprintf(stderr,"Unkown parity!\n");
            return -1;
    }

    /*設定停止位*/
    switch(stop)
    {
        case 1:
            options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位
            break;
        case 2:
            options.c_cflag |= CSTOPB;//CSTOPB:使用兩位停止位
            break;
        default:
            fprintf(stderr,"Unkown stop!\n");
            return -1;
    }

    /*設定輸出模式為原始輸出*/
    options.c_oflag &= ~OPOST;//OPOST:若設定則按定義的輸出處理,否則所有c_oflag失效

    /*設定本地模式為原始模式*/
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /*
     *ICANON:允許規範模式進行輸入處理
     *ECHO:允許輸入字元的本地回顯
     *ECHOE:在接收EPASE時執行Backspace,Space,Backspace組合
     *ISIG:允許訊號
     */

    /*設定等待時間和最小接受字元*/
    options.c_cc[VTIME] = 0;//可以在select中設定
    options.c_cc[VMIN] = 1;//最少讀取一個字元

    /*如果發生資料溢位,只接受資料,但是不進行讀操作*/
    tcflush(fd,TCIFLUSH);

    /*啟用配置*/
    if(tcsetattr(fd,TCSANOW,&options) < 0)
    {
        perror("tcsetattr failed");
        return -1;
    }

    return 0;

}

讀寫串列埠

ssize_t safe_write(int fd, const void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;

    while(nleft > 0)
    {
    if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0&&errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n);
}

ssize_t safe_read(int fd,void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;

    ptr=vptr;
    nleft=n;

    while(nleft > 0)
    {
        if((nread = read(fd,ptr,nleft)) < 0)
        {
            if(errno == EINTR)//被訊號中斷
                nread = 0;
            else
                return -1;
        }
        else
        if(nread == 0)
            break;
        nleft -= nread;
        ptr += nread;
    }
    return (n-nleft);
}

int uart_read(int fd,char *r_buf,size_t len)
{
    ssize_t cnt = 0;
    fd_set rfds;
    struct timeval time;

    /*將檔案描述符加入讀描述符集合*/
    FD_ZERO(&rfds);
    FD_SET(fd,&rfds);

    /*設定超時為15s*/
    time.tv_sec = 15;
    time.tv_usec = 0;

    /*實現串列埠的多路I/O*/
    ret = select(fd+1,&rfds,NULL,NULL,&time);
    switch(ret)
    {
        case -1:
            fprintf(stderr,"select error!\n");
            return -1;
        case 0:
            fprintf(stderr,"time over!\n");
            return -1;
        default:
            cnt = safe_read(fd,r_buf,len);
            if(cnt == -1)
            {
                fprintf(stderr,"read error!\n");
                return -1;
            }
            return cnt;
    }
}

int uart_write(int fd,const char *w_buf,size_t len)
{
    ssize_t cnt = 0;

    cnt = safe_write(fd,w_buf,len);
    if(cnt == -1)
    {
        fprintf(stderr,"write error!\n");
        return -1;
    }

    return cnt;
}

關閉串列埠

int uart_close(int fd)
{
    assert(fd);
    close(fd);

    /*可以在這裡做些清理工作*/

    return 0;
}

完整程式碼

 

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<assert.h>
#include<termios.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<errno.h>

static int ret;
static int fd;

/*
 * 安全讀寫函式
 */

ssize_t safe_write(int fd, const void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;

    while(nleft > 0)
    {
    if((nwritten = write(fd, ptr, nleft)) <= 0)
        {
            if(nwritten < 0&&errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n);
}

ssize_t safe_read(int fd,void *vptr,size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;

    ptr=vptr;
    nleft=n;

    while(nleft > 0)
    {
        if((nread = read(fd,ptr,nleft)) < 0)
        {
            if(errno == EINTR)//被訊號中斷
                nread = 0;
            else
                return -1;
        }
        else
        if(nread == 0)
            break;
        nleft -= nread;
        ptr += nread;
    }
    return (n-nleft);
}

int uart_open(int fd,const char *pathname)
{
    assert(pathname);

    /*開啟串列埠*/
    fd = open(pathname,O_RDWR|O_NOCTTY|O_NDELAY);
    if(fd == -1)
    {
        perror("Open UART failed!");
        return -1;
    }

    /*清除串列埠非阻塞標誌*/
    if(fcntl(fd,F_SETFL,0) < 0)
    {
        fprintf(stderr,"fcntl failed!\n");
        return -1;
    }

    return fd;
}

int uart_set(int fd,int baude,int c_flow,int bits,char parity,int stop)
{
    struct termios options;

    /*獲取終端屬性*/
    if(tcgetattr(fd,&options) < 0)
    {
        perror("tcgetattr error");
        return -1;
    }


    /*設定輸入輸出波特率,兩者保持一致*/
    switch(baude)
    {
        case 4800:
            cfsetispeed(&options,B4800);
            cfsetospeed(&options,B4800);
            break;
        case 9600:
            cfsetispeed(&options,B9600);
            cfsetospeed(&options,B9600);
            break;
        case 19200:
            cfsetispeed(&options,B19200);
            cfsetospeed(&options,B19200);
            break;
        case 38400:
            cfsetispeed(&options,B38400);
            cfsetospeed(&options,B38400);
            break;
        default:
            fprintf(stderr,"Unkown baude!\n");
            return -1;
    }

    /*設定控制模式*/
    options.c_cflag |= CLOCAL;//保證程式不佔用串列埠
    options.c_cflag |= CREAD;//保證程式可以從串列埠中讀取資料

    /*設定資料流控制*/
    switch(c_flow)
    {
        case 0://不進行流控制
            options.c_cflag &= ~CRTSCTS;
            break;
        case 1://進行硬體流控制
            options.c_cflag |= CRTSCTS;
            break;
        case 2://進行軟體流控制
            options.c_cflag |= IXON|IXOFF|IXANY;
            break;
        default:
            fprintf(stderr,"Unkown c_flow!\n");
            return -1;
    }

    /*設定資料位*/
    switch(bits)
    {
        case 5:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS5;
            break;
        case 6:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS6;
            break;
        case 7:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS7;
            break;
        case 8:
            options.c_cflag &= ~CSIZE;//遮蔽其它標誌位
            options.c_cflag |= CS8;
            break;
        default:
            fprintf(stderr,"Unkown bits!\n");
            return -1;
    }

    /*設定校驗位*/
    switch(parity)
    {
        /*無奇偶校驗位*/
        case 'n':
        case 'N':
            options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~INPCK;//INPCK:使奇偶校驗起作用
            break;
        /*設為空格,即停止位為2位*/
        case 's':
        case 'S':
            options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位
            break;
        /*設定奇校驗*/
        case 'o':
        case 'O':
            options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag |= PARODD;//PARODD:若設定則為奇校驗,否則為偶校驗
            options.c_cflag |= INPCK;//INPCK:使奇偶校驗起作用
            options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位
            break;
        /*設定偶校驗*/
        case 'e':
        case 'E':
            options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行奇偶校驗
            options.c_cflag &= ~PARODD;//PARODD:若設定則為奇校驗,否則為偶校驗
            options.c_cflag |= INPCK;//INPCK:使奇偶校驗起作用
            options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位
            break;
        default:
            fprintf(stderr,"Unkown parity!\n");
            return -1;
    }

    /*設定停止位*/
    switch(stop)
    {
        case 1:
            options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位
            break;
        case 2:
            options.c_cflag |= CSTOPB;//CSTOPB:使用兩位停止位
            break;
        default:
            fprintf(stderr,"Unkown stop!\n");
            return -1;
    }

    /*設定輸出模式為原始輸出*/
    options.c_oflag &= ~OPOST;//OPOST:若設定則按定義的輸出處理,否則所有c_oflag失效

    /*設定本地模式為原始模式*/
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /*
     *ICANON:允許規範模式進行輸入處理
     *ECHO:允許輸入字元的本地回顯
     *ECHOE:在接收EPASE時執行Backspace,Space,Backspace組合
     *ISIG:允許訊號
     */

    /*設定等待時間和最小接受字元*/
    options.c_cc[VTIME] = 0;//可以在select中設定
    options.c_cc[VMIN] = 1;//最少讀取一個字元

    /*如果發生資料溢位,只接受資料,但是不進行讀操作*/
    tcflush(fd,TCIFLUSH);

    /*啟用配置*/
    if(tcsetattr(fd,TCSANOW,&options) < 0)
    {
        perror("tcsetattr failed");
        return -1;
    }

    return 0;
}

int uart_read(int fd,char *r_buf,size_t len)
{
    ssize_t cnt = 0;
    fd_set rfds;
    struct timeval time;

    /*將檔案描述符加入讀描述符集合*/
    FD_ZERO(&rfds);
    FD_SET(fd,&rfds);

    /*設定超時為15s*/
    time.tv_sec = 15;
    time.tv_usec = 0;

    /*實現串列埠的多路I/O*/
    ret = select(fd+1,&rfds,NULL,NULL,&time);
    switch(ret)
    {
        case -1:
            fprintf(stderr,"select error!\n");
            return -1;
        case 0:
            fprintf(stderr,"time over!\n");
            return -1;
        default:
            cnt = safe_read(fd,r_buf,len);
            if(cnt == -1)
            {
                fprintf(stderr,"read error!\n");
                return -1;
            }
            return cnt;
    }
}

int uart_write(int fd,const char *w_buf,size_t len)
{
    ssize_t cnt = 0;

    cnt = safe_write(fd,w_buf,len);
    if(cnt == -1)
    {
        fprintf(stderr,"write error!\n");
        return -1;
    }

    return cnt;
}

int uart_close(int fd)
{
    assert(fd);
    close(fd);

    /*可以在這裡做些清理工作*/

    return 0;
}

int main(void)
{
    const char *w_buf = "something to write";
    size_t w_len = sizeof(w_buf);

    char r_buf[1024];
    bzero(r_buf,1024);

    fd = uart_open(fd,"/dev/ttyS0");/*串列埠號/dev/ttySn,USB口號/dev/ttyUSBn*/
    if(fd == -1)
    {
        fprintf(stderr,"uart_open error\n");
        exit(EXIT_FAILURE);
    }

    if(uart_set(fd,9600,0,8,'N',1) == -1)
    {
        fprintf(stderr,"uart set failed!\n");
        exit(EXIT_FAILURE);
    }

    ret = uart_write(fd,w_buf,w_len);
    if(ret == -1)
    {
        fprintf(stderr,"uart write failed!\n");
        exit(EXIT_FAILURE);
    }

    while(1)
    {
        ret = uart_read(fd,r_buf,1024);
        if(ret == -1)
        {
            fprintf(stderr,"uart read failed!\n");
            exit(EXIT_FAILURE);
        }
    }

    ret = uart_close(fd);
    if(ret == -1)
    {
        fprintf(stderr,"uart_close error\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}