串口本身,標准和硬件 †
串口是計算機上的串行通訊的物理接口。計算機歷史上,串口曾經被廣泛用於連接計算機和終端設備和各種外部設備。雖然以太網接口和USB接口也是以一個串行流進行數據傳送的,但是串口連接通常特指那些與RS-232標准兼容的硬件或者調制解調器的接口。雖然現在在很多個人計算機上,原來用以連接外部設備的串口已經廣泛的被USB和Firewire替代;而原來用以連接網絡的串口則被以太網替代,還有用以連接終端的串口設備則已經被MDA或者VGA取而代之。但是,一方面因為串口本身造價便宜技術成熟,另一方面因為串口的控制台功能RS-232標准高度標准化並且非常普及,所以直到現在它仍然被廣泛應用到各種設備上。 某些計算機使用一個叫做UART的集成電路來作為串口設備。這個集成電路可以進行字符和異步串行通訊序列之間的轉換,並且可以自動地處理數據的時序。而某些低端設備則會讓CPU直接通過輸出針來傳送數據,這種技術叫做bit-banging。 因為“串口”,RS-232和UARTs基本上總是在同一個語境中出現,所以這些名詞通常會被搞混。下面逐一解釋以下一些重要的名詞和術語。
什么是串行通信 †
計算機可以每次傳送一個或者多個位(bit)的數據。“串行”指的式每次只傳輸一位(1bit)數據。 當需要通過串行通訊傳輸一個字(word)的數據時,只能以每次一位的方式接收或者發送。每個位可能是on(1)或者off(0)。很多技術術語中經常用mark表示on,而space表示off。
串行數據的速度通常用每秒傳輸的字節數bits-per-second(bps)或者波特率(baud)表示。這個值表示的是每秒鍾被送出的0和1的個數。很久很久以前,300bps就是很快的速度了,而現在的電腦可以處理高達430,800的RS-232速率。表示波特率的單位還有kpbs和Mbps,1kps=1000bps而1Mbps=1000kbps。 一般有人提到串行設備的時候,它通常說可能是某種數據通訊設備-DCE(Data Communications Equipment)或者數據終端設備-DTE(Data Terminal Equipment)。它們之間的區別非常簡單,每個信號對,比如傳送和接收,它們倆正好是相反的。如果需要將兩個DTE或者DCE設備連接起來的話,需要適配器或者交叉線纜將信號對交換。
什么是RS-232 †
RS-232是EIA(Electronic Industries Association)定義的串行通信的電器接口。RS-232事實上有三種(A,B和C),它們分別采用不同的電壓來表示on和off。最被廣泛使用的是RS-232C,它將mark(on)比特的電壓定義為-3V到-12V之間,而將space(off)的電壓定義到+3V到+12V之間。雖然RS-232C標准說信號最遠被傳輸8m,但事實上你可以使用它傳輸更長的距離,直到信號波特率已經小到不行了為止。 RS-232的連結線中除去用來傳入傳出數據的電線,還有一些用來提供時序,狀態和握手的電線:
RS-232 針腳定義
DB-25
針腳 | 描述 | 針腳 | 描述 | 針腳 | 描述 | 針腳 | 描述 | 針腳 | 描述 |
1 | Earth Ground | 6 | DSR - Data Set Ready | 11 | Unassigned | 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 Detecter | 13 | Secondary CTS | 18 | Unassigned | 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 | Unassigned |
DB-9
針腳 | 名稱 | 全名 | 方向(主機 外設) |
3 | TD | Transmit Data | -> |
2 | RD | Receive Data | <- |
7 | RTS | Request To Send | -> |
8 | CTS | Clear To Send | <- |
6 | DSR | Data Set Ready | <- |
4 | DTR | Data Terminal Ready | -> |
1 | CD | Data Carrier Detect | <- |
9 | RI | Ring Indicator | <- |
5 | - | Signal Ground |
另外兩個比較常見的串行接口的標准式RS-422和RS-574。RS-422使用更低的電壓和差分信號,這樣可以將傳輸距離擴張到300m。而RS-574定義了通常可以見到的用在電腦上的9針連接器和電壓。
信號定義 †
RS-232標准定義了18個不同的串行通信的信號。而這些之中,僅僅有如下6個可以在UNIX環境中使用。
- GND - Logic Ground
從技術角度講,GND不能算是信號。但是沒有它其他信號都不能用了。基本上,logic ground有點像一個參考電壓,通過它來判斷哪個電壓表示正哪個電壓表示負。
- TXD - Transmitted Data
TXD信號負載着從你的電腦或者設備到另一端(比如調制解調器)的數據。Mark范圍的電壓被解析成1,而space范圍電壓被解析成0。
- RXD - Received Data
RXD於TXD正好相反。它負載着從另一端的電腦或者設備上傳到你的工作站的數據。Mark和space的解析方法於TXD一致。
- 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位會被加入傳輸字符的各個位之前,在字符本身的位之后會有一個可選的parity位和一個或者多個stop位。Start位總是space(0)並且它會告訴計算機新的串行數據過來了。數據可以隨時被送出或者接收,這就是所謂的異步。
#ref(): File not found: "async.gif" at page "Linux串口編程詳解"
那個可選的parity位僅僅是所有傳輸位的和,這個和用以表示傳輸字符中有奇數個1還是偶數個1。在偶數parity中,如果有傳輸字符中有偶數個1,那么parity位被設置成0,而傳輸字符中有奇數個1,那么parity位被設置成1。在奇數parity中,位設置與此相反。還有一些術語,比如space parity, mark parity和no parity。Space parity是指parity位會一直被設置位0,而mark parity正好與此相反,parity會一直是1。No parity的意思就是根本不會傳輸parity位。 剩余的位叫做stop位。傳輸字符之間可以有1個,1.5個或者2個stop位,而且,它們的值總是1。傳統上,Stop位式用給計算機一些時間處理前面的字符的,但是它只是被用來同步接收數據的計算機和接受的字符。 異步數據通常被表示成"8N1","7E1",或者與此類似的形式。這表示“8數據位,no parity和1個stop bit”,還有相應得,“7數據位,even parity和1個stop bit”。
什么是全雙工和半雙工 †
全雙工(Full duplex)是說計算機可以同時接受和發送數據——也就是它有兩個分開的數據傳輸通道(一個傳入,一個傳出)。
半雙工(Half duplex)表示計算機不能同時接受和發送數據,而在某一時刻它只能單一的傳送或者接收。這通常意味着,它只有一個數據通道。半雙工並不是說RS-232的某些信號不能使用,而是,它通常是使用了有別於RS-232的其他不支持全雙工的標准。
什么是流控制 †
兩個串行接口之間的傳輸數據流通常需要協調一致才行。這可能是由於用以通信的某個串行接口或者某些存儲介質的中間串行通信鏈路的限制造成的。對於異步數據這里有兩個方法做到這一點。
第一種方法通常被叫做“軟件”流控制。這種方法采用特殊字符來開始(XON,DC1,八進制數021)或者結束(XOFF,DC3或者八進制數023)數據流。而這些字符都在ASCII中定義好了。雖然這些編碼對於傳輸文本信息非常有用,但是它們卻不能被用於在特殊程序中的其他類型的信息。
第二種方法叫做“硬件”流控制。這種方法使用RS-232標准的CTS和RTS信號來取代之前提到的特殊字符。當准備就緒時,接受一方會將CTS信號設置成為space電壓,而尚未准備就緒時它會被設置成為mark電壓。相應得,發送方會在准備就緒的情況下將RTS設置成space電壓。正因為硬件流控制使用了於數據分隔的信號,所以與需要傳輸特殊字符的軟件流控制相比它的速度很快。但是,並不是所有的硬件和操作系統都支持CTS/RTS流控制。
什么是BREAK †
通常,直到有數據傳輸時,接收和傳輸信號會保持在mark電壓。如果一個信號掉到space電壓並且持續了很長時間,一般來說是1/4到1/2秒,那么就說有一個break條件存在了。
BREAK經常被用來重置一條數據線或者用來改變像調制解調器這樣的設備的通訊模式。
同步通訊 †
與異步數據不同,同步數據是一個穩定的字節流。為了能夠在線路上讀取到數據,計算機必須提供或者接受一個時鍾,這樣才能保證發送端和接收端同步。盡管已經有同步時鍾,計算機還是必須以某種方式標志數據流的開端。做這件事情最常見的辦法就是使用像Serial Data Link Control("SDLC")或者High-Speed Data Link Control("HDLC")這樣的數據包通訊協議。
這些協議每個都定義了一個確定的比特序列來表示數據包的開始和結束。當然,它們也定義了一個用來表示沒有數據傳輸的比特序列。這些比特序列可以幫助計算機識別數據包的開端。
因為同步協議可以不使用每個字符的同步比特位,所以通常它們的性能比異步通訊快最少25%,而且一般比較適用於遠距離的網絡鏈接或者有兩個串口接口的配置的情況。盡管同步通訊的速度有優勢,大部分RS-232硬件卻不支持它,因為同步通訊需要其他的硬件和軟件。
用戶看到的串口和用戶空間的串口編程 †
和其他設備一樣,Linux也是通過設備文件來提供訪問串口的功能。當需要訪問串口的時候,你只需要open相應的文件。
串口的設備文件 †
Linux系統上一般有一個或者多個串口,而這些串口設備文件名字比較奇怪,如比下面這樣
串口設備文件名
操作系統 | 串口1 | 串口2 | USB/RS-232轉換器 |
Windows | COM1 | COM2 | - |
Linux | /dev/ttyS0 | /dev/ttyS1 | /dev/ttyUSB0 |
打開串口 †
因為串口和其他設備一樣,在類Unix系統中都是以設備文件的形式存在的,所以,理所當然得你可以使用open(2)系統調用/函數來訪問它。但Linux系統中卻有一個稍微不方便的地方,那就是普通用戶一般不能直接訪問設備文件。你可以選擇以下方式做一些調整,以便你編寫的程序可以訪問串口。
- 改變設備文件的訪問權限設置 [#cd9bd1e0]
- 以root超級用戶的身份運行程序 [#kdd0e577]
- 將你的程序編寫位setuid程序,以串口設備所有者的身份運行程序 [#s7b703ff]
OK.假如你已經准備好了讓串口設備文件可以被所有用戶訪問,你可以在Linux系統中實驗一下下面這個程序,它可以打開計算機的串口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); } }
打開文件的選項 †
打開串口連接的時候,程序在open函數中除了Read+Write模式以外還指定了兩個選項;
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
標志O_NOCTTY可以告訴UNIX這個程序不會成為這個端口上的“控制終端”。如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信號等等,會影響到你的進程。而有些程序比如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,當調制解調器或者數據鏈路將Data Carrier Detect(DCD)信號線弄掉了,就會發生這個錯誤。而且,直至關閉端口這個情況會一直持續。
從端口上讀取數據 †
從串口上讀取數據的時候就得耍花招了。因為,如果你在原數據模式(raw data mode)操作端口的話,每個read(2)系統調用都會返回從串口輸入緩沖區中實際得到的字符的個數。在不能得到數據的情況下,read(2)系統調用就會一直等着,只到有端口上新的字符可以讀取或者發生超時或者錯誤的情況發生。如果需要read(2)函數迅速返回的話,你可以使用下面這個方式:
fcntl(fd, F_SETFL, FNDELAY);
標志FNDELAY可以保證read(2)函數在端口上讀不到字符的時候返回0。需要回到正常(阻塞)模式的時候,需要再次在不帶FNDELAY標志的情況下調用fcntl(2)函數:
fcntl(fd, F_SETFL, 0);
當然,如果你最初就是以O_NDELAY標志打開串口的,你也可在之后使用這個方法改變讀取的行為方式。
關閉串口 †
可以使用close(2)系統調用關閉串口:
close(fd);
關閉串口會將DTR信號線設置成low,這會導致很多調制解調器掛起。
配置串口 †
POSIX終端接口 †
很多系統都支持POSIX終端(串口)接口。程序可以利用這個接口來改變終端的參數,比如,波特率,字符大小等等。要使用這個端口的話,你必須將<termios.h>頭文件包含到你的程序中。這個頭文件中定義了終端控制結構體和POSIX控制函數。
與串口操作相關的最重要的兩個POSIX函數可能就是tcgetattr(3)和tcsetattr(3)。顧名思義,這兩個函數分別用來取得設設置終端的屬性。調用這兩個函數的時候,你需要提供一個包含着所有串口選項的termios結構體:
termios結構體成員
成員 | 描述 |
c_cflag | 控制選項 |
c_lflag | 行選項 |
c_iflag | 輸入選項 |
c_oflag | 輸出選項 |
c_cc | 控制字符 |
c_ispeed | 輸入波特率(NEW) |
c_ospeed | 輸出波特率(NEW) |
控制選項 †
通過termios結構體的c_cflag成員可以控制波特率,數據的比特數,parity,停止位和硬件流控制。下面這張表列出了所有可以使用的常數。
c_cflag常數
常量 | 描述 |
CBAUD | Bit mask for baud rate |
B0 | 0 baud (drop DTR) |
B50 | 50 baud |
B75 | 75 baud |
B110 | 110 baud |
B134 | 134.5 baud |
B150 | 150 baud |
B200 | 200 baud |
B300 | 300 baud |
B600 | 600 baud |
B1200 | 1200 baud |
B1800 | 1800 baud |
B2400 | 2400 baud |
B4800 | 4800 baud |
B9600 | 9600 baud |
B19200 | 19200 baud |
B38400 | 38400 baud |
B57600 | 57,600 baud |
B76800 | 76,800 baud |
B115200 | 115,200 baud |
EXTA | External rate clock |
EXTB | External rate clock |
CSIZE | Bit mask for data bits |
CS5 | 5 data bits |
CS6 | 6 data bits |
CS7 | 7 data bits |
CS8 | 8 data bits |
CSTOPB | 2 stop bits (1 otherwise) |
CREAD | Enable receiver |
PARENB | Enable parity bit |
PARODD | Use odd parity instead of even |
HUPCL | Hangup (drop DTR) on last close |
CLOCAL | Local line - do not change "owner" of port |
LOBLK | Block job control output |
CNEW_RTSCTS/CRTSCTS | Enable hardware flow control (not supported on all platforms) |
在傳統的POSIX編程中,當連接一個本地的(不通過調制解調器)或者遠程的終端(通過調制解調器)時,這里有兩個選項應當一直打開,一個是CLOCAL,另一個是CREAD。這兩個選項可以保證你的程序不會變成端口的所有者,而端口所有者必須去處理發散性作業控制和掛斷信號,同時還保證了串行接口驅動會讀取過來的數據字節。
波特率常數(CBAUD,B9600等等)通常指用到那些不支持c_ispeed和c_ospeed成員的舊的接口上。后面文章將會提到如何使用其他POSIX函數來設置波特率。
千萬不要直接用使用數字來初始化c_cflag(當然還有其他標志),最好的方法是使用位運算的與或非組合來設置或者清除這個標志。不同的操作系統版本會使用不同的位模式,使用常數定義和位運算組合來避免重復工作從而提高程序的可移植性。
設置波特率 †
不同的操作系統會將波特率存儲在不同的位置。舊的編程接口將波特率存儲在上表所示的c_cflag成員中,而新的接口實裝則提供了c_ispeed和c_ospeed成員來保存實際波特率的值。
程序中可是使用cfsetospeed(3)和cfsetispeed(3)函數在termios結構體中設置波特率而不用去管底層操作系統接口。下面的代碼是個非常典型的設置波特率的例子。
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);
函數tcgetattr(3)會將當前串口配置回填到termio結構體option中。然后,程序設置了輸入輸出的波特率並且將本地模式(CLOCAL)和串行數據接收(CREAD)設置為有效,接着將新的配置作為參數傳遞給函數tcsetattr(3)。常量TCSANOW標志所有改變必須立刻生效而不用等到數據傳輸結束。其他另一些常數可以保證等待數據結束或者刷新輸入輸出之后再生效。
tcsetattr常量
常量 | 描述 |
TCSANOW | Make changes now without waiting for data to complete |
TCSADRAIN | Wait until everything has been transmitted |
TCSAFLUSH | Flush input and output buffers and make the change |
不同的系統上可能支持不同的輸入輸出速度,所以,通過串口連接兩台機器或者設備的時候,應該將波特率設置成兩者中較小的那個,即MIN(speed1, speed2)。
設置字符大小 †
設置字符大小的時候,這里卻沒有像設置波特率那么方便的函數。所以,程序中需要一些位掩碼運算來把事情搞定。字符大小以比特為單位指定:
options.c_flag &= ~CSIZE; /* Mask the character size bits */ options.c_flag |= CS8; /* Select 8 data bits */
設置奇偶校驗 †
與設置字符大小的方式差不多,這里仍然需要組合一些位掩碼來將奇偶校驗設為有效和奇偶校驗的類型。UNIX串口驅動可以生成even,odd和no parity位碼。設置space奇偶校驗需要耍點小手段。
- No parity (8N1)
options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8;
- Even parity (7E1)
options.c_cflag |= PARENB options.c_cflag &= ~PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
- Odd parity (7O1)
options.c_cflag |= PARENB options.c_cflag |= PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
- Space parity is setup the same as no parity (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成員來設置經典輸入和原始輸入模式。
成員變量c_lflag可以使用的常量
ISIG | Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals |
ICANON | Enable canonical input (else raw) |
XCASE | Map uppercase \lowercase (obsolete) |
ECHO | Enable echoing of input characters |
ECHOE | Echo erase character as BS-SP-BS |
ECHOK | Echo NL after kill character |
ECHONL | Echo NL |
NOFLSH | Disable flushing of input buffers after interrupt or quit characters |
IEXTEN | Enable extended functions |
ECHOCTL | Echo control characters as ^char and delete as ~? |
ECHOPRT | Echo erased character as character erased |
ECHOKE | BS-SP-BS entire line on line kill |
FLUSHO | Output being flushed |
PENDIN | Retype pending input at next read or input char |
TOSTOP | Send SIGTTOU for background output |
選擇經典輸入 †
經典輸入是以面向行設計的。在經典輸入模式中輸入字符會被放入一個緩沖之中,這樣可以以與用戶交互的方式編輯緩沖的內容,直到收到CR(carriage return)或者LF(line feed)字符。
選擇使用經典輸入模式的時候,你通常需要選擇ICANON,ECHO和ECHOE選項:
options.c_lflag |= (ICANON | ECHO | ECHOE);
選擇原始輸入 †
原始輸入根本不會被處理。輸入字符只是被原封不動的接收。一般情況中,如果要使用原始輸入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG選項:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
輸入選項 †
可以通過輸入模式成員c_iflag來控制從端口上收到的字符的輸入過程。與c_cflag一樣,c_iflag的最終值是想要使用的所有狀態的位運算OR的組合。
c_iflag成員可以使用的常量
常量 | 描述 |
INPCK | Enable parity check |
IGNPAR | Ignore parity errors |
PARMRK | Mark parity errors |
ISTRIP | Strip parity bits |
IXON | Enable software flow control (outgoing) |
IXOFF | Enable software flow control (incoming) |
IXANY | Allow any character to start flow again |
IGNBRK | Ignore break condition |
BRKINT | Send a SIGINT when a break condition is detected |
INLCR | Map NL to CR |
IGNCR | Ignore CR |
ICRNL | Map CR to NL |
IUCLC | Map uppercase to lowercase |
IMAXBEL | Echo BEL on input line too long |
設置輸入奇偶校驗選項 †
當程序在c_cflag中設置了奇偶校驗成員(PARENB)的時候,程序就需要將輸入奇偶校驗設置成為有效。與奇偶校驗相關的常量有INPCK,IGNPAR,PARMRK和ISTRIP。一般情況下,你可能需要選擇INPCK和ISTRIP將奇偶校驗設置為有效同時從接收字串中脫去奇偶校驗位:
options.c_iflag |= (INPCK | ISTRIP);
IGNPAR是一個比較危險選項,即便有錯誤發生時,它也會告訴串口驅動直接忽略奇偶校驗錯誤給數據放行。這個選項在測試鏈接的通訊質量時比較有用而通常不會被用在實際程序中。
PARMRK會導致奇偶校驗錯誤被標志成特殊字符加入到輸入流之中。如果IGNPAR選項也是有效的,那么一個NUL(八進制000)字符會被加入到發生奇偶校驗錯誤的字符前面。否則,DEL(八進制177)和NUL字符會和出錯的字符一起送出。
設置軟件流控制 †
軟件流控制可以通過IXON,IXOFF和IXANY常量設置成有效:
options.c_iflag |= (IXON | IXOFF | IXANY);
將其設置為無效的時候,很簡單,只需要對這些位取反:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
XON(start data)和XOFF(stop data)字符卻是在c_cc數組中定義的,下面會詳細描述這個數組。
輸出選項 †
成員變量c_oflag之中包括了輸出過濾選項。和輸入模式相似,程序可以選擇使用經過加工的或者原始的數據輸出。
c_oflag成員的常量
常量 | 描述 |
OPOST | Postprocess output (not set = raw output) |
OLCUC | Map lowercase to uppercase |
ONLCR | Map NL to CR-NL |
OCRNL | Map CR to NL |
NOCR | No CR output at column 0 |
ONLRET | NL performs CR function |
OFILL | Use fill characters for delay |
OFDEL | Fill character is DEL |
NLDLY | Mask for delay time needed between lines |
NL0 | No delay for NLs |
NL1 | Delay further output after newline for 100 milliseconds |
CRDLY | Mask for delay time needed to return carriage to left column |
CR0 | No delay for CRs |
CR1 | Delay after CRs depending on current column position |
CR2 | Delay 100 milliseconds after sending CRs |
CR3 | Delay 150 milliseconds after sending CRs |
TABDLY | Mask for delay time needed after TABs |
TAB0 | No delay for TABs |
TAB1 | Delay after TABs depending on current column position |
TAB2 | Delay 100 milliseconds after sending TABs |
TAB3 | Expand TAB characters to spaces |
BSDLY | Mask for delay time needed after BSs |
BS0 | No delay for BSs |
BS1 | Delay 50 milliseconds after sending BSs |
VTDLY | Mask for delay time needed after VTs |
VT0 | No delay for VTs |
VT1 | Delay 2 seconds after sending VTs |
FFDLY | Mask for delay time needed after FFs |
FF0 | No delay for FFs |
FF1 | Delay 2 seconds after sending FFs |
選擇加工過的輸出 †
通過在c_oflag成員變量中設置OPOST選項的方法程序可以選擇加工過的輸入。
options.c_oflag |= OPOST;
在所有選項當中,你可能只需要使用ONLCR選項來將行分隔符映射到CR-LF組合對上。其他選項主要是歷史遺留,僅僅與行打印機和終端跟不上串行數據的年代有關。
選擇原始輸出 †
原始輸出方式可以通過在c_oflag中重置OPOST選項來選擇:
options.c_oflag &= ~OPOST;
如果OPOST選項被設置成無效的話,其他c_oflag中的選項都會失效。
控制字符 †
字符數組c_cc里面包括了控制字符的定義和超時參數。這個數組的每個元素都是以常量定義的。
成員變量c_cc中的控制字符
常量 | 描述 | 鍵 |
VINTR | Interrupt | CTRL-C |
VQUIT | Quit | CTRL-Z |
VERASE | Erase | Backspace (BS) |
VKILL | Kill-line | CTRL-U |
VEOF | End-of-file | CTRL-D |
VEOL | End-of-line | Carriage return (CR) |
VEOL2 | Second end-of-line | Line feed (LF) |
VMIN | Minimum number of characters to read | - |
VSTART | Start flow | CTRL-Q (XON) |
VSTOP | Stop flow | CTRL-S (XOFF) |
VTIME | Time to wait for data (tenths of seconds) | - |
設置軟件流控制字符 †
用來做軟件流控制的字符包含在數組c_cc的VSTART和VSTOP元素里面。通常情況下,它們應該被設置成DC1(八進制021)和DC3(八進制023),它們在ASCII標准中代表着XON和XOFF字符。
設置讀取超時 †
UNIX串口驅動提供了設置字符和包超時的能力。數組c_cc中有兩個元素可以用來設置超時:VMIN和VTIME。在經典輸入模式或者通過open(2)和fcntl(2)函數傳遞NDELAY選項時,超時設置會被忽略。
VMIN可以指定讀取的最小字符數。如果它被設置為0,那么VTIME值則會指定每個字符讀取的等待時間。
如果VMIN不為零,VTIME會指定等待第一個字符讀取操作的時間。如果在這個指定時間中可以開始讀取某個字符,直到VMIN個數的所有字符全部被讀取,其他讀取操作將會被阻塞(等待)。也就是說,一旦讀取第一個字符,串口驅動的預期就是接收到整個字符包(一共VMIN字節)。如果在允許的時間內沒有字符被讀取,那么read(2)調用就會返回0。通過這個方法可以確切得告訴串口驅動程序需要讀取N個字節,而且read(2)調用只會返回N或者0。然而,超時設置只對第一個字符的讀取操作有效,所以,如果因為某些原因驅動程序在N字節的包中丟失某個字符的話,read(2)調用將會一直等下去。
VTIME可以以十分之一秒為單位指定等待字符輸入的時間。如果VTIME設置為0(默認情況),除非open(2)或者fcntl(2)函數設置了NDELAY選項,否則read(2)將會永久得阻塞(等待)。
調制解調器通訊 †
說到串口通訊就不得不提一下通過調劑解調器通訊的方式。這里給出的程序例子都適用於支持“事實上的”標准AT命令集的調制解調器。
什么是調制解調器 †
調制解調器是一種可以將數字信號的串行數據轉化為模擬信號頻率的設備。通過這種轉換,信息就可以通過像電話線或者有線電視線纜那樣的模擬數據鏈路來傳輸了。口語中,經常將調制解調器稱作“貓”。標准的電話調制解調器可以將串行數據轉化為能夠通過電話線傳輸的音頻;因為這種轉化非常之快又非常復雜,所以如果你去聽一下的話,這些音頻很像是大聲尖叫時發出來的聲音。
今天可以見到的調制解調器可以通過電話線每秒傳輸53000比特——5.3Kbps——的數據。還有就是,大多數調制解調器都使用數據壓縮技術,這樣就可以將某些類型數據的傳輸比特率提高到100kbps。
與調制解調器通訊 †
於調制解調器通訊的第一步就是要以原始輸入模式打開和配置串口。
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);
接下來就需要和調制解調器建立通訊連接。最好的辦法就是給調制解調器發送“AT”命令。這也會讓比較只能的調制解調器探測到你正在使用的波特率。如果正確地連接到調制解調器上,並且調制解調器開啟電源,它會返回一個回應信號“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); }
標准調制解調器命令 †
大多數調制解調器都支持“AT”命令集。之所以這樣叫是因為這個命令集中的每個命令都是以“AT”字符開頭。每個命令都是以第一列的AT開頭字符后面跟上特殊命令參數和一個回車符CR(八進制015)。調制解調器處理完這條命令之后會根據命令回復一些文本消息。
- ATD 撥號 [#b39592a6]
通過ATD命令可以撥打一個指定號碼。除過號碼和分隔符(-)以外,你還可以指定以音頻("T")或者脈沖("P")方式撥號,暫停一秒(",")和等待撥號音("W"):
ATDT 555-1212 ATDT 18008008008W1234,1,1234 ATD T555-1212WP1234
調制解調器可能回復下面列出的某個消息:
NO DIALTONE BUSY NO CARRIER CONNECT CONNECT baud
- ATH 掛斷
通過ATH命令可以讓調制解調器掛斷。因為,調制解調器如果在“命令”模式的話,你可能就不能打普通電話了。
如果DTR信號線掉了的話,大部分調制解調器也會掛斷。你可以將波特率設置成0並且持續至少1秒來做到這一點。再次讓DTR掉落同樣也可以把調制解調器重新拉回命令模式。
調制解調器成功掛斷以后,它會回復一個"NO CARRIER"回來。如果調制解調器仍然保持連接,它則會發送"CONNECT"或者"CONNECT baud"這樣的消息。
- ATZ 重置調制解調器
通過ATZ命令可以重置調制解調器。重置之后它會回復字符串"OK"。
- 與調制解調器通訊的常見問題
首先,也是最重要的一點,千萬不要使用回聲輸入(input echoing)。回聲輸入會導致調制解調器和計算機之間產生反饋循環。
其次,當發送調制解調器命令時,命令必須以回車(CR)而不是換行(NL)結束。C語言中回車的字符常量是"\r"。
最后,處理調制解調器通訊的時候,要一定保證你使用了調制解調器支持的波特率。雖然大多數調制解調器都支持自動探測波特率,但你也會注意到某些(通常是19.2kbps或者比較老的調制解調器)有局限性。
高級串口編程 †
所謂高級串口編程其實說的就是使用更直接的底層的ioctl(2)和select(2)系統調用來操作串口。
串口的ioctl †
前文中曾經提到使用tcgetattr和tcsetattr函數來配置串口。UNIX環境下,這些函數都是使用ioctl(2)系統調用來實現的。
系統調用ioctl可以帶三個參數:
int ioctl(int fd, int request, ...);
顯然,fd參數對於串口編程來說就是串口設備文件的文件描述符咯。而request參數是在<termios.h>頭文件中定義的常量,而且一般不會超出下表所列的范圍。
串口的IOCTL請求
REQUEST | 描述 | POSIX函數 |
TCGETS | Gets the current serial port settings. | tcgetattr |
TCSETS | Sets the serial port settings immediately. | tcsetattr(fd, TCSANOW, &options) |
TCSETSF | Sets the serial port settings after flushing the input and output buffers. | tcsetattr(fd, TCSAFLUSH, &options) |
TCSETSW | Sets the serial port settings after allowing the input and output buffers to drain/empty. | tcsetattr(fd, TCSADRAIN, &options) |
TCSBRK | Sends a break for the given time. | tcsendbreak, tcdrain |
TCXONC | Controls software flow control. | tcflow |
TCFLSH | Flushes the input and/or output queue. | tcflush |
TIOCMGET | Returns the state of the "MODEM" bits. | None |
TIOCMSET | Sets the state of the "MODEM" bits. | None |
FIONREAD | Returns the number of bytes in the input buffer. | None |
取得控制信號 †
TIOCMGET ioctl可以取得當前調制解調器的狀態位。這個狀態位囊括了除去RXD和TXD信號線的所有RS-232信號,這些都在下表中列出。
控制信號常量
常量 | 描述 |
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) |
例如下面這個程序片段,你可以通過給ioctl帶一個用來保存狀態位的整形變量的指針來取得狀態位。
#include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status);
設置控制信號 †
TIOCMSET ioctl可以設置上面定義的調制解調器狀態位。下面的例子展示如何使用它來將DTR信號線設成掉線狀態。
#include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &status);
可能被設置的狀態位取決於操作系統,驅動和正在使用的模式。關於更詳細的信息應該去看以下你所使用的操作系統的文檔。