linux串口操作

man termios:http://www.man7.org/linux/man-pages/man4/tty_ioctl.4.html

串行口是計算機一種常用的接口,具有連接線少,通訊簡單,得到廣泛的使用。常用的串口是RS-232-C接口(又稱EIA RS-232-C)它是在1970年由美國電子工業協會(EIA)聯合貝爾系統、調制解調器廠家及計算機終端生產廠家共同制定的用於串行通訊的標準。串口通訊指的是計算機依次以位(bit)爲單位來傳送數據,串行通訊使用的範圍很廣,在嵌入式系統開發過程中串口通訊也經常用到通訊方式之一。

Linux對所有設備的訪問是通過設備文件來進行的,串口也是這樣,爲了訪問串口,只需打開其設備文件即可操作串口設備。在linux系統下面,每一個串口設備都有設備文件與其關聯,設備文件位於系統的/dev目錄下面。如linux下的/ttyS0,/ttyS1分別表示的是串口1和串口2。下面來詳細介紹linux下是如何使用串口的:

串口操作需要用到的頭文件

#include     <stdio.h>      /*標準輸入輸出定義*/
#include     <stdlib.h>     /*標準函數庫定義*/
#include     <unistd.h>     /*Unix 標準函數定義*/
#include     <sys/types.h> 
#include     <sys/stat.h>  
#include     <fcntl.h>      /*文件控制定義*/
#include     <termios.h>    /*POSIX 終端控制定義*/
#include     <errno.h>      /*錯誤號定義*/
#include     <string.h>       /*字符串功能函數*/

串口屬性結構體

在程序中,很容易配置串口的屬性,這些屬性定義在結構體struct termios中。爲在程序中使用該結構體,需要包含文件<termbits.h>,該頭文件定義了結構體struct termios。該結構體定義如下:

#define NCCS 19

struct termios {
    tcflag_t c_iflag;               /* 輸入參數 */
    tcflag_t c_oflag;               /* 輸出參數 */
    tcflag_t c_cflag;               /* 控制參數*/
    tcflag_t c_ispeed;              /* 輸入波特率 */
    tcflag_t c_ospeed;              /* 輸出波特率 */
    tcflag_t c_lflag; 				/*區域模式標誌或本地模式標誌或局部模式*/
    cc_t c_line;                   	/* 線控制 */
    cc_t c_cc[NCCS];              	/* 控制字符*/
};

其中成員c_line在POSIX(Portable Operating System Interface for UNIX)系統中不使用。

這個變量被用來提供一個健全的線路設置集合, 如果這個端口在被用戶初始化前使用. 驅動初始化這個變量使用一個標準的數值集, 它拷貝自 tty_std_termios 變量. tty_std_termos 在 tty 核心被定義爲:

struct termios tty_std_termios = {
 .c_iflag = ICRNL | IXON,
 .c_oflag = OPOST | ONLCR,
 .c_cflag = B38400 | CS8 | CREAD | HUPCL,
 .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN,
 .c_cc = INIT_C_CC
};

這個 struct termios 結構用來持有所有的當前線路設置, 給這個 tty 設備的一個特定端口.

這些線路設置控制當前波特率, 數據大小, 數據流控設置, 以及許多其他值.

c_iflag 標誌常量:Input mode ( 輸入模式)

input mode可以在輸入值傳給程序之前控制其處理的方式。其中輸入值可能是由序列號或鍵盤的終端驅動程序所接收到的字元。 我們可以利用termios結構的c_iflag的標誌來加以控制,其定義的方式皆以OR來加以組合。

IGNBRK :忽略輸入中的 BREAK 狀態。 (忽略命令行中的中斷)

BRKINT :(命令行出現中斷時,可產生一插斷)如果設置了 IGNBRK,將忽略 BREAK。如果沒有設置,但是設置了 BRKINT,那麼 BREAK 將使得輸入和輸出隊列被刷新,如果終端是一個前臺進程組的控制終端,這個進程組中所有進程將收到 SIGINT 信號。如果既未設置 IGNBRK 也未設置 BRKINT,BREAK 將視爲與 NUL 字符同義,除非設置了 PARMRK,這種情況下它被視爲序列 377 。

