Linux串口編程詳解


串口本身,標準和硬件 

串口是計算機上的串行通訊的物理接口。計算機歷史上,串口曾經被廣泛用於連接計算機和終端設備和各種外部設備。雖然以太網接口和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可以取得當前調制解調器的狀態位。這個狀態位囊括了除去RXDTXD信號線的所有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);

可能被設置的狀態位取決於操作系統,驅動和正在使用的模式。關於更詳細的信息應該去看以下你所使用的操作系統的文檔。

發佈了53 篇原創文章 · 獲贊 17 · 訪問量 48萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章