Serial Programming Guide
for
POSIX Operating Systems
POSIX作業系統串列埠程式設計指南
5th Edition, 3rd Revision
Copyright 1994-2003 by Michael R. Sweet
Translated by Cedar Peng ( 翻譯 彭雪松 )
允許拷貝,分發或在GNU自由文檔許可(Version 1.2 或任何更新的由自由軟體基金會發布的版本)的條件下的修改,不可有任何章節的變化,額外的開頭和結尾文字。一份許可包含在 附錄 C, GNU自由文檔許可.
目錄
介紹
POSIX 作業系統串列埠程式設計指南將教會你如何成功、有效和可移植地在你的UNIX?工作站或PC上程式設計。 每章提供的程式設計樣例使用POSIX (UNIX可移植標準)終端控制函數,少量修改就可以運行于IRIX®, HP-UX, SunOS®, Solaris®, Digital UNIX®, Linux®, 和許多其它的UNIX作業系統。 你將發現下不同作業系統之間的最大差別是 串列埠設備和鎖定檔 的名字。
許可
在GNU自由文檔許可(版本1.2或更新的,由自由軟體基金發布的版本)的條件下,允許複製、分發或修改這個文檔;不能改變章節,不能有開頭和結尾的附蓋文本。在 附錄C,GNU自由文檔許可 中有一份許可複製,作為參考。
組織
這篇指南由以下章節和附錄組成:
第一章,串列埠通訊基礎
這章介紹了串列埠通訊,RS-232和其它一些在計算機上廣泛使用的標準,同時還有如何從C程式存取串列埠的內容。
什麼是串列埠通訊?
計算機一次傳輸一或多位元的訊息(數據)。串列埠是指一次傳輸一位元數據。串列埠通訊包括了大多數的網路設備、鍵盤、 麥克風,數據機和終端。當進行串列埠通訊時,你傳送或接受的每個字(例如︰位元組或字元)是以每次一位元傳輸的。 每位 或者為on 或者 off。有時你也能聽到以mark 表示 on 狀態和 space 表示 off 狀態。
串接資料的速度常表示為位元率 ("bps") 或鮑率 ("baud")。這只是用于表示每秒能夠傳送的1和0的數量。 追溯到計算機時代的早期,300bps被認為是很快的速度,但今天的計算機能夠控制RS-232速度高達430,800bps﹗ 當鮑率超過1,000,你通常將看到速度被表示為千bps,或kbps (例如9.6k, 19.2k等等)。對于超過1,000,000的 速度表示為兆bps,或者Mbps (例如1.5Mbps)。
當提及串列埠設備或端點的時候,他們被表明數據通訊設備 ("DCE")或者數據終端設備 ("DTE")。 這之間的差別很簡單每對信號,例如傳送和接受,都需要交換。當連接兩個DTE或兩個DCE界面到一起的時候,需要使用一 個串列埠null-MODEM 纜線或適配卡來交換信號對。
什麼是 RS-232?
RS-232一標準的串列埠通訊電氣界面,由 Electronic Industries Association ("EIA")定義。RS-232實際分為三部分(A, B和C),每部分都定義了不同的 on 與 off 間的電壓等級。最為常用的是RS-232C,它定義mark (on) 一位元電壓在-3V到-12V之間和space (off)一位元在電壓+3V到 +12V之間。 RS-232C定義這些信號可以傳輸達25英尺 (8米)有效。只要鮑率足夠低,你通常能夠傳送更長的距離。
除了輸入和輸出數據的引線,還有提供時間,狀態和握手信號的引線︰
1 | Earth Ground | 6 | DSR - Data Set Ready | 11 | 未定義 | 16 | Secondary RXD | 21 | Signal Quality Detect |
2 | TXD - Transmitted Data | 7 | GND - Logic Ground | 12 | Secondary DCD | 17 | Receiver Clock | 22 | Ring Detect |
3 | RXD - Received Data | 8 | DCD - Data Carrier Detect | 13 | Secondary CTS | 18 | 未定義 | 23 | Data Rate Select |
4 | RTS - Request To Send | 9 | Reserved | 14 | Secondary TXD | 19 | Secondary RTS | 24 | Transmit Clock |
5 | CTS - Clear To Send | 10 | Reserved | 15 | Transmit Clock | 20 | DTR - Data Terminal Ready | 25 | 未定義 |
其它兩個你可能也看到過的串列埠界面標準是RS-422和RS-574。RS-422使用低電壓和微分信號, 允許線纜長度到 1000英尺 (300米)。RS-574定義了9-pin PC串列埠界面和電壓。
信號定義
RS-232標準定義了18種不同的串列埠通訊信號。當然,在UNIX環境下只有6種。
GND - Logic Ground 邏輯地
學術上而言,邏輯地不是個信號,但沒有它,其它信號都不能工作。簡而言之,邏輯地作為參考電壓, 從而使電子器件知道某個電壓是正或負。
TXD - Transmitted Data 傳送數據
TXD信號傳送數據從你的工作站到計算機或在另一端的設備(例如MODEM)。一個mark電壓被解釋為1,與 此同時一space電壓被解釋為0。
RXD - Received Data 接受數據
RXD信號傳送數據從計算機或另一端的設備到你的工作站。類似TXD,mark和space電壓相應地被解釋為1和0。
DCD - Data Carrier Detect 載波偵聽
DCD信號接受你的串列埠電纜另一頭的計算機或其它設備信號。一個space電壓表示計算機或設備當前連接著或在線。DCD並不總可用或存在。
DTR - Data Terminal Ready 數據終端準備好
DTR信號由你的工作站產生,告訴另一端的計算機或設備你準備好了 (一個space電壓) 或者沒有 (一個mark電壓)。當你在工作站上打開串列埠界面時,DTR總是自動有效。
CTS - Clear To Send 允許發送
CTS信號接受自串列埠纜線另一端。一個space電壓表明可以從你的工作站發送更多的串接資料。
CTS通常被用作控制從你的工作站發往另一端的串接資料。
RTS - Request To Send 要求發送
RTS信號被你的工作站設置為 space 電壓表明有更多的數據等待傳送。
類似 CTS,RTS幫助控制你的工作站和另一端的計算機或設備間的數據流。多數工作站始終保持這個信號為space電壓。
異步通訊
為使計算機理解進入它的串接資料,它需要一些途徑決定那裡是字元的開始和結束。本指南僅討論異步串接資料。
在異步模式下,串接資料線保持在mark (1)狀態,直到有字元傳送。一位元起始 start 位,字元內容的每一位元,一位元可選的同位,以及一位元或一位元半的終止位。起始位始終是一個 space (0),從而告訴計算機新的串接資料到來。數據能夠在任何時候傳送或接受,所以稱做異步。
圖 1 -- 異步數據通訊
可選的同位是一個簡單數據位的加和,表明了數據位包含偶數或奇數個1。如果是偶同位, 當在字元中的1是偶數個數時,同位為0。如果是奇同位,當字元中的1是奇數個時,同位為0。你可能也聽說過space同位, mark同位, 和無同位。Space同位意味著同位始終為0,而mark同位意味著同位始終為1。無同位意味同位不存在或不傳輸。
余下的被稱為停止位。這可以是1, 1.5, 或2位停止位在字元之間,始終是1。停止位原先是用來給計算機時間處理前面的字元的,但現下只是用來同步計算機和接收字元。
異步數據格式通常表示為"8N1", "7E1",諸如此類。它們相應地表示"8位數據,無同位,1位停止"和"7位數據,偶同位,1位停止"。
什麼是全雙工與半雙工?
全雙工 表明計算機能夠同時接受和發送數據 - 有兩個分離的數據通道(一個輸入,一個輸出)。
半雙工 表明計算機不能同時接受和發送數據。通常這意味著只有一個數據通道用來通訊。這並不意味著一些 RS-232信號沒有使用。而通常是表明通訊鏈路使用了不同于RS-232的標準,而它們不支持全雙工操作。
數據流控制
通常在兩個串列埠間傳送數據時,需要控制數據流。這可能是由於通訊連接媒介、一個串列埠界面、或某些存儲介質的限制。兩種方法通常用于異步數據。
第一個方法叫作"軟體"流控制,使用特殊字元開始 (XON or DC1, 021 octal) 或停止 (XOFF or DC3, 023 octal) 數據流。這些字元由 American Standard Code for Information Interchange ("ASCII")定義。雖然這些代碼在傳送文本訊息時十分有用,但沒有特殊的程式設計手段,在傳輸其它類型訊息的時候就不能使用了。
第二個方法叫做"硬體"流控制,使用RS-232 CTS 和RTS信號代替特殊字元。當準備好接受更多的數據后,接受方設置CTS為space電壓;而設置為mark電壓,表示沒有準備好。同樣的,準備好發送更多數據后,發送方將RTS設置為 space電壓。因為硬體流控制使用獨立的信號,所以同樣情況下,它要比需要傳送更多位元訊息的軟體流控制快許多。 CTS/RTS流控制並不被所有的硬體或作業系統支持。
什麼是中斷?
N正常的一個接受或發送數據信號保持在mark電壓知道一個新的字元被傳送。如果信號掉到space電壓一段較長時間,通常是1/4到1/2秒,就說出現了個中斷。
中斷有時是用于重置通訊線或改變通訊硬體的模式,例如MODEM.第三章,MODEM通訊更為詳細地討論了這些。
同步通訊
不同于異步數據,同步數據表現為連續數據流。為在線讀取數據,計算機必須提供或接受一位元同步時鐘,從而 使接受和發送同步。
即便是同步情況下,計算機仍然必須標明數據的開始。最通常的做法是使用數據包協議,例如 串列埠 Data Link Control("SDLC")或 High-Speed Data Link Control ("HDLC")。
每個協議都定義了表示數據包開始和結束的一定位元序列。且都定義了沒有數據時的位元序列。這些位元序列使 得計算機能夠看到數據包的起始。
因為同步協議不使用每字元的同步位,它們通常提供比異步通訊至少高25%的效率,適合于在兩個以上的串列埠界面 環境下,遠距離網路操作與配置。
儘管同步通訊的速度優勢,多數RS-232硬體仍不支持,主要是由於額外的硬體和軟體需求。
存取串列埠端點
象所有的設備一樣,UNIX提供設備文件以存取端點。要存取一個串列埠端點,你只要簡單地打開相應的設備文件。
串列埠端點文件
每個串列埠端點在UNIX系統上有一個或多個設備文件 (文件在/dev目錄下)相關聯︰
IRIX® | /dev/ttyf1 | /dev/ttyf2 |
HP-UX | /dev/tty1p0 | /dev/tty2p0 |
Solaris®/SunOS® | /dev/ttya | /dev/ttyb |
Linux® | /dev/ttyS0 | /dev/ttyS1 |
Digital UNIX® | /dev/tty01 | /dev/tty02 |
打開串列埠端點
由於串列埠端點是一個文件, open(2) 函數正是用來存取它的。存在的阻礙是UNIX下設備文件經常不允許普通用戶存取。 工作的範圍包括改變有疑問的文件存取許可,以超級用戶(root)運行你的程式, 或使你的程式設置userid,從而它能以設備文件的擁有者運行(由於顯著的安全性問題,不建議用...)
現下我們假設文件能夠被所有的用戶存取。 列表1 表示了在運行Linux的PC上打開串列埠端點一的例子。
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
/*
* 'open_port()' - Open Serial port 1.
*
* Returns the file descriptor on success or -1 on error.
*/
int
open_port(void)
{
int fd; /* File descriptor for the port */
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
/*
* Could not open the 端點.
*/
perror("open_端點: Unable to open /dev/ttyS0 - ");
}
else
fcntl(fd, F_SETFL, 0);
return (fd);
}
其它系統可能需要不同的設備文件名,然而代碼是一樣的。
Open 選項
你將注意到我們打開設備文件時,除了讀寫模式外,還使用了兩個其它的標誌︰
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
O_NOCTTY 標誌告訴UNIX,該程式不想成為那個端點的"控制終端"。如果你不指明這個,任何輸入(例如鍵盤中斷信號, 諸如此類)都會影響你的進程。象getty(1M/8) 之類的程式在啟動登錄進程時,設置這一特性,但通常用戶的程式不想有這種行為。
O_NDELAY 標誌告訴UNIX,該程式不關心DCD信號的輸入狀態 - 即無論另一端端點是否啟用和運行。如果你不指明這個標誌, 你的進程將休眠,直到DCD信號線是space電壓。
寫數據到端點
寫數據到端點是簡單的 -- 只需要使用 write(2) 系統呼叫去傳送數據︰
n = write(fd, "ATZ\r", 4);
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);
write 函數返回發送的位元組號 -- 或1如果發現了任何錯誤。通常唯一的錯誤你可能運行進入是 EIO , 即當MODEM或數據鏈路丟失Carrier Detect (DCD) 線。該條件允許你保持直到端點關閉。
從端點讀數據端點
從端點讀取數據有一點小騙術。當你以行數據模式操作端點,每個 read(2) 系統呼叫都會返回, 而不論多少字元實際存在于串列埠輸入暫存區中。 如果沒有字元存在,呼叫將阻塞(等待)直到有字元進入,或超時,或錯誤出現。read方法能夠用以下方法獲得立即的返回︰
fcntl(fd, F_SETFL, FNDELAY);
當端點沒有接受到字元時,FNDELAY 項使得 read 函數返回0。要實現標準的(阻塞)行為,呼叫 fcntl() 而不帶 FNDELAY 項︰
fcntl(fd, F_SETFL, 0);
這同樣可以在帶O_NDELAY項打開串列埠端點后使用。
關閉串列埠端點
要關閉串列埠端點,只要使用 close 系統呼叫︰
close(fd);
關閉串列埠端點通常同時將DTR信號設置為低,這將使多數MODEM掛機。
第二章,設置串列埠端點
本章討論如何透過POSIX termios界面,在C語言中設置端點。
POSIX終端界面
多數系統支持透過POSIX終端 (串列埠) 界面來改變,例如鮑率,字元尺寸,等參數。你需要做的第一件事情是引入文件 <termios.h>;它定義了終端控制架構和POSIX控制函數。
兩個最重要的POSIX函數函數是 tcgetattr(3) 和 tcsetattr(3)。這些獲得和設定相關的終端特性, 你要提供一個termios 架構的指標包含所有可用的串列埠設置項︰
c_cflag | 控制項 |
c_lflag | 線路項 |
c_iflag | 輸入項 |
c_oflag | 輸出項 |
c_cc | 控制字元 |
c_ispeed | 輸入bps(新界面) |
c_ospeed | 輸出bps(新界面) |
控制項
c_cflag成員控制鮑率、數據位、同位、停止位和硬體流控制。所有支持的設置都有常數對應。CBAUD | 鮑率掩碼 |
B0 | 0 bps (drop DTR) |
B50 | 50 bps |
B75 | 75 bps |
B110 | 110 bps |
B134 | 134.5 bps |
B150 | 150 bps |
B200 | 200 bps |
B300 | 300 bps |
B600 | 600 bps |
B1200 | 1200 bps |
B1800 | 1800 bps |
B2400 | 2400 bps |
B4800 | 4800 bps |
B9600 | 9600 bps |
B19200 | 19200 bps |
B38400 | 38400 bps |
B57600 | 57,600 bps |
B76800 | 76,800 bps |
B115200 | 115,200 bps |
EXTA | 外部速率時鐘 |
EXTB | 外部速率時鐘 |
CSIZE | 位數據位掩碼 |
CS5 | 5位數據位 |
CS6 | 6位數據位 |
CS7 | 7位數據位 |
CS8 | 8位數據位 |
CSTOPB | 2位停止位(不指明是1位) |
CREAD | 接收有效 |
PARENB | 同位有效 |
PARODD | 使用奇同位代替偶同位 |
HUPCL | 最後關閉后,當機(hangup)(降低 DTR) |
CLOCAL | 局部線 - 不改變端點的"擁有者" |
LOBLK | 阻塞模式工作控制輸出 |
CNEW_RTSCTS CRTSCTS | 採用硬體流控制(在所有的平台上都不支持) |
c_cflag成員包含兩個選項必須總是有效,CLOCAL 和 CREAD。這些將保證你的程式不會成為端點的所有者, 從而妨礙控制工作和當機(hangup)信號,並使串列埠界面驅動將讀取進入的數據。
鮑率常數 (CBAUD, B9600, 等等。)是用來老式界面,那些缺少 c_ispeed 和 c_ospeed 成員的。參考下一章關於用于鮑率設定的POSIX函數。
決不 直接初始化 c_cflag (或其它)成員;你應該總是用 AND, OR和NOT位元操作來設定或清除成員中的位元。 不同的作業系統版本(甚至是修補檔) 能也確實使用不同模式處理位元,所以使用位元操作符,能夠使你避免在使用一個新的串列埠設 備要用的位元時遭到殘敗。
設置鮑率
鮑率存儲于不同的位置倚賴于具體系統。舊版本界面存儲鮑率在 c_cflag成員中,使用一個表4中的鮑率常數, 而新的實現提供了c_ispeed和 c_ospeed 成員來包含鮑率數值。
cfsetospeed(3) 和 cfsetispeed(3) 函數用于提供鮑率設置到 termios 架構中,而不論其下的作業系統系統界面如何。
典型地你使用 列表 2 來設置鮑率。
struct termios options;
/*
* Get the current options for the port...
*/
tcgetattr(fd, options);
/*
* Set the bps rates to 19200...
*/
cfsetispeed( options, B19200);
cfsetospeed( options, B19200);
/*
* Enable the receiver and set local mode...
*/
options.c_cflag |= (CLOCAL | CREAD);
/*
* Set the new options for the port...
*/
tcsetattr(fd, TCSANOW, options);
tcgetattr(3) 函數用當前的串列埠端點設置來填充你的termios架構。在我們設置了鮑率、局部模式和串接資料接收, 我們使用tcsetattr(3)選定新的設置。TCSANOW常數表明所有的改變立即生效而不等待發送或接受的數據結束。還 有其它的參數常數用來等待輸出和輸入結束或刷新輸入輸出暫存區。
多數系統不支持端點有不同的輸入與輸出速率,所以確定設置為相同的值以獲得最大的可移植性。
TCSANOW | 立即改變,不等待數據結束 |
TCSADRAIN | 等待直到所有的都傳完 |
TCSAFLUSH | 刷新輸入輸出暫存區,然後改變 |
設置字元尺寸
不同于鮑率,沒有簡便的函數用于設定字元尺寸。取而代之的是你需要做一點位元掩碼的操作來實現。字元的大小是以位方 式設定︰
options.c_cflag = ~CSIZE; /* Mask the character size bits */
options.c_cflag |= CS8; /* Select 8 data bits */
設置同位(Parity)檢查
類似字元尺寸,你必須手動設置同位有效和同位位元組。UNIX串列埠驅動支持偶, 奇, 和無同位產生。空同位能夠透過聰明的程式設計來模擬。
- 無同位 (8N1):
options.c_cflag = ~PARENB
options.c_cflag = ~CSTOPB
options.c_cflag = ~CSIZE;
options.c_cflag |= CS8; - 偶同位 (7E1):
options.c_cflag |= PARENB
options.c_cflag = ~PARODD
options.c_cflag = ~CSTOPB
options.c_cflag = ~CSIZE;
options.c_cflag |= CS7; - 奇同位 (7O1):
options.c_cflag |= PARENB
options.c_cflag |= PARODD
options.c_cflag = ~CSTOPB
options.c_cflag = ~CSIZE;
options.c_cflag |= CS7; - 空同位設置同無同位 (7S1):
options.c_cflag = ~PARENB
options.c_cflag = ~CSTOPB
options.c_cflag = ~CSIZE;
options.c_cflag |= CS8;
設置硬體流控制
某些版本的UNIX支持硬體流控制,採用的是CTS (Clear To Send) 和RTS (Request To Send) 信號線。如果 CNEW_RTSCTS 或 CRTSCTS 常數在你的系統上有定義,硬體流控制可能得到支持。以下用于選擇硬體流控制︰
options.c_cflag |= CNEW_RTSCTS; /* Also called CRTSCTS */
類似的,關閉硬體流控制︰
options.c_cflag = ~CNEW_RTSCTS;
局部項
局部模式成員 c_lflag 控制輸入字元如何被串列埠驅動使用。一般你設置 c_lflag 成員為 canonical 或 raw 輸入模式。
ISIG | 使SIGINTR, SIGSUSP, SIGDSUSP, SIGQUIT等信號作用 |
ICANON | 設定規範canonical(或行式raw) |
XCASE | Map uppercase \lowercase (廢除) |
ECHO | 回顯輸入字元 |
ECHOE | 回顯擦除字元 BS-SP-BS |
ECHOK | 在刪除字元后,回顯 NL |
ECHONL | 回顯 NL |
NOFLSH | 不在中斷或退出字元后刷新輸入暫存區 |
IEXTEN | 擴充函數作用 |
ECHOCTL | 以^char模式回顯控制字元和以~?模式顯示刪除字元 |
ECHOPRT | 回顯提示有刪除字元 |
ECHOKE | BS-SP-BS整行,在有行刪除時 |
FLUSHO | 刷新輸出 |
PENDIN | 在下次讀或輸入時,將未決字元重打 |
TOSTOP | 傳送SIGTTOU信號作為背景 |
選擇規範(Canonical)模式輸入
規範模式輸入是行式操作的。輸入字元存放于一用戶可編輯的暫存區中,直到接受到CR (返回)或 LF (進行)。
當選擇這個模式時,你通常選擇ICANON, ECHO, and ECHOE options:
options.c_lflag |= (ICANON | ECHO | ECHOE);
選擇行(Raw)模式輸入
行式輸入是不經處理的。輸入字元在接受到后就直接傳遞,保持原樣不處理。一般當你用行模式輸入時,你需要去除 ICANON, ECHO, ECHOE, and ISIG 項︰
options.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
關於回顯(Echo)的說明
決不 使用輸入回顯 (ECHO, ECHOE) ,當傳送指令到一MODEM或其它計算機時,因為如果對方也回顯字元的話,你就造成了在兩個串列埠間回饋死循環﹗
輸入項
輸入模式成員 c_iflag 控制任何在端點接收字元的輸入處理。類似 c_cflag 域,最終存儲于 c_iflag 位元符OR操作于期望項。
INPCK | 同位有效 |
IGNPAR | 忽略同位錯誤 |
PARMRK | 標記同位錯誤 |
ISTRIP | 剝離同位 |
IXON | 軟體流控制作用 (輸出) |
IXOFF | 軟體流控制作用 (輸入) |
IXANY | 允許任何字元再次啟動流 |
IGNBRK | 忽略任何中斷條件 |
BRKINT | 傳送SIGINT,當檢測到中斷 |
INLCR | 映射NL到CR |
IGNCR | 忽略CR |
ICRNL | 映射CR到NL |
IUCLC | 映射大寫到小寫 |
IMAXBEL | 在輸入線上回顯BEL過長 |
設置輸入同位項
當你在c_cflag成員中選擇了同位作用(PARENB),你就應該使輸入同位檢查作用。輸入同位檢查的相關常數是 INPCK, IGNPAR, PARMRK , 和 ISTRIP。一般你將選擇 INPCK 和 ISTRIP 來檢查和剝離同位︰
options.c_iflag |= (INPCK | ISTRIP);
IGNPAR 是個稍微有點危險的選項,告訴串列埠驅動忽略同位錯誤和傳遞輸入數據,只當沒有錯誤發生。在測試通訊鏈路質 量時有用,但一般沒有實際使用的理由。
PARMRK 使用特殊字元將輸入流中同位錯誤標記'marked'。如果 IGNPAR 作用,在每個有同位錯誤的字元前插入一個NUL字元 (000 八進製)。 除此之外,伴隨錯誤字元發送DEL (177 八進製) 和 NUL 字元。
設置軟體流控制
軟體流控制透過設置 IXON, IXOFF 和 IXANY 常數︰
options.c_iflag |= (IXON | IXOFF | IXANY);
取消軟體流控制,只要簡單地屏蔽那些數據位︰
options.c_iflag = ~(IXON | IXOFF | IXANY);
XON (開始傳送數據) XOFF (停止數據傳送) 字元的定義在 c_cc 數組中定義,描述如下。
輸出項
c_oflag 成員包含輸出過濾項。類似輸入模式,你能選擇處理或行式數據輸出。
OPOST | 輸出后處理 (不設置 = 行式輸出) |
OLCUC | 映射小寫到大寫 |
ONLCR | 映射NL到CR-NL |
OCRNL | 映射CR到NL |
NOCR | 沒有CR輸出在列0 |
ONLRET | NL實現CR功能 |
OFILL | 對于延時用填充字元 |
OFDEL | 填充字元DEL |
NLDLY | 在行之間,需要掩蔽延時 |
NL0 | NL沒有延時 |
NL1 | 在換行后,新的輸出前延時100ms |
CRDLY | 返回到左邊,需要掩蔽的延時 |
CR0 | CR沒有延時 |
CR1 | CR后的延時,倚賴于當前的列位置 |
CR2 | CR后延時100ms |
CR3 | CR后延時150ms |
TABDLY | TAB后需要延時 |
TAB0 | TAB后沒有延時 |
TAB1 | TAB后的延時根據當前的列位置 |
TAB2 | TAB后延時100ms |
TAB3 | TAB擴充為空格 |
BSDLY | BS后需要延時掩蔽 |
BS0 | BS后無延時 |
BS1 | BS后延時50ms |
VTDLY | VT后需要延時掩蔽 |
VT0 | VT后無延時 |
VT1 | VT后延時2秒 |
FFDLY | FF后需要延時掩蔽 |
FF0 | FF后無延時 |
FF1 | FF后延時2秒 |
選擇輸出處理
輸出處理是透過選擇c_oflag成員中的OPOST ︰
options.c_oflag |= OPOST;
在眾多不同的項目中,你可能只會用到ONLCR 項,該項映射換行為 CR-LF 對。余下的輸出項主要都是出于歷史原因,要追溯到印表機和終端還不能和串接資料流保持一致的時候﹗
選擇行式輸出
行式輸出是透過重置c_oflag成員中的OPOST 項來實現的。
options.c_oflag = ~OPOST;
當OPOST 設為不可用十,所有其它在c_oflag中的位元項都被忽略。
控制字元
c_cc 字元數組包含了控制字元和超時參數。數組的每個元素都有常數定義。
VINTR | 中斷 | CTRL-C |
VQUIT | 退出 | CTRL-Z |
VERASE | 擦除 | 空格(BS) |
VKILL | 刪行 | CTRL-U |
VEOF | 文件結尾 | CTRL-D |
VEOL | 行結尾 | 返回(CR) |
VEOL2 | 第二行結尾 | 進行(LF) |
VMIN | 讀取的最小字元數 | - |
VTIME | 等待時間(數十秒) | - |
設置軟體流控制字元
c_cc數組的VSTART 和 VSTOP 元素包含用于軟體流控制的字元。通常它們設定為DC1 (021 八進製) 和 DC3 (023 八進製),代表ASCII 標準的XON和XOFF字元。
設置讀取超時
UNIX串列埠界面驅動提供確定的字元和包超時設定。c_cc 數組的兩個元素用于該目的︰VMIN 和 VTIME。 對于透過open或 fcntl的文件,在規範模式下canonical或當NDELAY項被設置,忽略超時設置。
VMIN 指明了最小的讀取字元。如果它被設置為0,VTIME的值用于指明等待讀取每個字元的時間。注意這不是意味著 read 呼叫讀取N個位元組需要等待N個字元進入。而是,超時將僅對第一個字元作用,並且read 呼叫將立即返回字元的個數 (最大為你要求的數目)。
如果 VMIN 非0,VTIME 指明了等待第一個字元到來的時間。如果在指定時間內讀到一個字元,任何讀操作將阻塞 (等待)直到所有VMIN 規定的字元數讀到。那就是說,一旦第一個字元讀到,串列埠界面驅動期望接收一整個包的字元 (VMIN 規定字元數)。如果在規定時間內沒有字元被讀取,read呼叫返回0。該方法允許你告訴串列埠驅動,你準確需要 N 個位元組,以及 read 呼叫將只返回0或N位元組。然而,超時只對第一個字元作用,所以如果由於某些原因驅動丟失了N個字 節包中的某個字元,read 呼叫將永遠阻塞等待字元輸入。
VTIME 指明等待輸入的時間是以十分之一秒為單位。如果VTIME 設置為0 (缺省),讀操作將不確定地阻塞(等待) ,直到用open or fcntl在端點上設置NDELAY 項。
第三章,MODEM通訊
本章包括了撥號調製/解調器(MODEM)的通訊基礎知識。供MODEM使用的例子採用的是事實上的標準AT指令。
什麼是MODEM?
MODEM是調製串接資料為頻率信號,從而可以在類比數據線路上傳輸,例如電話線或TV電纜接線。一個標準的電話MODEM轉換串接資料 為語音,從而能夠在電話線上傳送,由於速度和複雜的轉換機製,這些語音聽起來象高聲尖叫。
今天的電話MODEM能夠在電話線上以每秒53,000位元的速率傳輸數據,或者說是53kbps。此外,多數MODEM使用數據壓縮技術,從而可 將某些類型的位元速率提升到100kbps以上。
與MODEM通訊
和MODEM通訊的第一步是打開端點並設置為行模式輸入,就象列表 3中所示。
int fd;
struct termios options;
/* open the port */
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
fcntl(fd, F_SETFL, 0);
/* get the current options */
tcgetattr(fd, options);
/* set raw input, 1 second timeout */
options.c_cflag |= (CLOCAL | CREAD);
options.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag = ~OPOST;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;
/* set the options */
tcsetattr(fd, TCSANOW, options);
下一步是建立與MODEM的通訊。最好的辦法是發送"AT"指令到MODEM。這還允許智能MODEM檢測你使用的鮑率。 當MODEM正確連接和加電,它將返回"OK"。
int /* O - 0 = MODEM ok, -1 = MODEM bad */
init_modem(int fd) /* I - Serial port file */
{
char buffer[255]; /* Input buffer */
char *bufptr; /* Current char in buffer */
int nbytes; /* Number of bytes read */
int tries; /* Number of tries so far */
for (tries = 0; tries < 3; tries ++)
{
/* send an AT command followed by a CR */
if (write(fd, "AT\r", 3) < 3)
continue;
/* read characters into our string buffer until we get a CR or NL */
bufptr = buffer;
while ((nbytes = read(fd, bufptr, buffer + sizeof(buffer) - bufptr - 1)) > 0)
{
bufptr += nbytes;
if (bufptr[-1] == '\n' || bufptr[-1] == '\r')
break;
}
/* nul terminate the string and see if we got an OK response */
*bufptr = '\0';
if (strncmp(buffer, "OK", 2) == 0)
return (0);
}
return (-1);
}
標準MODEM指令
多數MODEM支持"AT"指令,這么稱呼是因為每個指令都以"AT"字元開頭。每個指令都以"AT"字元開頭,跟以特殊指令和返回結尾(CR, 015 octal)。
處理指令之后,MODEM將根據指令返回若干文本訊息之一。
ATD - Dial A Number撥一個號碼
ATD指令用戶撥一個指定號碼。除數字外,你還能添加少量內容,包括指明是語音("T")或脈波("P")撥號,等待一秒 (",")和等待撥號音("W")︰
ATDT 555-1212
ATDT 18008008008W1234,1,1234
ATD T555-1212WP1234
MODEM將返回以下訊息之一︰
NO DIALTONE
BUSY
NO CARRIER
CONNECT
CONNECT baud
ATH - Hang Up當機
ATH指令導致MODEM當機(hangup)。因為MODEM是處于"命令"模式,你通常是不需要在普通通話中這么做。
DTR信號下降,將導致多數MODEM當機(hangup);你可以透過將鮑率設置為0至少一秒來實現。降低DTR信號同時也將MODEM返回命令模式。
在當機(hangup)成功后,MODEM將返回"NO CARRIER"。如果MODEM依然連接,將傳回"CONNECT"或"CONNECT BAUD"。
ATZ - Reset MODEM 重置MODEM
ATZ指令重置MODEM。MODEM將返回字元"OK"。MODEM常見通訊問題
首先和最為重要的,不要忘記關閉輸入回顯。 輸入回顯將導致MODEM和計算機間形成回饋回路。
其次,在傳輸指令到MODEM時,必須以返回(CR)結尾,而不是新行(NL)。C對于CR的字元常數是"\r"。
最後,當操作MODEM時,請確認你使用的鮑率是MODEM所支持的。儘管許多MODEM能夠自動檢測鮑率,一些限制 (通常在老式MODEM上是19.2kbps)你必須注意。
第四章,高級串列埠程式設計
本章包括了高級串列埠程式設計技術,使用 ioctl(2) 和 select(2) 系統呼叫。
串列埠端點 IOCTLs
在第二章,串列埠端點設置中,我們使用 tcgetattr 和 tcsetattr 函數來設置串列埠端點。在UNIX下這些函數使用 ioctl(2) 系統呼叫來實現其功能。
ioctl 系統呼叫使用三個參數︰
int ioctl(int fd, int request, ...);
fd 參數指明串列埠端點文件描述符。 request 參數是一個常數定義于<termios.h>
頭文件中,通常是列在表 10 中的某個常數。
TCGETS | 獲得串列埠端點設置。 | tcgetattr |
TCSETS | 立即設置串列埠端點。 | tcsetattr(fd, TCSANOW, options) |
TCSETSF | 刷新輸入輸出暫存區后,設置串列埠端點。 | tcsetattr(fd, TCSAFLUSH, options) |
TCSETSW | 允許輸入輸出暫存區排空后,設置串列埠端點。 | tcsetattr(fd, TCSADRAIN, options) |
TCSBRK | 一個給定時間的中斷 | tcsendbreak, tcdrain |
TCXONC | 軟體流控制 | tcflow |
TCFLSH | 刷新輸入輸出暫存區 | tcflush |
TIOCMGET | 返回"MODEM"狀態位。 | None |
TIOCMSET | 設定"MODEM"狀態位。 | None |
FIONREAD | 返回輸入暫存區中的位元數目 | None |
獲得控制信號
TIOCMGET
ioctl 獲得當前"MODEM"狀態位,包含所有RS-232信號線,除了RXD和TXD, 列在表 11。
要獲得狀態位,呼叫 ioctl 帶一個整數指標保存數據位,就象列表5 .
#include <unistd.h>
#include <termios.h>
int fd;
int status;
ioctl(fd, TIOCMGET, status);
TIOCM_LE | DSR (data set ready/line enable) |
TIOCM_DTR | DTR (data terminal ready) |
TIOCM_RTS | RTS (request to send) |
TIOCM_ST | Secondary TXD (transmit) |
TIOCM_SR | Secondary RXD (receive) |
TIOCM_CTS | CTS (clear to send) |
TIOCM_CAR | DCD (data carrier detect) |
TIOCM_CD | Synonym for TIOCM_CAR |
TIOCM_RNG | RNG (ring) |
TIOCM_RI | Synonym for TIOCM_RNG |
TIOCM_DSR | DSR (data set ready) |
設定控制信號
TIOCMSET
ioctl 設置"MODEM"狀態,定義如上。要降低DTR信號,你可以使用列表 6中的代碼。
列表 6 - 使用TIOCMSET ioctl 降低DTR信號。
#include <unistd.h>
#include <termios.h>
int fd;
int status;
ioctl(fd, TIOCMGET, status);
status = ~TIOCM_DTR;
ioctl(fd, TIOCMSET, status);
能夠設定的數位倚賴于作業系統,驅動和使用模式。查詢你的作業系統文檔以獲得訊息。
獲得存在的位元組個數
FIONREAD
ioctl 獲得串列埠端點輸入暫存區的位元組個數。你傳遞TIOCMGET的同時用一個整數指標獲得位元組個數,例如
列表 7.
#include <unistd.h>
#include <termios.h>
int fd;
int bytes;
ioctl(fd, FIONREAD, bytes);
這在用串列埠端點獲得數據時很有用,使你的程式能夠在嘗試讀取前確定位元組個數。
從串列埠端點選擇輸入
雖然簡單應用程式能夠獲得或等待串列埠端點的數據,多數應用程式並不簡單並需要從多個來源獲得輸入。
UNIX透過select(2) 系統呼叫提供這一能力。系統呼叫允許你的程式檢查輸入,輸出,或在一或多個文件描述符上的錯誤條件。 文件描述符能夠指定到串列埠端點,普通文件,其它設備,管道或端點。你能檢查獲得的未完輸入,等待輸入結束,或者在指定時間后超時, 使用select系統呼叫非常靈活。
多數GUI工具提供了一個界面存取select;我們將在本章稍后討論X Intrinsics ("Xt")庫。
SELECT系統呼叫
select 系統呼叫能夠接受5個參數︰
int select(int max_fd, fd_set *input, fd_set *output, fd_set *error,
struct timeval *timeout);
max_fd 參數指定了最高數字的文件描述符,在input, output, 和 error 集合。input, output, 和error 參數指明了用于未處理的文件描述符設置的輸入,輸出或錯誤條件;指定 NULL
來禁止檢測相關條件。這些設置用以下三個宏初始化︰
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
FD_ZERO宏完全清除設定。FD_SET 和 FD_CLR 宏從設置中添加和移除相關的文件描述符。
超時 timeout 參數指明了超時設置由秒(timeout.tv_sec)和毫秒(timeout.tv_usec )組成。從一或多個文件描述符來源獲得數據,設置秒和毫秒為零。無限等待,指定NULL
到超時指標。
select 系統呼叫返回未處理的文件描述符的個數,或者-1代表一個錯誤。
使用SELECT系統呼叫
假設我們從一個串列埠端點和一個套接字獲得數據。我們從每個文件描述符檢查輸入,但同時想在10秒內沒有輸入時提醒用戶。要實現這, 我們需要使用select系統呼叫,如列表 8.
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
int n;
int socket;
int fd;
int max_fd;
fd_set input;
struct timeval timeout;
/* Initialize the input set */
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(socket, input);
max_fd = (socket > fd ? socket : fd) + 1;
/* Initialize the timeout structure */
timeout.tv_sec = 10;
timeout.tv_usec = 0;
/* Do the select */
n = select(max_fd, input, NULL, NULL, timeout);
/* See if there was an error */
if (n < 0)
perror("select failed");
else if (n == 0)
puts("TIMEOUT");
else
{
/* We have input */
if (FD_ISSET(fd, input))
process_fd();
if (FD_ISSET(socket, input))
process_socket();
}
你注意到我們首先檢查select 系統呼叫的返回值。0和-1值導致警告和錯誤訊息。大于0的值表明有一或多個未處理的值。
要確定哪個文件描述符有沒處理的輸入,我們使用FD_ISSET 宏來測試每個文件描述符的輸入設置。如果文件描述符設定了,且設定的條件出現(這裡就是有輸入沒處理),我們就需要做點什麼。
使用SELECT配合X Intrinsics庫
X Intrinsics庫提供了一個 select 系統呼叫的界面,透過 XtAppAddInput(3x) 和 XtAppRemoveInput(3x) 函數︰
int XtAppAddInput(XtAppContext context, int fd, int mask,
XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);
select 系統呼叫是內部使用,用于實現來自X server的超時,工作過程和輸入檢查。這些函數能夠與任何基于Xt的工具一同使用,包括Xaw, Lesstif, and Motif。
XtAppAddInput的proc 參數指明了當在文件描述符上選定的條件出現時(例如,有輸入),呼叫的函數。類似前面的例子,你可以用 process_fd 或 process_socket 函數。
因為Xt限制了你存取 select 系統呼叫,你需要用其它方法實現超時功能,可能的方法有透過XtAppAddTimeout(3x)。
留言列表