IGNPAR :忽略楨錯誤和奇偶校驗錯。

PARMRK :如果沒有設置 IGNPAR,在有奇偶校驗錯或楨錯誤的字符前插入 377 。如果既沒有設置 IGNPAR 也沒有設置 PARMRK,將有奇偶校驗錯或楨錯誤的字符視爲 。

INPCK :啓用輸入奇偶檢測。

ISTRIP :去掉第八位。

INLCR :將輸入中的 NL 翻譯爲 CR。(將收到的換行符號轉換爲Return)

IGNCR :忽略輸入中的回車。

ICRNL :將輸入中的回車翻譯爲新行 (除非設置了 IGNCR)(否則當輸入信號有 CR 時不會終止輸入)。

IUCLC :(不屬於 POSIX) 將輸入中的大寫字母映射爲小寫字母。

IXON :啓用輸出的 XON/XOFF 流控制。

IXANY :(不屬於 POSIX.1;XSI) 允許任何字符來重新開始輸出。(?)

IXOFF :啓用輸入的 XON/XOFF 流控制。

IMAXBEL:(不屬於 POSIX) 當輸入隊列滿時響零。Linux 沒有實現這一位,總是將它視爲已設置。

c_oflag 標誌常量:Output mode ( 輸出模式)

Output mode主要負責控制輸出字元的處理方式。輸出字元在傳送到序列埠或顯示器之前是如何被程序來處理。

輸出模式是利用termios結構的c_oflag的標誌來加以控制,其定義的方式皆以OR來加以組合。

OPOST :啓用具體實現自行定義的輸出處理。

OLCUC :(不屬於 POSIX) 將輸出中的小寫字母映射爲大寫字母。

ONLCR :(XSI) 將輸出中的新行符映射爲回車-換行。

OCRNL :將輸出中的回車映射爲新行符

ONOCR :不在第 0 列輸出回車。

ONLRET :不輸出回車。

OFILL :發送填充字符作爲延時,而不是使用定時來延時。

OFDEL :(不屬於 POSIX) 填充字符是 ASCII DEL (0177)。如果不設置,填充字符則是 ASCII NUL。

NLDLY :新行延時掩碼。取值爲 NL0 和 NL1。

CRDLY :回車延時掩碼。取值爲 CR0, CR1, CR2, 或 CR3。

TABDLY :水平跳格延時掩碼。取值爲 TAB0, TAB1, TAB2, TAB3 (或 XTABS)。取值爲 TAB3,即 XTABS,將擴展跳格爲空格 (每個跳格符填充 8 個空格)。(?)

BSDLY :回退延時掩碼。取值爲 BS0 或 BS1。(從來沒有被實現過)

VTDLY :豎直跳格延時掩碼。取值爲 VT0 或 VT1。

FFDLY :進表延時掩碼。取值爲 FF0 或 FF1。

c_cflag 標誌常量:Control mode ( 控制模式)

Control mode主要用於控制終端設備的硬件設置。利用termios結構的c_cflag的標誌來加以控制。控制模式用在序列線連接到數據設備,也可以用在與終端設備的交談。

一般來說,改變終端設備的組態要比使用termios的控制模式來改變行(lines)的行爲來得容易。

CBAUD :(不屬於 POSIX) 波特率掩碼 (4+1 位)。

CBAUDEX :(不屬於 POSIX) 擴展的波特率掩碼 (1 位),包含在 CBAUD 中。

(POSIX 規定波特率存儲在 termios 結構中,並未精確指定它的位置,而是提供了函數 cfgetispeed() 和 cfsetispeed() 來存取它。一些系統使用 c_cflag 中 CBAUD 選擇的位,其他系統使用單獨的變量,例如 sg_ispeed 和 sg_ospeed 。)

CSIZE:字符長度掩碼(傳送或接收字元時用的位數)。取值爲 CS5(傳送或接收字元時用5bits), CS6, CS7, 或 CS8。

CSTOPB :設置兩個停止位,而不是一個。

CREAD :打開接受者。

PARENB :允許輸出產生奇偶信息以及輸入的奇偶校驗(啓用同位產生與偵測)。

PARODD :輸入和輸出是奇校驗(使用奇同位而非偶同位)。

