串行口是計算機一種經常使用的接口,具備鏈接線少,通信簡單,獲得普遍的使用。經常使用的串口是RS-232-C接口(又稱EIA RS-232-C)它是在1970年由美國電子工業協會(EIA)聯合貝爾系統、調制解調器廠家及計算機終端生產廠家共同制定的用於串行通信的標準。串口 通信指的是計算機依次以位(bit)爲單位來傳送數據,串行通信使用的範圍很廣,在嵌入式系統開發過程當中串口通信也常常用到通信方式之一。 html
Linux對全部設備的訪問是經過設備文件來進行的,串口也是這樣,爲了訪問串口,只需打開其設備文件便可操做串口設備。在linux系統下面,每 一個串口設備都有設備文件與其關聯,設備文件位於系統的/dev目錄下面。如linux下的/ttyS0,/ttyS1分別表示的是串口1和串口2。下面 來詳細介紹linux下是如何使用串口的: linux
1. 串口操做須要用到的頭文件 ios
#include /*標準輸入輸出定義*/ shell
#include /*標準函數庫定義*/ 數組
#include /*Unix 標準函數定義*/ bash
#include 數據結構
#include 異步
#include /*文件控制定義*/ 函數
#include /*POSIX 終端控制定義*/ oop
#include /*錯誤號定義*/
#include /*字符串功能函數*/
2. 串口通信波特率設置
波特率的設置定義在,其包含在頭文件裏。
經常使用的波特率常數以下:
B0-------à0 B1800-------à1800
B50-----à50 B2400------à2400
B75-----à75 B4800------à4800
B110----à110 B9600------à9600
B134----à134.5 B19200-----à19200
B200----à200 B38400------à38400
B300----à300 B57600------à57600
B600----à600 B76800------à76800
B1200---à1200 B115200-----à115200
假定程序中想要設置通信的波特率,使用cfsetispeed( )和cfsetospeed( )函數來操做,獲取波特率信息是經過cfgetispeed()和cfgetospeed()函數來完成的。好比能夠這樣來指定串口通信的波特率:
#include //頭文件定義
........
........
.......
struct termios opt; /*定義指向termios 結構類型的指針opt*/
/***************如下設置通信波特率****************/
cfsetispeed(&opt,B9600 ); /*指定輸入波特率,9600bps*/
cfsetospeed(&opt,B9600);/*指定輸出波特率,9600bps*/
/************************************************/
.........
..........
通常來講,輸入、輸出的波特率應該是一致的。
3. 串口屬性配置
在程序中,很容易配置串口的屬性,這些屬性定義在結構體struct termios中。爲在程序中使用該結構體,須要包含文件,該頭文件定義告終構體struct termios。該結構體定義以下:
#define NCCS 19
struct termios {
tcflag_t c_iflag; /* 輸入參數 */
tcflag_t c_oflag; /* 輸出參數 */
tcflag_t c_cflag; /* 控制參數*/
tcflag_t c_ispeed; /* 輸入波特率 */
tcflag_t c_ospeed; /* 輸出波特率 */
cc_t c_line; /* 線控制 */
cc_t c_cc[NCCS]; /* 控制字符*/
};
其中成員c_line在POSIX(Portable Operating System Interface for UNIX)系統中不使用。對於支持POSIX終端接口的系統中,對於端口屬性的設置和獲取要用到兩個重要的函數是:
(1).int tcsetattr(int fd,int opt_DE,*ptr)
該函數用來設置終端控制屬性,其參數說明以下:
l fd:待操做的文件描述符
l opt_DE:選項值,有三個選項以供選擇:
TCSANOW: 不等數據傳輸完畢就當即改變屬性
TCSADRAIN:等待全部數據傳輸結束才改變屬性
TCSAFLUSH:清空輸入輸出緩衝區才改變屬性
l *ptr:指向termios結構的指針
函數返回值:成功返回0,失敗返回-1。
(2).int tcgetattr(int fd,*ptr)
該函數用來獲取終端控制屬性,它把串口的默認設置賦給了termios數據數據結構,其參數說明以下:
l fd:待操做的文件描述符
l *ptr:指向termios結構的指針
函數返回值:成功返回0,失敗返回-1。
4. 打開串口
在前面已經提到linux下的串口訪問是以設備文件形式進行的,因此打開串口也便是打開文件的操做。函數原型能夠以下所示:
int open(「DE_name」,int open_Status)
參數說明:
(1).DE_name:要打開的設備文件名
好比要打開串口1,即爲/dev/ttyS0。
(2).open_Status:文件打開方式,可採用下面的文件打開模式:
l O_RDONLY:以只讀方式打開文件
l O_WRONLY:以只寫方式打開文件
l O_RDWR:以讀寫方式打開文件
l O_APPEND:寫入數據時添加到文件末尾
l O_CREATE:若是文件不存在則產生該文件,使用該標誌須要設置訪問權限位mode_t
l O_EXCL:指定該標誌,而且指定了O_CREATE標誌,若是打開的文件存在則會產生一個錯誤
l O_TRUNC:若是文件存在而且成功以寫或者只寫方式打開,則清除文件全部內容,使得文件長度變爲0
l O_NOCTTY:若是打開的是一個終端設備,這個程序不會成爲對應這個端口的控制終端,若是沒有該標誌,任何一個輸入,例如鍵盤停止信號等,都將影響進程。
l O_NONBLOCK:該標誌與早期使用的O_NDELAY標誌做用差很少。程序不關心DCD信號線的狀態,若是指定該標誌,進程將一直在休眠狀態,直到DCD信號線爲0。
函數返回值:
成功返回文件描述符,若是失敗返回-1
例如假定以可讀寫方式打開/dev/ttyS0設備,就能夠這樣操做:
#include //頭文件包含
......
......
int fd; /* 文件描述符 */
fd = open("/dev/ttyS0", O_RDWR | 0_NOCTTY); /*以讀寫方式打開設備*/
if(fd == -1)
perror("Can not open Serial_Port 1/n!");/*打開失敗時的錯誤提示*/
........
........
5. 串口讀操做(接收端)
用open函數打開設備文件,函數返回一個文件描述符(file descriptors,fd),經過文件描述符來訪問文件。讀串口操做是經過read函數來完成的。函數原型以下:
int read(int fd, *buffer,length);
參數說明:
(1).int fd:文件描述符
(2).*buffer:數據緩衝區
(3).length:要讀取的字節數
函數返回值:
讀操做成功讀取返回讀取的字節數,失敗則返回-1。
6. 串口寫操做(發送端)
寫串口操做是經過write函數來完成的。函數原型以下:
write(int fd, *buffer,length);
參數說明:
(1).fd:文件描述符
(2).*buffer:存儲寫入數據的數據緩衝區
(3).length:寫入緩衝去的數據字節數
函數返回值:
成功返回寫入數據的字節數,該值一般等於length,若是寫入失敗返回-1。
例如:向終端設備發送初始化命令
#include //頭文件包含
......
......
int n
sbuf[]={Hello,this is a Serial_Port test!/n };//待發送數據
int len_send="sizeof"(sbuf);//發送緩衝區字節數定義
n = write(fd,sbuf,len_send); //寫緩衝區
if(n == -1)
{
printf("Wirte sbuf error./n");
}
......
......
7. 關閉串口
對設備文件的操做與對普通文件的操做同樣,打開操做以後還須要關閉,關閉串口用函數close( )來操做,函數原型爲:
int close(int fd);
參數說明:
fd:文件描述符
函數返回值:
成功返回0,失敗返回-1。
termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed - 獲取和設置終端屬性,行控制,獲取和設置波特率
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
int cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(struct termios *termios_p);
speed_t cfgetospeed(struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
termios 函數族提供了一個常規的終端接口,用於控制非同步通訊端口。
這裏描述的大部分屬性有一個 termios_p 類型的參數,它是指向一個 termios 結構的指針。這個結構包含了至少下列成員:
tcflag_t c_iflag; /* 輸入模式 */ tcflag_t c_oflag; /* 輸出模式 */ tcflag_t c_cflag; /* 控制模式 */ tcflag_t c_lflag; /* 本地模式 */ cc_t c_cc[NCCS]; /* 控制字符 */
c_iflag 標誌常量:
IGNBRK忽略輸入中的 BREAK 狀態。 BRKINT若是設置了 IGNBRK,將忽略 BREAK。若是沒有設置,可是設置了 BRKINT,那麼 BREAK 將使得輸入和輸出隊列被刷新,若是終端是一個前臺進程組的控制終端,這個進程組中全部進程將收到 SIGINT 信號。若是既未設置 IGNBRK 也未設置 BRKINT,BREAK 將視爲與 NUL 字符同義,除非設置了 PARMRK,這種狀況下它被視爲序列 /377 /0 /0。 IGNPAR忽略楨錯誤和奇偶校驗錯。 PARMRK若是沒有設置 IGNPAR,在有奇偶校驗錯或楨錯誤的字符前插入 /377 /0。若是既沒有設置 IGNPAR 也沒有設置 PARMRK,將有奇偶校驗錯或楨錯誤的字符視爲 /0。 INPCK啓用輸入奇偶檢測。 ISTRIP去掉第八位。 INLCR將輸入中的 NL 翻譯爲 CR。 IGNCR忽略輸入中的回車。 ICRNL將輸入中的回車翻譯爲新行 (除非設置了 IGNCR)。 IUCLC(不屬於 POSIX) 將輸入中的大寫字母映射爲小寫字母。 IXON啓用輸出的 XON/XOFF 流控制。 IXANY(不屬於 POSIX.1;XSI) 容許任何字符來從新開始輸出。(?) IXOFF啓用輸入的 XON/XOFF 流控制。 IMAXBEL(不屬於 POSIX) 當輸入隊列滿時響零。Linux 沒有實現這一位,老是將它視爲已設置。POSIX.1 中定義的 c_oflag 標誌常量:
OPOST啓用具體實現自行定義的輸出處理。其他 c_oflag 標誌常量定義在 POSIX 1003.1-2001 中,除非另外說明。
OLCUC(不屬於 POSIX) 將輸出中的小寫字母映射爲大寫字母。 ONLCR(XSI) 將輸出中的新行符映射爲回車-換行。 OCRNL將輸出中的回車映射爲新行符 ONOCR不在第 0 列輸出回車。 ONLRET不輸出回車。 OFILL發送填充字符做爲延時,而不是使用定時來延時。 OFDEL(不屬於 POSIX) 填充字符是 ASCII DEL (0177)。若是不設置,填充字符則是 ASCII NUL。 NLDLY新行延時掩碼。取值爲 NL0 和 NL1。 CRDLY回車延時掩碼。取值爲 CR0, CR1, CR2, 或 CR3。 TABDLY水平跳格延時掩碼。取值爲 TAB0, TAB1, TAB2, TAB3 (或 XTABS)。取值爲 TAB3,即 XTABS,將擴展跳格爲空格 (每一個跳格符填充 8 個空格)。(?) BSDLY回退延時掩碼。取值爲 BS0 或 BS1。(歷來沒有被實現過) VTDLY豎直跳格延時掩碼。取值爲 VT0 或 VT1。 FFDLY進表延時掩碼。取值爲 FF0 或 FF1。c_cflag 標誌常量:
CBAUD(不屬於 POSIX) 波特率掩碼 (4+1 位)。 CBAUDEX(不屬於 POSIX) 擴展的波特率掩碼 (1 位),包含在 CBAUD 中。(POSIX 規定波特率存儲在 termios 結構中,並未精確指定它的位置,而是提供了函數 cfgetispeed() 和 cfsetispeed() 來存取它。一些系統使用 c_cflag 中 CBAUD 選擇的位,其餘系統使用單獨的變量,例如 sg_ispeed 和 sg_ospeed 。)
CSIZE字符長度掩碼。取值爲 CS5, CS6, CS7, 或 CS8。 CSTOPB設置兩個中止位,而不是一個。 CREAD打開接受者。 PARENB容許輸出產生奇偶信息以及輸入的奇偶校驗。 PARODD輸入和輸出是奇校驗。 HUPCL在最後一個進程關閉設備後,下降 modem 控制線 (掛斷)。(?) CLOCAL忽略 modem 控制線。 LOBLK(不屬於 POSIX) 從非當前 shell 層阻塞輸出(用於 shl )。(?) CIBAUD(不屬於 POSIX) 輸入速度的掩碼。CIBAUD 各位的值與 CBAUD 各位相同,左移了 IBSHIFT 位。 CRTSCTS(不屬於 POSIX) 啓用 RTS/CTS (硬件) 流控制。c_lflag 標誌常量:
ISIG當接受到字符 INTR, QUIT, SUSP, 或 DSUSP 時,產生相應的信號。 ICANON啓用標準模式 (canonical mode)。容許使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的緩衝。 XCASE(不屬於 POSIX; Linux 下不被支持) 若是同時設置了 ICANON,終端只有大寫。輸入被轉換爲小寫,除了以 / 前綴的字符。輸出時,大寫字符被前綴 /,小寫字符被轉換成大寫。 ECHO回顯輸入字符。 ECHOE若是同時設置了 ICANON,字符 ERASE 擦除前一個輸入字符,WERASE 擦除前一個詞。 ECHOK若是同時設置了 ICANON,字符 KILL 刪除當前行。 ECHONL若是同時設置了 ICANON,回顯字符 NL,即便沒有設置 ECHO。 ECHOCTL(不屬於 POSIX) 若是同時設置了 ECHO,除了 TAB, NL, START, 和 STOP 以外的 ASCII 控制信號被回顯爲 ^X, 這裏 X 是比控制信號大 0x40 的 ASCII 碼。例如,字符 0x08 (BS) 被回顯爲 ^H。 ECHOPRT(不屬於 POSIX) 若是同時設置了 ICANON 和 IECHO,字符在刪除的同時被打印。 ECHOKE(不屬於 POSIX) 若是同時設置了 ICANON,回顯 KILL 時將刪除一行中的每一個字符,如同指定了 ECHOE 和 ECHOPRT 同樣。 DEFECHO(不屬於 POSIX) 只在一個進程讀的時候回顯。 FLUSHO(不屬於 POSIX; Linux 下不被支持) 輸出被刷新。這個標誌能夠經過鍵入字符 DISCARD 來開關。 NOFLSH禁止在產生 SIGINT, SIGQUIT 和 SIGSUSP 信號時刷新輸入和輸出隊列。 TOSTOP向試圖寫控制終端的後臺進程組發送 SIGTTOU 信號。 PENDIN(不屬於 POSIX; Linux 下不被支持) 在讀入下一個字符時,輸入隊列中全部字符被從新輸出。( bash 用它來處理 typeahead) IEXTEN啓用實現自定義的輸入處理。這個標誌必須與 ICANON 同時使用,才能解釋特殊字符 EOL2,LNEXT,REPRINT 和 WERASE, IUCLC 標誌纔有效。c_cc 數組定義了特殊的控制字符。符號下標 (初始值) 和意義爲:
VINTR(003, ETX, Ctrl-C, or also 0177, DEL, rubout) 中斷字符。發出 SIGINT 信號。當設置 ISIG 時可被識別,再也不做爲輸入傳遞。 VQUIT(034, FS, Ctrl-/) 退出字符。發出 SIGQUIT 信號。當設置 ISIG 時可被識別,再也不做爲輸入傳遞。 VERASE(0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) 刪除字符。刪除上一個尚未刪掉的字符,但不刪除上一個 EOF 或行首。當設置 ICANON 時可被識別,再也不做爲輸入傳遞。 VKILL(025, NAK, Ctrl-U, or Ctrl-X, or also @) 終止字符。刪除自上一個 EOF 或行首以來的輸入。當設置 ICANON 時可被識別,再也不做爲輸入傳遞。 VEOF(004, EOT, Ctrl-D) 文件尾字符。更精確地說,這個字符使得 tty 緩衝中的內容被送到等待輸入的用戶程序中,而沒必要等到 EOL。若是它是一行的第一個字符,那麼用戶程序的 read() 將返回 0,指示讀到了 EOF。當設置 ICANON 時可被識別,再也不做爲輸入傳遞。 VMIN非 canonical 模式讀的最小字符數。 VEOL(0, NUL) 附加的行尾字符。當設置 ICANON 時可被識別。 VTIME非 canonical 模式讀時的延時,以十分之一秒爲單位。 VEOL2(not in POSIX; 0, NUL) 另外一個行尾字符。當設置 ICANON 時可被識別。 VSWTCH(not in POSIX; not supported under Linux; 0, NUL) 開關字符。(只爲 shl 所用。) VSTART(021, DC1, Ctrl-Q) 開始字符。從新開始被 Stop 字符停止的輸出。當設置 IXON 時可被識別,再也不做爲輸入傳遞。 VSTOP(023, DC3, Ctrl-S) 中止字符。中止輸出,直到鍵入 Start 字符。當設置 IXON 時可被識別,再也不做爲輸入傳遞。 VSUSP(032, SUB, Ctrl-Z) 掛起字符。發送 SIGTSTP 信號。當設置 ISIG 時可被識別,再也不做爲輸入傳遞。 VDSUSP(not in POSIX; not supported under Linux; 031, EM, Ctrl-Y) 延時掛起信號。當用戶程序讀到這個字符時,發送 SIGTSTP 信號。當設置 IEXTEN 和 ISIG,而且系統支持做業管理時可被識別,再也不做爲輸入傳遞。 VLNEXT(not in POSIX; 026, SYN, Ctrl-V) 字面上的下一個。引用下一個輸入字符,取消它的任何特殊含義。當設置 IEXTEN 時可被識別,再也不做爲輸入傳遞。 VWERASE(not in POSIX; 027, ETB, Ctrl-W) 刪除詞。當設置 ICANON 和 IEXTEN 時可被識別,再也不做爲輸入傳遞。 VREPRINT(not in POSIX; 022, DC2, Ctrl-R) 從新輸出未讀的字符。當設置 ICANON 和 IEXTEN 時可被識別,再也不做爲輸入傳遞。 VDISCARD(not in POSIX; not supported under Linux; 017, SI, Ctrl-O) 開關:開始/結束丟棄未完成的輸出。當設置 IEXTEN 時可被識別,再也不做爲輸入傳遞。 VSTATUS(not in POSIX; not supported under Linux; status request: 024, DC4, Ctrl-T).這些符號下標值是互不相同的,除了 VTIME,VMIN 的值可能分別與 VEOL,VEOF 相同。 (在 non-canonical 模式下,特殊字符的含義更改成延時含義。MIN 表示應當被讀入的最小字符數。TIME 是以十分之一秒爲單位的計時器。若是同時設置了它們,read 將等待直到至少讀入一個字符,一旦讀入 MIN 個字符或者從上次讀入字符開始通過了 TIME 時間就當即返回。若是隻設置了 MIN,read 在讀入 MIN 個字符以前不會返回。若是隻設置了 TIME,read 將在至少讀入一個字符,或者計時器超時的時候當即返回。若是都沒有設置,read 將當即返回,只給出當前準備好的字符。) (?)
tcgetattr() 獲得與 fd 指向的對象相關的參數,將它們保存於 termios_p 引用的 termios 結構中。函數能夠從後臺進程中調用;可是,終端屬性可能被後來的前臺進程所改變。
tcsetattr() 設置與終端相關的參數 (除非須要底層支持卻沒法知足),使用 termios_p 引用的 termios 結構。optional_actions 指定了何時改變會起做用:
TCSANOW改變當即發生 TCSADRAIN改變在全部寫入 fd 的輸出都被傳輸後生效。這個函數應當用於修改影響輸出的參數時使用。 TCSAFLUSH改變在全部寫入 fd 引用的對象的輸出都被傳輸後生效,全部已接受但未讀入的輸入都在改變發生前丟棄。tcsendbreak() 傳送連續的 0 值比特流,持續一段時間,若是終端使用異步串行數據傳輸的話。若是 duration 是 0,它至少傳輸 0.25 秒,不會超過 0.5 秒。若是 duration 非零,它發送的時間長度由實現定義。
若是終端並不是使用異步串行數據傳輸,tcsendbreak() 什麼都不作。
tcdrain() 等待直到全部寫入 fd 引用的對象的輸出都被傳輸。
tcflush() 丟棄要寫入 引用的對象,可是還沒有傳輸的數據,或者收到可是還沒有讀取的數據,取決於 queue_selector 的值:
TCIFLUSH刷新收到的數據可是不讀 TCOFLUSH刷新寫入的數據可是不傳送 TCIOFLUSH同時刷新收到的數據可是不讀,而且刷新寫入的數據可是不傳送tcflow() 掛起 fd 引用的對象上的數據傳輸或接收,取決於 action 的值:
TCOOFF掛起輸出 TCOON從新開始被掛起的輸出 TCIOFF發送一個 STOP 字符,中止終端設備向系統傳送數據 TCION發送一個 START 字符,使終端設備向系統傳輸數據打開一個終端設備時的默認設置是輸入和輸出都沒有掛起。
波特率函數被用來獲取和設置 termios 結構中,輸入和輸出波特率的值。新值不會立刻生效,直到成功調用了 tcsetattr() 函數。
設置速度爲 B0 使得 modem "掛機"。與 B38400 相應的實際比特率能夠用 setserial(8) 調整。
輸入和輸出波特率被保存於 termios 結構中。
cfmakeraw 設置終端屬性以下:
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP |INLCR|IGNCR|ICRNL|IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); termios_p->c_cflag &= ~(CSIZE|PARENB); termios_p->c_cflag |= CS8;
cfgetospeed() 返回 termios_p 指向的 termios 結構中存儲的輸出波特率
cfsetospeed() 設置 termios_p 指向的 termios 結構中存儲的輸出波特率爲 speed。取值必須是如下常量之一:
B0 B50 B75 B110 B134 B150 B200 B300 B600 B1200 B1800 B2400 B4800 B9600 B19200 B38400 B57600 B115200 B230400
零值 B0 用來中斷鏈接。若是指定了 B0,不該當再假定存在鏈接。一般,這樣將斷開鏈接。CBAUDEX 是一個掩碼,指示高於 POSIX.1 定義的速度的那一些 (57600 及以上)。所以,B57600 & CBAUDEX 爲非零。
cfgetispeed() 返回 termios 結構中存儲的輸入波特率。
cfsetispeed() 設置 termios 結構中存儲的輸入波特率爲 speed。若是輸入波特率被設爲0,實際輸入波特率將等於輸出波特率。
cfgetispeed() 返回 termios 結構中存儲的輸入波特率。
cfgetospeed() 返回 termios 結構中存儲的輸出波特率。
其餘函數返回:
0成功-1失敗,而且爲 errno 置值來指示錯誤。注意 tcsetattr() 返回成功,若是任何所要求的修改能夠實現的話。所以,當進行多重修改時,應當在這個函數以後再次調用 tcgetattr() 來檢測是否全部修改都成功實現。
Unix V7 以及不少後來的系統有一個波特率的列表,在十四個值 B0, ..., B9600 以後能夠看到兩個常數 EXTA, EXTB ("External A" and "External B")。不少系統將這個列表擴展爲更高的波特率。
tcsendbreak 中非零的 duration 有不一樣的效果。SunOS 指定中斷 duration*N 秒,其中 N 至少爲 0.25,不高於 0.5 。Linux, AIX, DU, Tru64 發送 duration 微秒的 break 。FreeBSD, NetBSD, HP-UX 以及 MacOS 忽略 duration 的值。在 Solaris 和 Unixware 中, tcsendbreak 搭配非零的 duration 效果相似於 tcdrain。
全部的範例來源自miniterm.c. The type ahead 暫存器被限制在 255 個字元, 就跟標準輸入程序的最大字串長度相同 (或).
參考程序碼中的註解它會解釋不一樣輸入模式的使用. 我但願這些程序碼都能被瞭解. 標準輸入程序的程序範例的註解寫得最好, 其它的範例都只在不一樣於其它範例的地方作註解.
敘述不是很完整, 但能夠激勵你對這範例作實驗, 以延生出合於你所需應用程序的最佳解.
別忘記要把序列埠的權限設定正確 (也就是:chmod a+rw /dev/ttyS1)!
#include #include #include #include #include /* 鮑率設定被定義在 , 這在 被引入 */ #define BAUDRATE B38400 /* 定義正確的序列埠 */ #define MODEMDEVICE "/dev/ttyS1" #define _POSIX_SOURCE 1 /* POSIX 系統兼容 */ #define FALSE 0 #define TRUE 1 volatile int STOP=FALSE; main() { int fd,c, res; struct termios oldtio,newtio; char buf[255]; /* 開啓數據機裝置以讀取並寫入而不以控制 tty 的模式 由於咱們不想程序在送出 CTRL-C 後就被殺掉. */ fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); if (fd <0) {perror(MODEMDEVICE); exit(-1); } tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定 */ bzero(&newtio, sizeof(newtio)); /* 清除結構體以放入新的序列埠設定值 */ /* BAUDRATE: 設定 bps 的速度. 你也能夠用 cfsetispeed 及 cfsetospeed 來設定. CRTSCTS : 輸出資料的硬件流量控制 (只能在具完整線路的纜線下工做 參考 Serial-HOWTO 第七節) CS8 : 8n1 (8 位元, 不作同位元檢查,1 個終止位元) CLOCAL : 本地連線, 不具數據機控制功能 CREAD : 致能接收字元 */ newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; /* IGNPAR : 忽略經同位元檢查後, 錯誤的位元組 ICRNL : 比 CR 對應成 NL (不然當輸入信號有 CR 時不會終止輸入) 在否則把裝置設定成 raw 模式(沒有其它的輸入處理) */ newtio.c_iflag = IGNPAR | ICRNL; /* Raw 模式輸出. */ newtio.c_oflag = 0; /* ICANON : 致能標準輸入, 使全部迴應機能停用, 並不送出信號以叫用程序 */ newtio.c_lflag = ICANON; /* 初始化全部的控制特性 預設值能夠在 /usr/include/termios.h 找到, 在註解中也有, 但咱們在這不須要看它們 */ newtio.c_cc[VINTR] = 0; /* Ctrl-c */ newtio.c_cc[VQUIT] = 0; /* Ctrl-/ */ newtio.c_cc[VERASE] = 0; /* del */ newtio.c_cc[VKILL] = 0; /* @ */ newtio.c_cc[VEOF] = 4; /* Ctrl-d */ newtio.c_cc[VTIME] = 0; /* 不使用分割字元組的計時器 */ newtio.c_cc[VMIN] = 1; /* 在讀取到 1 個字元前先中止 */ newtio.c_cc[VSWTC] = 0; /* '/0' */ newtio.c_cc[VSTART] = 0; /* Ctrl-q */ newtio.c_cc[VSTOP] = 0; /* Ctrl-s */ newtio.c_cc[VSUSP] = 0; /* Ctrl-z */ newtio.c_cc[VEOL] = 0; /* '/0' */ newtio.c_cc[VREPRINT] = 0; /* Ctrl-r */ newtio.c_cc[VDISCARD] = 0; /* Ctrl-u */ newtio.c_cc[VWERASE] = 0; /* Ctrl-w */ newtio.c_cc[VLNEXT] = 0; /* Ctrl-v */ newtio.c_cc[VEOL2] = 0; /* '/0' */ /* 如今清除數據機線並啓動序列埠的設定 */ tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); /* 終端機設定完成, 如今處理輸入信號 在這個範例, 在一行的開始處輸入 'z' 會退出此程序. */ while (STOP==FALSE) { /* 迴圈會在咱們發出終止的信號後跳出 */ /* 即便輸入超過 255 個字元, 讀取的程序段仍是會一直等到行終結符出現才中止. 若是讀到的字元組低於正確存在的字元組, 則所剩的字元會在下一次讀取時取得. res 用來存放真正讀到的字元組個數 */ res = read(fd,buf,255); buf[res]=0; /* 設定字串終止字元, 因此咱們能用 printf */ printf(":%s:%d/n", buf, res); if (buf[0]=='z') STOP=TRUE; } /* 回存舊的序列埠設定值 */ tcsetattr(fd,TCSANOW,&oldtio); }
在非標準的輸入程序模式下, 輸入的資料不會被組合成一行而輸入後的處理功能 (清除, 殺掉, 刪除, 等等.) 都不能使用. 這個模式有兩個功能控制參數:c_cc[VTIME]設定字元輸入時間計時器, 及c_cc[VMIN]設定知足讀取功能的最低字元接收個數.
若是 MIN > 0 且 TIME = 0, MIN 設定爲知足讀取功能的最低字元接收個數. 因爲 TIME 是 零, 因此計時器將不被使用.
若是 MIN = 0 且 TIME > 0, TIME 將被當作逾時設定值. 知足讀取功能的狀況爲讀取到單一字元, 或者超過 TIME 所定義的時間 (t = TIME *0.1 s). 若是超過 TIME 所定義的時間, 則不會傳回任何字元.
若是 MIN > 0 且 TIME > 0, TIME 將被當作一個分割字元組的計時器. 知足讀取功能的條件爲接收到 MIN 個數的字元, 或兩個字元的間隔時間超過 TIME 所定義的值. 計時器會在每讀到一個字元后從新計時, 且只會在第一個字元收到後纔會啓動.
若是 MIN = 0 且 TIME = 0, 讀取功能就立刻被知足. 目前所存在的字元組個數, 或者 將回傳的字元組個數. 根據 Antonino (參考 貢獻) 所說, 你能夠用fcntl(fd, F_SETFL, FNDELAY);在讀取前獲得相同的結果.
藉由修改newtio.c_cc[VTIME]及newtio.c_cc[VMIN]上述的模式就能夠測試了.
#include #include #include #include #include #define BAUDRATE B38400 #define MODEMDEVICE "/dev/ttyS1" #define _POSIX_SOURCE 1 /* POSIX 系統兼容 */ #define FALSE 0 #define TRUE 1 volatile int STOP=FALSE; main() { int fd,c, res; struct termios oldtio,newtio; char buf[255]; fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); if (fd <0) {perror(MODEMDEVICE); exit(-1); } tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定 */ bzero(&newtio, sizeof(newtio)); newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; /* 設定輸入模式 (非標準型, 不迴應,...) */ newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; /* 不使用分割字元組計時器 */ newtio.c_cc[VMIN] = 5; /* 在讀取到 5 個字元前先中止 */ tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); while (STOP==FALSE) { /* 輸入迴圈 */ res = read(fd,buf,255); /* 在輸入 5 個字元后即返回 */ buf[res]=0; /* 因此咱們能用 printf... */ printf(":%s:%d/n", buf, res); if (buf[0]=='z') STOP=TRUE; } tcsetattr(fd,TCSANOW,&oldtio); }
#include #include #include #include #include #include #define BAUDRATE B38400 #define MODEMDEVICE "/dev/ttyS1" #define _POSIX_SOURCE 1 /* POSIX 系統兼容 */ #define FALSE 0 #define TRUE 1 volatile int STOP=FALSE; void signal_handler_IO (int status); /* 定義信號處理程序 */ int wait_flag=TRUE; /* 沒收到信號的話就會是 TRUE */ main() { int fd,c, res; struct termios oldtio,newtio; struct sigaction saio; /* definition of signal action */ char buf[255]; /* 開啓裝置爲 non-blocking (讀取功能會立刻結束返回) */ fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd <0) {perror(MODEMDEVICE); exit(-1); } /* 在使裝置非同步化前, 安裝信號處理程序 */ saio.sa_handler = signal_handler_IO; saio.sa_mask = 0; saio.sa_flags = 0; saio.sa_restorer = NULL; sigaction(SIGIO,&saio,NULL); /* 容許行程去接收 SIGIO 信號*/ fcntl(fd, F_SETOWN, getpid()); /* 使文檔ake the file descriptor 非同步 (使用手冊上說只有 O_APPEND 及 O_NONBLOCK, 而 F_SETFL 也能夠用...) */ fcntl(fd, F_SETFL, FASYNC); tcgetattr(fd,&oldtio); /* 儲存目前的序列埠設定值 */ /* 設定新的序列埠爲標準輸入程序 */ newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; newtio.c_iflag = IGNPAR | ICRNL; newtio.c_oflag = 0; newtio.c_lflag = ICANON; newtio.c_cc[VMIN]=1; newtio.c_cc[VTIME]=0; tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); /* 等待輸入信號的迴圈. 不少有用的事咱們將在這作 */ while (STOP==FALSE) { printf("./n");usleep(100000); /* 在收到 SIGIO 後, wait_flag = FALSE, 輸入信號存在則能夠被讀取 */ if (wait_flag==FALSE) { res = read(fd,buf,255); buf[res]=0; printf(":%s:%d/n", buf, res); if (res==1) STOP=TRUE; /* 若是隻輸入 CR 則中止迴圈 */ wait_flag = TRUE; /* 等待新的輸入信號 */ } } /* 回存舊的序列埠設定值 */ tcsetattr(fd,TCSANOW,&oldtio); } /*************************************************************************** * 信號處理程序. 設定 wait_flag 爲 FALSE, 以使上述的迴圈能接收字元 * ***************************************************************************/ void signal_handler_IO (int status) { printf("received SIGIO signal./n"); wait_flag = FALSE; }
這一段很短. 它只能被拿來當成寫程序時的提示, 故範例程序也很簡短. 但這個範例不僅能用在序列埠上, 還能夠用在被當成文檔來使用的裝置上.
select 呼叫及伴隨它所引起的巨集共用fd_set.fd_set則是一個位元陣列, 而其中每個位元表明一個有效的文檔敘述結構.select呼叫接受一個有效的文檔敘述結構並傳回fd_set位元陣列, 而該位元陣列中如有某一個位元爲 1, 就表示相對映的文檔敘述結構的文檔發生了輸入, 輸出或有例外事件. 而這些巨集提供了全部處理fd_set的功能. 亦可參考手冊 select(2).
#include #include #include main() { int fd1, fd2; /* 輸入源 1 及 2 */ fd_set readfs; /* 文檔敘述結構設定 */ int maxfd; /* 最大可用的文檔敘述結構 */ int loop=1; /* 迴圈在 TRUE 時成立 */ /* open_input_source 開啓一個裝置, 正確的設定好序列埠, 並回傳回此文檔敘述結構體 */ fd1 = open_input_source("/dev/ttyS1"); /* COM2 */ if (fd1<0) exit(0); fd2 = open_input_source("/dev/ttyS2"); /* COM3 */ if (fd2<0) exit(0); maxfd = MAX (fd1, fd2)+1; /* 測試最大位元輸入 (fd) */ /* 輸入迴圈 */ while (loop) { FD_SET(fd1, &readfs); /* 測試輸入源 1 */ FD_SET(fd2, &readfs); /* 測試輸入源 2 */ /* block until input becomes available */ select(maxfd, &readfs, NULL, NULL, NULL); if (FD_ISSET(fd1)) /* 若是輸入源 1 有信號 */ handle_input_from_source1(); if (FD_ISSET(fd2)) /* 若是輸入源 2 有信號 */ handle_input_from_source2(); } }
這個範例程序在等待輸入信號出現前, 不能肯定它會停頓下來. 若是你須要在輸入時加入逾時功能, 只需把 select 呼叫換成:
int res; struct timeval Timeout; /* 設定輸入迴圈的逾時值 */ Timeout.tv_usec = 0; /* 毫秒 */ Timeout.tv_sec = 1; /* 秒 */ res = select(maxfd, &readfs, NULL, NULL, &Timeout); if (res==0) /* 文檔敘述結構數在 input = 0 時, 會發生輸入逾時. */
這個程序會在 1 秒鐘後逾時. 若是超過期間, select 會傳回 0, 可是應該留意Timeout的時間遞減是由select所等待輸入信號的時間爲基準. 若是逾時的值是 0, select 會立刻結束返回.
Linux 環境下使用RS-232接口 RS是英文 "推薦標準"的縮寫 232爲標識號 RS-485 串口通訊表示計算機一次傳送一個位的數據, 當使用串行通訊時,每一個字的數據是一個位一個位的傳輸或接收的, 每一個位不是高電平,就是低電平. 串行通訊的速率一般是使用"位/每秒"的方式來表示的,即波特率。 全雙工--計算機能夠同時收發數據, 它有兩個獨立的數據通道,一個輸入,一個輸出, 半雙工意味着計算機不能同時收發信息, 只能有一人通道進行通訊. 流控: 一般,當數據在兩個串行接口之間進行傳輸時須要對其進行控制. 這一般依賴於串行通訊鏈接的各類規定, 對異步數據傳輸的控制有兩種方法. 一種叫:「軟件」流控 。 一種叫: 「硬件"流控 。 串口設備: 打開一個串行口 #include #include #include #include // 文件控制定義 #include #include //POSIX終端控制定義 /* * open_port() --打開串行口 * * 成功的話,返回文件描述符,錯誤則返回 -1. */ int open_port(void) { int fd; fd=open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY); if (fd == -1) { /*沒法打開串口*/ perror("open_port : Unable to open /dev/ttyS0"); } else fcntl(fd,F_SETFL,0); return (fd); } //O_NOCTTY 標誌 ,該程序不想成爲此端口的「控制終端"。 若是沒有強調這一點, //O_NDELAY標誌 , 標誌告訴Linux ,該程序並不關注DCD信叼線所處的狀態, 即無論另一端的設備是在運行仍是被掛起。若是沒有指定該標誌,那麼程序就會被設置睡 眠狀態, (2)向端口寫數據 向端口寫數據是很容易的,只要使用write()系統調用就能夠了。 例如: n=write(fd,"ATZ/r",4); if (n<0) fputs("write() of 4 bytes failed!/n",stderr); write函數返回發送數據的個數,若是出現錯誤,則返回 -1。 (3) 讀端口數據 從端口讀數據則須要些技巧。若是在原始數據的模式下對端口進行操做, read()系統調用將返回串行口輸入緩衝區中全部的字符數據,無論有多少, 若是沒有數據,那麼該調用將被阻塞.處於等待狀態,直到有字符輸入, 或者到了規定的時限和出現錯誤爲止, 經過如下方法,能使read函數當即返回。 fcntl(fd,F_SETFL,FNDELAY); FNDELAY 函數使read函數在端口沒月字符存在的狀況下,馬上返回0, 若是要恢復正常(阻塞)狀態,能夠調用fcntl()函數,不要FNDELAY參數, 以下所示: fcntl(Fd,F_SETFL,0); 在使用O_NDELAY參數打開串行口後,一樣與使用了該函數調用。 fcntl(fd,F_SETFL,0); POSIX終端接口 串口,波特率,字符大小等, POSIX函數是 tcgetattr()和tcsetattr() 獲取和設置終端的屬性, 能夠提供 structrure termios的指針