串口本身,標(biāo)準(zhǔn)和硬件串口是計(jì)算機(jī)上的串行通訊的物理接口。計(jì)算機(jī)歷史上,串口曾經(jīng)被廣泛用于連接計(jì)算機(jī)和終端設(shè)備和各種外部設(shè)備。雖然以太網(wǎng)接口和USB接口也是以一個串行流進(jìn)行數(shù)據(jù)傳送的,但是串口連接通常特指那些與RS-232標(biāo)準(zhǔn)兼容的硬件或者調(diào)制解調(diào)器的接口。雖然現(xiàn)在在很多個人計(jì)算機(jī)上,原來用以連接外部設(shè)備的串口已經(jīng)廣泛的被USB和Firewire替代;而原來用以連接網(wǎng)絡(luò)的串口則被以太網(wǎng)替代,還有用以連接終端的串口設(shè)備則已經(jīng)被MDA或者VGA取而代之。但是,一方面因?yàn)榇诒旧碓靸r便宜技術(shù)成熟,另一方面因?yàn)榇诘目刂婆_功能RS-232標(biāo)準(zhǔn)高度標(biāo)準(zhǔn)化并且非常普及,所以直到現(xiàn)在它仍然被廣泛應(yīng)用到各種設(shè)備上。 某些計(jì)算機(jī)使用一個叫做UART的集成電路來作為串口設(shè)備。這個集成電路可以進(jìn)行字符和異步串行通訊序列之間的轉(zhuǎn)換,并且可以自動地處理數(shù)據(jù)的時序。而某些低端設(shè)備則會讓CPU直接通過輸出針來傳送數(shù)據(jù),這種技術(shù)叫做bit-banging。 因?yàn)椤按凇?,RS-232和UARTs基本上總是在同一個語境中出現(xiàn),所以這些名詞通常會被搞混。下面逐一解釋以下一些重要的名詞和術(shù)語。 什么是串行通信計(jì)算機(jī)可以每次傳送一個或者多個位(bit)的數(shù)據(jù)?!按小敝傅氖矫看沃粋鬏斠晃?1bit)數(shù)據(jù)。 當(dāng)需要通過串行通訊傳輸一個字(word)的數(shù)據(jù)時,只能以每次一位的方式接收或者發(fā)送。每個位可能是on(1)或者off(0)。很多技術(shù)術(shù)語中經(jīng)常用mark表示on,而space表示off。 串行數(shù)據(jù)的速度通常用每秒傳輸?shù)淖止?jié)數(shù)bits-per-second(bps)或者波特率(baud)表示。這個值表示的是每秒鐘被送出的0和1的個數(shù)。很久很久以前,300bps就是很快的速度了,而現(xiàn)在的電腦可以處理高達(dá)430,800的RS-232速率。表示波特率的單位還有kpbs和Mbps,1kps=1000bps而1Mbps=1000kbps。 一般有人提到串行設(shè)備的時候,它通常說可能是某種數(shù)據(jù)通訊設(shè)備-DCE(Data Communications Equipment)或者數(shù)據(jù)終端設(shè)備-DTE(Data Terminal Equipment)。它們之間的區(qū)別非常簡單,每個信號對,比如傳送和接收,它們倆正好是相反的。如果需要將兩個DTE或者DCE設(shè)備連接起來的話,需要適配器或者交叉線纜將信號對交換。 什么是RS-232RS-232是EIA(Electronic Industries Association)定義的串行通信的電器接口。RS-232事實(shí)上有三種(A,B和C),它們分別采用不同的電壓來表示on和off。最被廣泛使用的是RS-232C,它將mark(on)比特的電壓定義為-3V到-12V之間,而將space(off)的電壓定義到+3V到+12V之間。雖然RS-232C標(biāo)準(zhǔn)說信號最遠(yuǎn)被傳輸8m,但事實(shí)上你可以使用它傳輸更長的距離,直到信號波特率已經(jīng)小到不行了為止。 RS-232的連結(jié)線中除去用來傳入傳出數(shù)據(jù)的電線,還有一些用來提供時序,狀態(tài)和握手的電線: RS-232 針腳定義 DB-25
DB-9
另外兩個比較常見的串行接口的標(biāo)準(zhǔn)式RS-422和RS-574。RS-422使用更低的電壓和差分信號,這樣可以將傳輸距離擴(kuò)張到300m。而RS-574定義了通??梢砸姷降挠迷陔娔X上的9針連接器和電壓。 信號定義RS-232標(biāo)準(zhǔn)定義了18個不同的串行通信的信號。而這些之中,僅僅有如下6個可以在UNIX環(huán)境中使用。
異步通訊計(jì)算機(jī)為了弄懂傳給它的串行數(shù)據(jù),它需要確定每個字符開始和結(jié)束的位置。這通常是用異步串行數(shù)據(jù)來完成的。 在異步模式中,除非有字符被傳輸,否則串行數(shù)據(jù)線總是處于mark(1)狀態(tài)。有一個start位會被加入傳輸字符的各個位之前,在字符本身的位之后會有一個可選的parity位和一個或者多個stop位。Start位總是space(0)并且它會告訴計(jì)算機(jī)新的串行數(shù)據(jù)過來了。數(shù)據(jù)可以隨時被送出或者接收,這就是所謂的異步。 #ref(): File not found: "async.gif" at page "Linux串口編程詳解" 那個可選的parity位僅僅是所有傳輸位的和,這個和用以表示傳輸字符中有奇數(shù)個1還是偶數(shù)個1。在偶數(shù)parity中,如果有傳輸字符中有偶數(shù)個1,那么parity位被設(shè)置成0,而傳輸字符中有奇數(shù)個1,那么parity位被設(shè)置成1。在奇數(shù)parity中,位設(shè)置與此相反。還有一些術(shù)語,比如space parity, mark parity和no parity。Space parity是指parity位會一直被設(shè)置位0,而mark parity正好與此相反,parity會一直是1。No parity的意思就是根本不會傳輸parity位。 剩余的位叫做stop位。傳輸字符之間可以有1個,1.5個或者2個stop位,而且,它們的值總是1。傳統(tǒng)上,Stop位式用給計(jì)算機(jī)一些時間處理前面的字符的,但是它只是被用來同步接收數(shù)據(jù)的計(jì)算機(jī)和接受的字符。 異步數(shù)據(jù)通常被表示成"8N1","7E1",或者與此類似的形式。這表示“8數(shù)據(jù)位,no parity和1個stop bit”,還有相應(yīng)得,“7數(shù)據(jù)位,even parity和1個stop bit”。 什么是全雙工和半雙工全雙工(Full duplex)是說計(jì)算機(jī)可以同時接受和發(fā)送數(shù)據(jù)——也就是它有兩個分開的數(shù)據(jù)傳輸通道(一個傳入,一個傳出)。 半雙工(Half duplex)表示計(jì)算機(jī)不能同時接受和發(fā)送數(shù)據(jù),而在某一時刻它只能單一的傳送或者接收。這通常意味著,它只有一個數(shù)據(jù)通道。半雙工并不是說RS-232的某些信號不能使用,而是,它通常是使用了有別于RS-232的其他不支持全雙工的標(biāo)準(zhǔn)。 什么是流控制兩個串行接口之間的傳輸數(shù)據(jù)流通常需要協(xié)調(diào)一致才行。這可能是由于用以通信的某個串行接口或者某些存儲介質(zhì)的中間串行通信鏈路的限制造成的。對于異步數(shù)據(jù)這里有兩個方法做到這一點(diǎn)。 第一種方法通常被叫做“軟件”流控制。這種方法采用特殊字符來開始(XON,DC1,八進(jìn)制數(shù)021)或者結(jié)束(XOFF,DC3或者八進(jìn)制數(shù)023)數(shù)據(jù)流。而這些字符都在ASCII中定義好了。雖然這些編碼對于傳輸文本信息非常有用,但是它們卻不能被用于在特殊程序中的其他類型的信息。 第二種方法叫做“硬件”流控制。這種方法使用RS-232標(biāo)準(zhǔn)的CTS和RTS信號來取代之前提到的特殊字符。當(dāng)準(zhǔn)備就緒時,接受一方會將CTS信號設(shè)置成為space電壓,而尚未準(zhǔn)備就緒時它會被設(shè)置成為mark電壓。相應(yīng)得,發(fā)送方會在準(zhǔn)備就緒的情況下將RTS設(shè)置成space電壓。正因?yàn)橛布骺刂剖褂昧擞跀?shù)據(jù)分隔的信號,所以與需要傳輸特殊字符的軟件流控制相比它的速度很快。但是,并不是所有的硬件和操作系統(tǒng)都支持CTS/RTS流控制。 什么是BREAK通常,直到有數(shù)據(jù)傳輸時,接收和傳輸信號會保持在mark電壓。如果一個信號掉到space電壓并且持續(xù)了很長時間,一般來說是1/4到1/2秒,那么就說有一個break條件存在了。 BREAK經(jīng)常被用來重置一條數(shù)據(jù)線或者用來改變像調(diào)制解調(diào)器這樣的設(shè)備的通訊模式。 同步通訊與異步數(shù)據(jù)不同,同步數(shù)據(jù)是一個穩(wěn)定的字節(jié)流。為了能夠在線路上讀取到數(shù)據(jù),計(jì)算機(jī)必須提供或者接受一個時鐘,這樣才能保證發(fā)送端和接收端同步。盡管已經(jīng)有同步時鐘,計(jì)算機(jī)還是必須以某種方式標(biāo)志數(shù)據(jù)流的開端。做這件事情最常見的辦法就是使用像Serial Data Link Control("SDLC")或者High-Speed Data Link Control("HDLC")這樣的數(shù)據(jù)包通訊協(xié)議。 這些協(xié)議每個都定義了一個確定的比特序列來表示數(shù)據(jù)包的開始和結(jié)束。當(dāng)然,它們也定義了一個用來表示沒有數(shù)據(jù)傳輸?shù)谋忍匦蛄?。這些比特序列可以幫助計(jì)算機(jī)識別數(shù)據(jù)包的開端。 因?yàn)橥絽f(xié)議可以不使用每個字符的同步比特位,所以通常它們的性能比異步通訊快最少25%,而且一般比較適用于遠(yuǎn)距離的網(wǎng)絡(luò)鏈接或者有兩個串口接口的配置的情況。盡管同步通訊的速度有優(yōu)勢,大部分RS-232硬件卻不支持它,因?yàn)橥酵ㄓ嵭枰渌挠布蛙浖?/p> 用戶看到的串口和用戶空間的串口編程和其他設(shè)備一樣,Linux也是通過設(shè)備文件來提供訪問串口的功能。當(dāng)需要訪問串口的時候,你只需要open相應(yīng)的文件。 串口的設(shè)備文件Linux系統(tǒng)上一般有一個或者多個串口,而這些串口設(shè)備文件名字比較奇怪,如比下面這樣 串口設(shè)備文件名
打開串口因?yàn)榇诤推渌O(shè)備一樣,在類Unix系統(tǒng)中都是以設(shè)備文件的形式存在的,所以,理所當(dāng)然得你可以使用open(2)系統(tǒng)調(diào)用/函數(shù)來訪問它。但Linux系統(tǒng)中卻有一個稍微不方便的地方,那就是普通用戶一般不能直接訪問設(shè)備文件。你可以選擇以下方式做一些調(diào)整,以便你編寫的程序可以訪問串口。
OK.假如你已經(jīng)準(zhǔn)備好了讓串口設(shè)備文件可以被所有用戶訪問,你可以在Linux系統(tǒng)中實(shí)驗(yàn)一下下面這個程序,它可以打開計(jì)算機(jī)的串口1。 #include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h> /* File control definitions */
#include <errno.h>
#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 port.
*/
perror("open_port: Unable to open /dev/ttyS0 -");
}
else
{
fcntl(fd, F_SETFL, 0);
return (fd);
}
}
打開文件的選項(xiàng)打開串口連接的時候,程序在open函數(shù)中除了Read+Write模式以外還指定了兩個選項(xiàng); fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
標(biāo)志O_NOCTTY可以告訴UNIX這個程序不會成為這個端口上的“控制終端”。如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信號等等,會影響到你的進(jìn)程。而有些程序比如getty(1M/8)則會在打開登錄進(jìn)程的時候使用這個特性,但是通常情況下,用戶程序不會使用這個行為。 O_NDELAY標(biāo)志則是告訴UNIX,這個程序并不關(guān)心DCD信號線的狀態(tài)——也就是不關(guān)心端口另一端是否已經(jīng)連接。如果不指定這個標(biāo)志的話,除非DCD信號線上有space電壓否則這個程序會一直睡眠。 給端口上寫數(shù)據(jù)給端口上寫入數(shù)據(jù)也很簡單,使用write(2)系統(tǒng)調(diào)用就可以發(fā)送數(shù)據(jù)了: n = write(fd, "ATZ\r", 4);
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);
和寫入其他設(shè)備文件的方式相同,write函數(shù)也會返回發(fā)送數(shù)據(jù)的字節(jié)數(shù)或者在發(fā)生錯誤的時候返回-1。通常,發(fā)送數(shù)據(jù)最常見的錯誤就是EIO,當(dāng)調(diào)制解調(diào)器或者數(shù)據(jù)鏈路將Data Carrier Detect(DCD)信號線弄掉了,就會發(fā)生這個錯誤。而且,直至關(guān)閉端口這個情況會一直持續(xù)。 從端口上讀取數(shù)據(jù)從串口上讀取數(shù)據(jù)的時候就得耍花招了。因?yàn)椋绻阍谠瓟?shù)據(jù)模式(raw data mode)操作端口的話,每個read(2)系統(tǒng)調(diào)用都會返回從串口輸入緩沖區(qū)中實(shí)際得到的字符的個數(shù)。在不能得到數(shù)據(jù)的情況下,read(2)系統(tǒng)調(diào)用就會一直等著,只到有端口上新的字符可以讀取或者發(fā)生超時或者錯誤的情況發(fā)生。如果需要read(2)函數(shù)迅速返回的話,你可以使用下面這個方式: fcntl(fd, F_SETFL, FNDELAY); 標(biāo)志FNDELAY可以保證read(2)函數(shù)在端口上讀不到字符的時候返回0。需要回到正常(阻塞)模式的時候,需要再次在不帶FNDELAY標(biāo)志的情況下調(diào)用fcntl(2)函數(shù): fcntl(fd, F_SETFL, 0); 當(dāng)然,如果你最初就是以O(shè)_NDELAY標(biāo)志打開串口的,你也可在之后使用這個方法改變讀取的行為方式。 關(guān)閉串口可以使用close(2)系統(tǒng)調(diào)用關(guān)閉串口: close(fd); 關(guān)閉串口會將DTR信號線設(shè)置成low,這會導(dǎo)致很多調(diào)制解調(diào)器掛起。 配置串口POSIX終端接口很多系統(tǒng)都支持POSIX終端(串口)接口。程序可以利用這個接口來改變終端的參數(shù),比如,波特率,字符大小等等。要使用這個端口的話,你必須將<termios.h>頭文件包含到你的程序中。這個頭文件中定義了終端控制結(jié)構(gòu)體和POSIX控制函數(shù)。 與串口操作相關(guān)的最重要的兩個POSIX函數(shù)可能就是tcgetattr(3)和tcsetattr(3)。顧名思義,這兩個函數(shù)分別用來取得設(shè)設(shè)置終端的屬性。調(diào)用這兩個函數(shù)的時候,你需要提供一個包含著所有串口選項(xiàng)的termios結(jié)構(gòu)體: termios結(jié)構(gòu)體成員
控制選項(xiàng)通過termios結(jié)構(gòu)體的c_cflag成員可以控制波特率,數(shù)據(jù)的比特?cái)?shù),parity,停止位和硬件流控制。下面這張表列出了所有可以使用的常數(shù)。 c_cflag常數(shù)
在傳統(tǒng)的POSIX編程中,當(dāng)連接一個本地的(不通過調(diào)制解調(diào)器)或者遠(yuǎn)程的終端(通過調(diào)制解調(diào)器)時,這里有兩個選項(xiàng)應(yīng)當(dāng)一直打開,一個是CLOCAL,另一個是CREAD。這兩個選項(xiàng)可以保證你的程序不會變成端口的所有者,而端口所有者必須去處理發(fā)散性作業(yè)控制和掛斷信號,同時還保證了串行接口驅(qū)動會讀取過來的數(shù)據(jù)字節(jié)。 波特率常數(shù)(CBAUD,B9600等等)通常指用到那些不支持c_ispeed和c_ospeed成員的舊的接口上。后面文章將會提到如何使用其他POSIX函數(shù)來設(shè)置波特率。 千萬不要直接用使用數(shù)字來初始化c_cflag(當(dāng)然還有其他標(biāo)志),最好的方法是使用位運(yùn)算的與或非組合來設(shè)置或者清除這個標(biāo)志。不同的操作系統(tǒng)版本會使用不同的位模式,使用常數(shù)定義和位運(yùn)算組合來避免重復(fù)工作從而提高程序的可移植性。 設(shè)置波特率不同的操作系統(tǒng)會將波特率存儲在不同的位置。舊的編程接口將波特率存儲在上表所示的c_cflag成員中,而新的接口實(shí)裝則提供了c_ispeed和c_ospeed成員來保存實(shí)際波特率的值。 程序中可是使用cfsetospeed(3)和cfsetispeed(3)函數(shù)在termios結(jié)構(gòu)體中設(shè)置波特率而不用去管底層操作系統(tǒng)接口。下面的代碼是個非常典型的設(shè)置波特率的例子。 struct termios options; /* * Get the current options for the port... */ tcgetattr(fd, &options); /* * Set the baud 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); 函數(shù)tcgetattr(3)會將當(dāng)前串口配置回填到termio結(jié)構(gòu)體option中。然后,程序設(shè)置了輸入輸出的波特率并且將本地模式(CLOCAL)和串行數(shù)據(jù)接收(CREAD)設(shè)置為有效,接著將新的配置作為參數(shù)傳遞給函數(shù)tcsetattr(3)。常量TCSANOW標(biāo)志所有改變必須立刻生效而不用等到數(shù)據(jù)傳輸結(jié)束。其他另一些常數(shù)可以保證等待數(shù)據(jù)結(jié)束或者刷新輸入輸出之后再生效。 tcsetattr常量
不同的系統(tǒng)上可能支持不同的輸入輸出速度,所以,通過串口連接兩臺機(jī)器或者設(shè)備的時候,應(yīng)該將波特率設(shè)置成兩者中較小的那個,即MIN(speed1, speed2)。 設(shè)置字符大小 ?設(shè)置字符大小的時候,這里卻沒有像設(shè)置波特率那么方便的函數(shù)。所以,程序中需要一些位掩碼運(yùn)算來把事情搞定。字符大小以比特為單位指定: options.c_flag &= ~CSIZE; /* Mask the character size bits */ options.c_flag |= CS8; /* Select 8 data bits */ 設(shè)置奇偶校驗(yàn)與設(shè)置字符大小的方式差不多,這里仍然需要組合一些位掩碼來將奇偶校驗(yàn)設(shè)為有效和奇偶校驗(yàn)的類型。UNIX串口驅(qū)動可以生成even,odd和no parity位碼。設(shè)置space奇偶校驗(yàn)需要耍點(diǎn)小手段。
options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8;
options.c_cflag |= PARENB options.c_cflag &= ~PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
options.c_cflag |= PARENB options.c_cflag |= PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; 設(shè)置硬件流控制某些版本的UNIX系統(tǒng)支持通過CTS(Clear To Send)和RTS(Request To Send)信號線來設(shè)置硬件流控制。如果系統(tǒng)上定義了CNEW_RTSCTS和CRTSCTS常量,那么很可能它會支持硬件流控制。使用下面的方法將硬件流控制設(shè)置成有效: options.c_cflag |= CNEW_RTSCTS; /* Also called CRTSCTS 將它設(shè)置成為無效的方法與此類似: options.c_cflag &= ~CNEW_RTSCTS; 本地設(shè)置本地模式成員變量c_lflag可以控制串口驅(qū)動怎樣控制輸入字符。通常,你可能需要通過c_lflag成員來設(shè)置經(jīng)典輸入和原始輸入模式。 成員變量c_lflag可以使用的常量
選擇經(jīng)典輸入經(jīng)典輸入是以面向行設(shè)計(jì)的。在經(jīng)典輸入模式中輸入字符會被放入一個緩沖之中,這樣可以以與用戶交互的方式編輯緩沖的內(nèi)容,直到收到CR(carriage return)或者LF(line feed)字符。 選擇使用經(jīng)典輸入模式的時候,你通常需要選擇ICANON,ECHO和ECHOE選項(xiàng): options.c_lflag |= (ICANON | ECHO | ECHOE); 選擇原始輸入原始輸入根本不會被處理。輸入字符只是被原封不動的接收。一般情況中,如果要使用原始輸入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG選項(xiàng): options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 輸入選項(xiàng)可以通過輸入模式成員c_iflag來控制從端口上收到的字符的輸入過程。與c_cflag一樣,c_iflag的最終值是想要使用的所有狀態(tài)的位運(yùn)算OR的組合。 c_iflag成員可以使用的常量
設(shè)置輸入奇偶校驗(yàn)選項(xiàng) ?當(dāng)程序在c_cflag中設(shè)置了奇偶校驗(yàn)成員(PARENB)的時候,程序就需要將輸入奇偶校驗(yàn)設(shè)置成為有效。與奇偶校驗(yàn)相關(guān)的常量有INPCK,IGNPAR,PARMRK和ISTRIP。一般情況下,你可能需要選擇INPCK和ISTRIP將奇偶校驗(yàn)設(shè)置為有效同時從接收字串中脫去奇偶校驗(yàn)位: options.c_iflag |= (INPCK | ISTRIP); IGNPAR是一個比較危險選項(xiàng),即便有錯誤發(fā)生時,它也會告訴串口驅(qū)動直接忽略奇偶校驗(yàn)錯誤給數(shù)據(jù)放行。這個選項(xiàng)在測試鏈接的通訊質(zhì)量時比較有用而通常不會被用在實(shí)際程序中。 PARMRK會導(dǎo)致奇偶校驗(yàn)錯誤被標(biāo)志成特殊字符加入到輸入流之中。如果IGNPAR選項(xiàng)也是有效的,那么一個NUL(八進(jìn)制000)字符會被加入到發(fā)生奇偶校驗(yàn)錯誤的字符前面。否則,DEL(八進(jìn)制177)和NUL字符會和出錯的字符一起送出。 設(shè)置軟件流控制軟件流控制可以通過IXON,IXOFF和IXANY常量設(shè)置成有效: options.c_iflag |= (IXON | IXOFF | IXANY); 將其設(shè)置為無效的時候,很簡單,只需要對這些位取反: options.c_iflag &= ~(IXON | IXOFF | IXANY); XON(start data)和XOFF(stop data)字符卻是在c_cc數(shù)組中定義的,下面會詳細(xì)描述這個數(shù)組。 輸出選項(xiàng)成員變量c_oflag之中包括了輸出過濾選項(xiàng)。和輸入模式相似,程序可以選擇使用經(jīng)過加工的或者原始的數(shù)據(jù)輸出。 c_oflag成員的常量
選擇加工過的輸出通過在c_oflag成員變量中設(shè)置OPOST選項(xiàng)的方法程序可以選擇加工過的輸入。 options.c_oflag |= OPOST; 在所有選項(xiàng)當(dāng)中,你可能只需要使用ONLCR選項(xiàng)來將行分隔符映射到CR-LF組合對上。其他選項(xiàng)主要是歷史遺留,僅僅與行打印機(jī)和終端跟不上串行數(shù)據(jù)的年代有關(guān)。 選擇原始輸出原始輸出方式可以通過在c_oflag中重置OPOST選項(xiàng)來選擇: options.c_oflag &= ~OPOST; 如果OPOST選項(xiàng)被設(shè)置成無效的話,其他c_oflag中的選項(xiàng)都會失效。 控制字符字符數(shù)組c_cc里面包括了控制字符的定義和超時參數(shù)。這個數(shù)組的每個元素都是以常量定義的。 成員變量c_cc中的控制字符
設(shè)置軟件流控制字符用來做軟件流控制的字符包含在數(shù)組c_cc的VSTART和VSTOP元素里面。通常情況下,它們應(yīng)該被設(shè)置成DC1(八進(jìn)制021)和DC3(八進(jìn)制023),它們在ASCII標(biāo)準(zhǔn)中代表著XON和XOFF字符。 設(shè)置讀取超時UNIX串口驅(qū)動提供了設(shè)置字符和包超時的能力。數(shù)組c_cc中有兩個元素可以用來設(shè)置超時:VMIN和VTIME。在經(jīng)典輸入模式或者通過open(2)和fcntl(2)函數(shù)傳遞NDELAY選項(xiàng)時,超時設(shè)置會被忽略。 VMIN可以指定讀取的最小字符數(shù)。如果它被設(shè)置為0,那么VTIME值則會指定每個字符讀取的等待時間。 如果VMIN不為零,VTIME會指定等待第一個字符讀取操作的時間。如果在這個指定時間中可以開始讀取某個字符,直到VMIN個數(shù)的所有字符全部被讀取,其他讀取操作將會被阻塞(等待)。也就是說,一旦讀取第一個字符,串口驅(qū)動的預(yù)期就是接收到整個字符包(一共VMIN字節(jié))。如果在允許的時間內(nèi)沒有字符被讀取,那么read(2)調(diào)用就會返回0。通過這個方法可以確切得告訴串口驅(qū)動程序需要讀取N個字節(jié),而且read(2)調(diào)用只會返回N或者0。然而,超時設(shè)置只對第一個字符的讀取操作有效,所以,如果因?yàn)槟承┰蝌?qū)動程序在N字節(jié)的包中丟失某個字符的話,read(2)調(diào)用將會一直等下去。 VTIME可以以十分之一秒為單位指定等待字符輸入的時間。如果VTIME設(shè)置為0(默認(rèn)情況),除非open(2)或者fcntl(2)函數(shù)設(shè)置了NDELAY選項(xiàng),否則read(2)將會永久得阻塞(等待)。 調(diào)制解調(diào)器通訊說到串口通訊就不得不提一下通過調(diào)劑解調(diào)器通訊的方式。這里給出的程序例子都適用于支持“事實(shí)上的”標(biāo)準(zhǔn)AT命令集的調(diào)制解調(diào)器。 什么是調(diào)制解調(diào)器調(diào)制解調(diào)器是一種可以將數(shù)字信號的串行數(shù)據(jù)轉(zhuǎn)化為模擬信號頻率的設(shè)備。通過這種轉(zhuǎn)換,信息就可以通過像電話線或者有線電視線纜那樣的模擬數(shù)據(jù)鏈路來傳輸了??谡Z中,經(jīng)常將調(diào)制解調(diào)器稱作“貓”。標(biāo)準(zhǔn)的電話調(diào)制解調(diào)器可以將串行數(shù)據(jù)轉(zhuǎn)化為能夠通過電話線傳輸?shù)囊纛l;因?yàn)檫@種轉(zhuǎn)化非常之快又非常復(fù)雜,所以如果你去聽一下的話,這些音頻很像是大聲尖叫時發(fā)出來的聲音。 今天可以見到的調(diào)制解調(diào)器可以通過電話線每秒傳輸53000比特——5.3Kbps——的數(shù)據(jù)。還有就是,大多數(shù)調(diào)制解調(diào)器都使用數(shù)據(jù)壓縮技術(shù),這樣就可以將某些類型數(shù)據(jù)的傳輸比特率提高到100kbps。 與調(diào)制解調(diào)器通訊于調(diào)制解調(diào)器通訊的第一步就是要以原始輸入模式打開和配置串口。 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);
接下來就需要和調(diào)制解調(diào)器建立通訊連接。最好的辦法就是給調(diào)制解調(diào)器發(fā)送“AT”命令。這也會讓比較只能的調(diào)制解調(diào)器探測到你正在使用的波特率。如果正確地連接到調(diào)制解調(diào)器上,并且調(diào)制解調(diào)器開啟電源,它會返回一個回應(yīng)信號“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);
}
標(biāo)準(zhǔn)調(diào)制解調(diào)器命令大多數(shù)調(diào)制解調(diào)器都支持“AT”命令集。之所以這樣叫是因?yàn)檫@個命令集中的每個命令都是以“AT”字符開頭。每個命令都是以第一列的AT開頭字符后面跟上特殊命令參數(shù)和一個回車符CR(八進(jìn)制015)。調(diào)制解調(diào)器處理完這條命令之后會根據(jù)命令回復(fù)一些文本消息。
通過ATD命令可以撥打一個指定號碼。除過號碼和分隔符(-)以外,你還可以指定以音頻("T")或者脈沖("P")方式撥號,暫停一秒(",")和等待撥號音("W"): ATDT 555-1212 ATDT 18008008008W1234,1,1234 ATD T555-1212WP1234 調(diào)制解調(diào)器可能回復(fù)下面列出的某個消息: NO DIALTONE BUSY NO CARRIER CONNECT CONNECT baud
通過ATH命令可以讓調(diào)制解調(diào)器掛斷。因?yàn)?,調(diào)制解調(diào)器如果在“命令”模式的話,你可能就不能打普通電話了。 如果DTR信號線掉了的話,大部分調(diào)制解調(diào)器也會掛斷。你可以將波特率設(shè)置成0并且持續(xù)至少1秒來做到這一點(diǎn)。再次讓DTR掉落同樣也可以把調(diào)制解調(diào)器重新拉回命令模式。 調(diào)制解調(diào)器成功掛斷以后,它會回復(fù)一個"NO CARRIER"回來。如果調(diào)制解調(diào)器仍然保持連接,它則會發(fā)送"CONNECT"或者"CONNECT baud"這樣的消息。
通過ATZ命令可以重置調(diào)制解調(diào)器。重置之后它會回復(fù)字符串"OK"。
首先,也是最重要的一點(diǎn),千萬不要使用回聲輸入(input echoing)?;芈曒斎霑?dǎo)致調(diào)制解調(diào)器和計(jì)算機(jī)之間產(chǎn)生反饋循環(huán)。 其次,當(dāng)發(fā)送調(diào)制解調(diào)器命令時,命令必須以回車(CR)而不是換行(NL)結(jié)束。C語言中回車的字符常量是"\r"。 最后,處理調(diào)制解調(diào)器通訊的時候,要一定保證你使用了調(diào)制解調(diào)器支持的波特率。雖然大多數(shù)調(diào)制解調(diào)器都支持自動探測波特率,但你也會注意到某些(通常是19.2kbps或者比較老的調(diào)制解調(diào)器)有局限性。 高級串口編程所謂高級串口編程其實(shí)說的就是使用更直接的底層的ioctl(2)和select(2)系統(tǒng)調(diào)用來操作串口。 串口的ioctl ?前文中曾經(jīng)提到使用tcgetattr和tcsetattr函數(shù)來配置串口。UNIX環(huán)境下,這些函數(shù)都是使用ioctl(2)系統(tǒng)調(diào)用來實(shí)現(xiàn)的。 系統(tǒng)調(diào)用ioctl可以帶三個參數(shù): int ioctl(int fd, int request, ...); 顯然,fd參數(shù)對于串口編程來說就是串口設(shè)備文件的文件描述符咯。而request參數(shù)是在<termios.h>頭文件中定義的常量,而且一般不會超出下表所列的范圍。 串口的IOCTL請求
取得控制信號TIOCMGET ioctl可以取得當(dāng)前調(diào)制解調(diào)器的狀態(tài)位。這個狀態(tài)位囊括了除去RXD和TXD信號線的所有RS-232信號,這些都在下表中列出。 控制信號常量
例如下面這個程序片段,你可以通過給ioctl帶一個用來保存狀態(tài)位的整形變量的指針來取得狀態(tài)位。 #include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status); 設(shè)置控制信號TIOCMSET ioctl可以設(shè)置上面定義的調(diào)制解調(diào)器狀態(tài)位。下面的例子展示如何使用它來將DTR信號線設(shè)成掉線狀態(tài)。 #include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &status); 可能被設(shè)置的狀態(tài)位取決于操作系統(tǒng),驅(qū)動和正在使用的模式。關(guān)于更詳細(xì)的信息應(yīng)該去看以下你所使用的操作系統(tǒng)的文檔。 |
|
|