HUPCL :在最後一個進程關閉設備後,降低 modem 控制線 (掛斷)。(?)

CLOCAL :忽略 modem 控制線。

LOBLK :(不屬於 POSIX) 從非當前 shell 層阻塞輸出(用於 shl )。(?)

CIBAUD :(不屬於 POSIX) 輸入速度的掩碼。CIBAUD 各位的值與 CBAUD 各位相同,左移了 IBSHIFT 位。

CRTSCTS :(不屬於 POSIX) 啓用 RTS/CTS (硬件) 流控制。

c_lflag 標誌常量:Local mode ( 局部模式)

Local mode主要用來控制終端設備不同的特色。利用termios結構裏的c_lflag的標誌來設定局部模式。

在巨集中有兩個比較重要的標誌:

1.ECHO:它可以讓你阻止鍵入字元的迴應。

2.ICANON(正規模式)標誌,它可以對所接收的字元在兩種不同的終端設備模式之間來回切換。

ISIG:當接受到字符 INTR, QUIT, SUSP, 或 DSUSP 時,產生相應的信號。

ICANON:啓用標準模式 (canonical mode)。允許使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的緩衝。

XCASE:(不屬於 POSIX; Linux 下不被支持) 如果同時設置了 ICANON,終端只有大寫。輸入被轉換爲小寫,除了有前綴的字符。輸出時,大寫字符被前綴(某些系統指定的特定字符) ,小寫字符被轉換成大寫。

ECHO :回顯輸入字符。

ECHOE :如果同時設置了 ICANON,字符 ERASE 擦除前一個輸入字符,WERASE 擦除前一個詞。

ECHOK :如果同時設置了 ICANON,字符 KILL 刪除當前行。

ECHONL :如果同時設置了 ICANON,回顯字符 NL,即使沒有設置 ECHO。

ECHOCTL :(不屬於 POSIX) 如果同時設置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信號被回顯爲 ^X, 這裏 X 是比控制信號大 0x40 的 ASCII 碼。例如,字符 0x08 (BS) 被回顯爲 ^H。

ECHOPRT :(不屬於 POSIX) 如果同時設置了 ICANON 和 IECHO,字符在刪除的同時被打印。

ECHOKE :(不屬於 POSIX) 如果同時設置了 ICANON,回顯 KILL 時將刪除一行中的每個字符,如同指定了 ECHOE 和 ECHOPRT 一樣。

DEFECHO :(不屬於 POSIX) 只在一個進程讀的時候回顯。

FLUSHO :(不屬於 POSIX; Linux 下不被支持) 輸出被刷新。這個標誌可以通過鍵入字符 DISCARD 來開關。

NOFLSH :禁止在產生 SIGINT, SIGQUIT 和 SIGSUSP 信號時刷新輸入和輸出隊列,即關閉queue中的flush。

TOSTOP :向試圖寫控制終端的後臺進程組發送 SIGTTOU 信號(傳送欲寫入的信息到後臺處理)。

PENDIN :(不屬於 POSIX; Linux 下不被支持) 在讀入下一個字符時,輸入隊列中所有字符被重新輸出。(bash 用它來處理 typeahead)

IEXTEN :啓用實現自定義的輸入處理。這個標誌必須與 ICANON 同時使用,才能解釋特殊字符 EOL2,LNEXT,REPRINT 和 WERASE,IUCLC 標誌纔有效。

c_cc 數組:特殊控制字元

可提供使用者設定一些特殊的功能, 如Ctrl+C的字元組合。特殊控制字元主要是利用termios結構裏c_cc的陣列成員來做設定。

c_cc陣列主要用於正規與非正規兩種環境,但要注意的是正規與非正規不可混爲一談。

其定義了特殊的控制字符。符號下標 (初始值) 和意義爲:

VINTR:(003, ETX, Ctrl-C, or also 0177, DEL, rubout) 中斷字符。發出 SIGINT 信號。當設置 ISIG 時可被識別,不再作爲輸入傳遞。

VQUIT :(034, FS, Ctrl-) 退出字符。發出 SIGQUIT 信號。當設置 ISIG 時可被識別,不再作爲輸入傳遞。

