close

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自由文檔許可.





目錄




介紹



第一章,串列埠通訊基礎



第二章,設置串列埠



第三章,MODEM通訊



第四章,高級串列埠程式設計



附錄A,引腳



附錄B,ASCII控制碼



附錄C,GNU自由文檔許可

附錄D,修改歷史







介紹


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 - RS-232引腳定義


Pin
描述
Pin
描述
Pin
描述
Pin
描述
Pin
描述






















































1Earth Ground6DSR - Data Set Ready11未定義16Secondary RXD21Signal Quality Detect
2TXD - Transmitted Data7GND - Logic Ground12Secondary DCD17Receiver Clock22Ring Detect
3RXD - Received Data8DCD - Data Carrier Detect13Secondary CTS18未定義23Data Rate Select
4RTS - Request To Send9Reserved14Secondary TXD19Secondary RTS24Transmit Clock
5CTS - Clear To Send10Reserved15Transmit Clock20DTR - Data Terminal Ready25未定義


其它兩個你可能也看到過的串列埠界面標準是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目錄下)相關聯︰



表 2 - 串列埠端點設備文件


系統
端點 1
端點 2



















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上打開串列埠端點一的例子。


列表1 - 打開一個串列埠端點。


    #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 架構的指標包含所有可用的串列埠設置項︰



表 3 - Termios架構成員


成員
描述




















c_cflag控制項
c_lflag線路項
c_iflag輸入項
c_oflag輸出項
c_cc控制字元
c_ispeed輸入bps(新界面)
c_ospeed輸出bps(新界面)


控制項

c_cflag成員控制鮑率、數據位、同位、停止位和硬體流控制。所有支持的設置都有常數對應。


表 4 - c_cflag成員對應的常數


常數
描述








































































































CBAUD鮑率掩碼
B00 bps (drop DTR)
B5050 bps
B7575 bps
B110110 bps
B134134.5 bps
B150150 bps
B200200 bps
B300300 bps
B600600 bps
B12001200 bps
B18001800 bps
B24002400 bps
B48004800 bps
B96009600 bps
B1920019200 bps
B3840038400 bps
B5760057,600 bps
B7680076,800 bps
B115200115,200 bps
EXTA外部速率時鐘
EXTB外部速率時鐘
CSIZE位數據位掩碼
CS55位數據位
CS66位數據位
CS77位數據位
CS88位數據位
CSTOPB2位停止位(不指明是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 來設置鮑率。


列表 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常數表明所有的改變立即生效而不等待發送或接受的數據結束。還 有其它的參數常數用來等待輸出和輸入結束或刷新輸入輸出暫存區。


多數系統不支持端點有不同的輸入與輸出速率,所以確定設置為相同的值以獲得最大的可移植性。




表 5 - tcsetattr 常數


常數
描述








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 輸入模式。





表 6 - c_lflag成員常數


常數
描述












































ISIG使SIGINTR, SIGSUSP, SIGDSUSP, SIGQUIT等信號作用
ICANON設定規範canonical(或行式raw)
XCASEMap uppercase \lowercase (廢除)
ECHO回顯輸入字元
ECHOE回顯擦除字元 BS-SP-BS
ECHOK在刪除字元后,回顯 NL
ECHONL回顯 NL
NOFLSH不在中斷或退出字元后刷新輸入暫存區
IEXTEN

擴充函數作用


ECHOCTL以^char模式回顯控制字元和以~?模式顯示刪除字元
ECHOPRT回顯提示有刪除字元
ECHOKEBS-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操作于期望項。




表 7 - c_iflag成員常數


常數
描述









































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, IXOFFIXANY 常數︰


    options.c_iflag |= (IXON | IXOFF | IXANY);

取消軟體流控制,只要簡單地屏蔽那些數據位︰


    options.c_iflag  = ~(IXON | IXOFF | IXANY);

XON (開始傳送數據) XOFF (停止數據傳送) 字元的定義在 c_cc 數組中定義,描述如下。


輸出項


c_oflag 成員包含輸出過濾項。類似輸入模式,你能選擇處理或行式數據輸出。



表 8 - c_oflag成員常數


常數
描述

























































































OPOST輸出后處理 (不設置 = 行式輸出)
OLCUC映射小寫到大寫
ONLCR映射NL到CR-NL
OCRNL映射CR到NL
NOCR沒有CR輸出在列0
ONLRETNL實現CR功能
OFILL對于延時用填充字元
OFDEL填充字元DEL
NLDLY在行之間,需要掩蔽延時
NL0NL沒有延時
NL1在換行后,新的輸出前延時100ms
CRDLY返回到左邊,需要掩蔽的延時
CR0CR沒有延時
CR1CR后的延時,倚賴于當前的列位置
CR2CR后延時100ms
CR3CR后延時150ms
TABDLYTAB后需要延時
TAB0TAB后沒有延時
TAB1TAB后的延時根據當前的列位置
TAB2TAB后延時100ms
TAB3TAB擴充為空格
BSDLYBS后需要延時掩蔽
BS0BS后無延時
BS1BS后延時50ms
VTDLYVT后需要延時掩蔽
VT0VT后無延時
VT1VT后延時2秒
FFDLYFF后需要延時掩蔽
FF0FF后無延時
FF1FF后延時2秒


選擇輸出處理


輸出處理是透過選擇c_oflag成員中的OPOST


    options.c_oflag |= OPOST;

在眾多不同的項目中,你可能只會用到ONLCR 項,該項映射換行為 CR-LF 對。余下的輸出項主要都是出于歷史原因,要追溯到印表機和終端還不能和串接資料流保持一致的時候﹗


選擇行式輸出


行式輸出是透過重置c_oflag成員中的OPOST 項來實現的。


    options.c_oflag  = ~OPOST;

OPOST 設為不可用十,所有其它在c_oflag中的位元項都被忽略。


控制字元


c_cc 字元數組包含了控制字元和超時參數。數組的每個元素都有常數定義。



表 9 - c_cc成員中的控制字元


常數
描述
Key



































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中所示。


列表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"。


列表 4 - 初試化MODEM.


    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 中的某個常數。



表 10 - IOCTL requests 串列埠端點


Request
描述
POSIX Function







































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信號線,除了RXDTXD, 列在表 11。


要獲得狀態位,呼叫 ioctl 帶一個整數指標保存數據位,就象列表5 .


列表 5 - 獲得MODEM狀態


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

int fd;
int status;

ioctl(fd, TIOCMGET, status);



表 11 - 控制常數


常數
描述
































TIOCM_LEDSR (data set ready/line enable)
TIOCM_DTRDTR (data terminal ready)
TIOCM_RTSRTS (request to send)
TIOCM_STSecondary TXD (transmit)
TIOCM_SRSecondary RXD (receive)
TIOCM_CTSCTS (clear to send)
TIOCM_CARDCD (data carrier detect)
TIOCM_CDSynonym for TIOCM_CAR
TIOCM_RNGRNG (ring)
TIOCM_RISynonym for TIOCM_RNG
TIOCM_DSRDSR (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.


列表 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.


列表 8 - 使用SELECT處理一個以上的輸入來源。


    #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。


XtAppAddInputproc 參數指明了當在文件描述符上選定的條件出現時(例如,有輸入),呼叫的函數。類似前面的例子,你可以用 process_fd process_socket 函數。


因為Xt限制了你存取 select 系統呼叫,你需要用其它方法實現超時功能,可能的方法有透過XtAppAddTimeout(3x)


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 立你斯 的頭像
    立你斯

    立你斯學習記錄

    立你斯 發表在 痞客邦 留言(0) 人氣()