VERASE :(0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) 刪除字符。刪除上一個還沒有刪掉的字符,但不刪除上一個 EOF 或行首。當設置 ICANON 時可被識別,不再作爲輸入傳遞。

VKILL :(025, NAK, Ctrl-U, or Ctrl-X, or also @) 終止字符。刪除自上一個 EOF 或行首以來的輸入。當設置 ICANON 時可被識別,不再作爲輸入傳遞。

VEOF :(004, EOT, Ctrl-D) 文件尾字符。更精確地說,這個字符使得 tty 緩衝中的內容被送到等待輸入的用戶程序中,而不必等到 EOL。如果它是一行的第一個字符,那麼用戶程序的 read() 將返回 0,指示讀到了 EOF。當設置 ICANON 時可被識別,不再作爲輸入傳遞。

VMIN :非 canonical 模式讀的最小字符數(MIN主要是表示能滿足read的最小字元數)。

VEOL :(0, NUL) 附加的行尾字符。當設置 ICANON 時可被識別。

VTIME :非 canonical 模式讀時的延時,以十分之一秒爲單位。

VEOL2 :(not in POSIX; 0, NUL) 另一個行尾字符。當設置 ICANON 時可被識別。

VSWTCH :(not in POSIX; not supported under Linux; 0, NUL) 開關字符。(只爲 shl 所用。)

VSTART :(021, DC1, Ctrl-Q) 開始字符。重新開始被 Stop 字符中止的輸出。當設置 IXON 時可被識別,不再作爲輸入傳遞。

VSTOP :(023, DC3, Ctrl-S) 停止字符。停止輸出,直到鍵入 Start 字符。當設置 IXON 時可被識別,不再作爲輸入傳遞。

VSUSP :(032, SUB, Ctrl-Z) 掛起字符。發送 SIGTSTP 信號。當設置 ISIG 時可被識別,不再作爲輸入傳遞。

VDSUSP :(not in POSIX; not supported under Linux; 031, EM, Ctrl-Y) 延時掛起信號。當用戶程序讀到這個字符時,發送 SIGTSTP 信號。當設置 IEXTEN 和 ISIG,並且系統支持作業管理時可被識別,不再作爲輸入傳遞。

VLNEXT :(not in POSIX; 026, SYN, Ctrl-V) 字面上的下一個。引用下一個輸入字符,取消它的任何特殊含義。當設置 IEXTEN 時可被識別,不再作爲輸入傳遞。

VWERASE :(not in POSIX; 027, ETB, Ctrl-W) 刪除詞。當設置 ICANON 和 IEXTEN 時可被識別,不再作爲輸入傳遞。

VREPRINT :(not in POSIX; 022, DC2, Ctrl-R) 重新輸出未讀的字符。當設置 ICANON 和 IEXTEN 時可被識別,不再作爲輸入傳遞。

VDISCARD :(not in POSIX; not supported under Linux; 017, SI, Ctrl-O) 開關:開始/結束丟棄未完成的輸出。當設置 IEXTEN 時可被識別,不再作爲輸入傳遞。

VSTATUS :(not in POSIX; not supported under Linux; status request: 024, DC4, Ctrl-T).

這些符號下標值是互不相同的,除了 VTIME,VMIN 的值可能分別與 VEOL,VEOF 相同。 (在 non-canonical 模式下,特殊字符的含義更改爲延時含義。MIN 表示應當被讀入的最小字符數。TIME 是以十分之一秒爲單位的計時器。如果同時設置了它們,read 將等待直到至少讀入一個字符,一旦讀入 MIN 個字符或者從上次讀入字符開始經過了 TIME 時間就立即返回。如果只設置了 MIN,read 在讀入 MIN 個字符之前不會返回。如果只設置了 TIME,read 將在至少讀入一個字符,或者計時器超時的時候立即返回。如果都沒有設置,read 將立即返回,只給出當前準備好的字符。)

MIN與TIME組合有以下四種:

  1. MIN = 0 , TIME =0

    有READ立即回傳

    否則傳回 0 ,不讀取任何字元

  2. MIN = 0 , TIME >0

    READ 傳回讀到的字元,或在十分之一秒後傳回TIME

    若來不及讀到任何字元,則傳回0

  3. MIN > 0 , TIME =0

    READ 會等待,直到MIN字元可讀

  4. MIN > 0 , TIME > 0

    每一格字元之間計時器即會被啓動

    READ 會在讀到MIN字元,傳回值或TIME的字元計時(1/10秒)超過時將值傳回

串口通訊波特率設置

波特率的設置定義在<asm/termbits.h>,其包含在頭文件<termios.h>裏。

常用的波特率常數如下:

B0-------à0                     B1800-------à1800

B50-----à50                    	B2400------à2400

B75-----à75                    	B4800------à4800

B110----à110                 	B9600------à9600

B134----à134.5              	B19200-----à19200

B200----à200                 	B38400------à38400

B300----à300                 	B57600------à57600

B600----à600                 	B76800------à76800

B1200---à1200              		B115200-----à115200

假定程序中想要設置通訊的波特率,使用cfsetispeed( )和cfsetospeed( )函數來操作,獲取波特率信息是通過cfgetispeed()和cfgetospeed()函數來完成的。比如可以這樣來指定串口通訊的波特率:

#include <stdio.h>    //頭文件定義
...
struct termios opt;           /*定義指向termios 結構類型的指針opt*/
/***************以下設置通訊波特率****************/
cfsetispeed(&opt,B9600 )/*指定輸入波特率,9600bps*/
cfsetospeed(&opt,B9600)/*指定輸出波特率,9600bps*/
/************************************************/
...

一般來說,輸入、輸出的波特率應該是一致的。

串口屬性的設置和獲取

對於支持POSIX終端接口的系統中,對於端口屬性的設置和獲取要用到兩個重要的函數是:

int tcsetattr(int fd,int opt_DE,*ptr)

該函數用來設置終端控制屬性,其參數說明如下:

  • fd:待操作的文件描述符
  • opt_DE:選項值,有三個選項以供選擇:
    • TCSANOW: 不等數據傳輸完畢就立即改變屬性
    • TCSADRAIN:等待所有數據傳輸結束才改變屬性
    • TCSAFLUSH:清空輸入輸出緩衝區才改變屬性
  • *ptr:指向termios結構的指針
  • 函數返回值:成功返回0,失敗返回-1。
int tcgetattr(int fd,*ptr)

該函數用來獲取終端控制屬性,它把串口的默認設置賦給了termios數據數據結構,其參數說明如下:

  • fd:待操作的文件描述符
  • *ptr:指向termios結構的指針
  • 函數返回值:成功返回0,失敗返回-1。

打開串口

在前面已經提到linux下的串口訪問是以設備文件形式進行的,所以打開串口也即是打開文件的操作。函數原型可以如下所示:

int open(“DE_name”,int open_Status)

例如假定以可讀寫方式打開/dev/ttyS0設備,就可以這樣操作:

#include<stdio.h>    //頭文件包含
...
int fd; /* 文件描述符 */
fd = open("/dev/ttyS0", O_RDWR | 0_NOCTTY)/*以讀寫方式打開設備*/
if(fd == -1)
perror("Can not open Serial_Port 1/n!")/*打開失敗時的錯誤提示*/
...

串口讀操作(接收端)

用open函數打開設備文件,函數返回一個文件描述符(file descriptors,fd),通過文件描述符來訪問文件。讀串口操作是通過read函數來完成的。函數原型如下:

int read(int fd, *buffer,length)

串口寫操作(發送端)

寫串口操作是通過write函數來完成的。函數原型如下:

int write(int fd, *buffer,length);

例如:向終端設備發送初始化命令

#include<stdio.h>    //頭文件包含
...
int n
sbuf[]={Hello,this is a Serial_Port test!/n }//待發送數據
int len_send="sizeof"(sbuf);//發送緩衝區字節數定義
n = write(fd,sbuf,len_send); //寫緩衝區
if(n == -1)
{
printf("Wirte sbuf error./n");
}
...

關閉串口

對設備文件的操作與對普通文件的操作一樣,打開操作之後還需要關閉,關閉串口用函數close( )來操作,函數原型爲:

int close(int fd);

所有的範例來源自 miniterm.c. The type ahead 暫存器被限制在 255 個字元, 就跟標準輸入程序的最大字串長度相同 (<linux/limits.h> 或 <posix1_lim.h>).

參考程序碼中的註解它會解釋不同輸入模式的使用. 我希望這些程序碼都能被瞭解. 標準輸入程序的程序範例的註解寫得最好, 其它的範例都只在不同於其它範例的地方做註解.

敘述不是很完整, 但可以激勵你對這範例做實驗, 以延生出合於你所需應用程序的最佳解.

別忘記要把序列埠的權限設定正確 (也就是: chmod a+rw /dev/ttyS1)!

標準輸入程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

/* 波特率設定被定義在 <asm/termbits.h>, 這在 <termios.h> 被引入 */
#define BAUDRATE B38400            
/* 定義正確的序列埠 */
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 系統兼容 */

#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  char buf[255];
/* 
  開啓數據機裝置以讀取並寫入而不以控制 tty 的模式
  因爲我們不想程序在送出 CTRL-C 後就被殺掉.
*/
 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
 if (fd <0) {perror(MODEMDEVICE); exit(-1); }

 tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定 */
 bzero(&newtio, sizeof(newtio)); /* 清除結構體以放入新的序列埠設定值 */

/* 
  BAUDRATE: 設定 bps 的速度. 你也可以用 cfsetispeed 及 cfsetospeed 來設定.
  CRTSCTS : 輸出資料的硬件流量控制 (只能在具完整線路的纜線下工作
            參考 Serial-HOWTO 第七節)
  CS8     : 8n1 (8 位元, 不做同位元檢查,1 個終止位元)
  CLOCAL  : 本地連線, 不具數據機控制功能
  CREAD   : 致能接收字元
*/
 newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
 
/*
  IGNPAR  : 忽略經同位元檢查後, 錯誤的位元組
  ICRNL   : 比 CR 對應成 NL (否則當輸入信號有 CR 時不會終止輸入)
            在不然把裝置設定成 raw 模式(沒有其它的輸入處理)
*/
 newtio.c_iflag = IGNPAR | ICRNL;
 
/*
 Raw 模式輸出.
*/
 newtio.c_oflag = 0;
 
/*
  ICANON  : 致能標準輸入, 使所有迴應機能停用, 並不送出信號以叫用程序
*/
 newtio.c_lflag = ICANON;
 
/* 
  初始化所有的控制特性
  預設值可以在 /usr/include/termios.h 找到, 在註解中也有,
  但我們在這不需要看它們
*/
 newtio.c_cc[VINTR]    = 0;     /* Ctrl-c */ 
 newtio.c_cc[VQUIT]    = 0;     /* Ctrl-/ */
 newtio.c_cc[VERASE]   = 0;     /* del */
 newtio.c_cc[VKILL]    = 0;     /* @ */
 newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
 newtio.c_cc[VTIME]    = 0;     /* 不使用分割字元組的計時器 */
 newtio.c_cc[VMIN]     = 1;     /* 在讀取到 1 個字元前先停止 */
 newtio.c_cc[VSWTC]    = 0;     /* '/0' */
 newtio.c_cc[VSTART]   = 0;     /* Ctrl-q */ 
 newtio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
 newtio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
 newtio.c_cc[VEOL]     = 0;     /* '/0' */
 newtio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
 newtio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
 newtio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
 newtio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
 newtio.c_cc[VEOL2]    = 0;     /* '/0' */

/* 
  現在清除數據機線並啓動序列埠的設定
*/
 tcflush(fd, TCIFLUSH);
 tcsetattr(fd,TCSANOW,&newtio);

/*
  終端機設定完成, 現在處理輸入信號
  在這個範例, 在一行的開始處輸入 'z' 會退出此程序.
*/
 while (STOP==FALSE) {     /* 迴圈會在我們發出終止的信號後跳出 */
 /* 即使輸入超過 255 個字元, 讀取的程序段還是會一直等到行終結符出現才停止.
    如果讀到的字元組低於正確存在的字元組, 則所剩的字元會在下一次讀取時取得.
    res 用來存放真正讀到的字元組個數 */
    res = read(fd,buf,255); 
    buf[res]=0;             /* 設定字串終止字元, 所以我們能用 printf */
    printf(":%s:%d/n", buf, res);
    if (buf[0]=='z') STOP=TRUE;
 }
 /* 回存舊的序列埠設定值 */
 tcsetattr(fd,TCSANOW,&oldtio);
}

非標準輸入程序

在非標準的輸入程序模式下, 輸入的資料不會被組合成一行而輸入後的處理功能 (清除, 殺掉, 刪除, 等等.) 都不能使用. 這個模式有兩個功能控制參數: c_cc[VTIME] 設定字元輸入時間計時器, 及 c_cc[VMIN] 設定滿足讀取功能的最低字元接收個數.

如果 MIN > 0 且 TIME = 0, MIN 設定爲滿足讀取功能的最低字元接收個數. 由於 TIME 是 零, 所以計時器將不被使用.

如果 MIN = 0 且 TIME > 0, TIME 將被當做逾時設定值. 滿足讀取功能的情況爲讀取到單一字元, 或者超過 TIME 所定義的時間 (t = TIME *0.1 s). 如果超過 TIME 所定義的時間, 則不會傳回任何字元.

如果 MIN > 0 且 TIME > 0, TIME 將被當做一個分割字元組的計時器. 滿足讀取功能的條件爲接收到 MIN 個數的字元, 或兩個字元的間隔時間超過 TIME 所定義的值. 計時器會在每讀到一個字元后重新計時, 且只會在第一個字元收到後纔會啓動.

如果 MIN = 0 且 TIME = 0, 讀取功能就馬上被滿足. 目前所存在的字元組個數, 或者 將回傳的字元組個數. 根據 Antonino (參考 貢獻) 所說, 你可以用 fcntl(fd, F_SETFL, FNDELAY); 在讀取前得到相同的結果.

藉由修改 newtio.c_cc[VTIME] 及 newtio.c_cc[VMIN] 上述的模式就可以測試了.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 系統兼容 */
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  char buf[255];

 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
 if (fd <0) {perror(MODEMDEVICE); exit(-1); }

 tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定 */

 bzero(&newtio, sizeof(newtio));
 newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
 newtio.c_iflag = IGNPAR;
 newtio.c_oflag = 0;

 /* 設定輸入模式 (非標準型, 不迴應,...) */
 newtio.c_lflag = 0;
 
 newtio.c_cc[VTIME]    = 0;   /* 不使用分割字元組計時器 */
 newtio.c_cc[VMIN]     = 5;   /* 在讀取到 5 個字元前先停止 */

 tcflush(fd, TCIFLUSH);
 tcsetattr(fd,TCSANOW,&newtio);


 while (STOP==FALSE) {       /* 輸入迴圈 */
   res = read(fd,buf,255);   /* 在輸入 5 個字元后即返回 */
   buf[res]=0;               /* 所以我們能用 printf... */
   printf(":%s:%d/n", buf, res);
   if (buf[0]=='z') STOP=TRUE;
 }
 tcsetattr(fd,TCSANOW,&oldtio);
}

非同步式輸入

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 系統兼容 */
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

void signal_handler_IO (int status);   /* 定義信號處理程序 */
int wait_flag=TRUE;                    /* 沒收到信號的話就會是 TRUE */

main()
{
  int fd,c, res;
  struct termios oldtio,newtio;
  struct sigaction saio;           /* definition of signal action */
  char buf[255];

  /* 開啓裝置爲 non-blocking (讀取功能會馬上結束返回) */
  fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
  if (fd <0) {perror(MODEMDEVICE); exit(-1); }

  /* 在使裝置非同步化前, 安裝信號處理程序 */
  saio.sa_handler = signal_handler_IO;
  saio.sa_mask = 0;
  saio.sa_flags = 0;
  saio.sa_restorer = NULL;
  sigaction(SIGIO,&saio,NULL);
  
  /* 允許行程去接收 SIGIO 信號*/
  fcntl(fd, F_SETOWN, getpid());
  /* 使文檔ake the file descriptor 非同步 (使用手冊上說只有 O_APPEND 及
  O_NONBLOCK, 而 F_SETFL 也可以用...) */
  fcntl(fd, F_SETFL, FASYNC);

  tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定值 */
  /* 設定新的序列埠爲標準輸入程序 */
  newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR | ICRNL;
  newtio.c_oflag = 0;
  newtio.c_lflag = ICANON;
  newtio.c_cc[VMIN]=1;
  newtio.c_cc[VTIME]=0;
  tcflush(fd, TCIFLUSH);
  tcsetattr(fd,TCSANOW,&newtio);
 
  /* 等待輸入信號的迴圈. 很多有用的事我們將在這做 */ 
  while (STOP==FALSE) {
    printf("./n");usleep(100000);
    /* 在收到 SIGIO 後, wait_flag = FALSE, 輸入信號存在則可以被讀取 */
    if (wait_flag==FALSE) { 
      res = read(fd,buf,255);
      buf[res]=0;
      printf(":%s:%d/n", buf, res);
      if (res==1) STOP=TRUE; /* 如果只輸入 CR 則停止迴圈 */
      wait_flag = TRUE;      /* 等待新的輸入信號 */
    }
  }
  /* 回存舊的序列埠設定值 */
  tcsetattr(fd,TCSANOW,&oldtio);
}

/***************************************************************************
* 信號處理程序. 設定 wait_flag 爲 FALSE, 以使上述的迴圈能接收字元          *
***************************************************************************/

void signal_handler_IO (int status)
{
  printf("received SIGIO signal./n");
  wait_flag = FALSE;
}

等待來自多個信號來源的輸入

這一段很短. 它只能被拿來當成寫程序時的提示, 故範例程序也很簡短. 但這個範例不只能用在序列埠上, 還可以用在被當成文檔來使用的裝置上.

select 呼叫及伴隨它所引發的巨集共用 fd_set. fd_set 則是一個位元陣列, 而其中每一個位元代表一個有效的文檔敘述結構. select 呼叫接受一個有效的文檔敘述結構並傳回 fd_set 位元陣列, 而該位元陣列中若有某一個位元爲 1, 就表示相對映的文檔敘述結構的文檔發生了輸入, 輸出或有例外事件. 而這些巨集提供了所有處理 fd_set 的功能. 亦可參考手冊 select(2).

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

main()
{
   int    fd1, fd2;  /* 輸入源 1 及 2 */
   fd_set readfs;    /* 文檔敘述結構設定 */
   int    maxfd;     /* 最大可用的文檔敘述結構 */
   int    loop=1;    /* 迴圈在 TRUE 時成立 */ 

   /* open_input_source 開啓一個裝置, 正確的設定好序列埠,
      並回傳回此文檔敘述結構體 */
   fd1 = open_input_source("/dev/ttyS1");   /* COM2 */
   if (fd1<0) exit(0);
   fd2 = open_input_source("/dev/ttyS2");   /* COM3 */
   if (fd2<0) exit(0);
   maxfd = MAX (fd1, fd2)+1;  /* 測試最大位元輸入 (fd) */

   /* 輸入迴圈 */
   while (loop) {
     FD_SET(fd1, &readfs);  /* 測試輸入源 1 */
     FD_SET(fd2, &readfs);  /* 測試輸入源 2 */
     /* block until input becomes available */
     select(maxfd, &readfs, NULL, NULL, NULL);
     if (FD_ISSET(fd1))         /* 如果輸入源 1 有信號 */
       handle_input_from_source1();
     if (FD_ISSET(fd2))         /* 如果輸入源 2 有信號 */
       handle_input_from_source2();
   }

}  

這個範例程序在等待輸入信號出現前, 不能確定它會停頓下來. 如果你需要在輸入時加入逾時功能, 只需把 select 呼叫換成:

int res;
struct timeval Timeout;

/* 設定輸入迴圈的逾時值 */
Timeout.tv_usec = 0;  /* 毫秒 */
Timeout.tv_sec  = 1;  /* 秒 */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);
if (res==0)
/* 文檔敘述結構數在 input = 0 時, 會發生輸入逾時. */

這個程序會在 1 秒鐘後逾時. 如果超過時間, select 會傳回 0, 但是應該留意 Timeout 的時間遞減是由 select 所等待輸入信號的時間爲基準. 如果逾時的值是 0, select 會馬上結束返回.

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