[轉]linux串口資料

NAME

termios, tcgetattr, tcsetattr, tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed - 獲取和設置終端屬性,行控制,獲取和設置波特率

SYNOPSIS 總覽

#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);

DESCRIPTION 描述

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
回車延時掩碼。取值爲 CR0CR1CR2, 或 CR3
TABDLY
水平跳格延時掩碼。取值爲 TAB0TAB1TAB2TAB3 (或 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
字符長度掩碼。取值爲 CS5CS6CS7, 或 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,實際輸入波特率將等於輸出波特率。

RETURN VALUE 返回值

cfgetispeed() 返回 termios 結構中存儲的輸入波特率。

cfgetospeed() 返回 termios 結構中存儲的輸出波特率。

其他函數返回:

0
成功
-1
失敗,並且爲 errno 置值來指示錯誤。

注意 tcsetattr() 返回成功,如果任何所要求的修改可以實現的話。因此,當進行多重修改時,應當在這個函數之後再次調用 tcgetattr() 來檢測是否所有修改都成功實現。

NOTES 注意

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 個字元, 就跟標準輸入程序的最大字串長度相同 (<linux/limits.h> 或 <posix1_lim.h>).

參考程序碼中的註解它會解釋不同輸入模式的使用. 我希望這些程序碼都能被瞭解. 標準輸入程序的程序範例的註解寫得最好, 其它的範例都只在不同於其它範例的地方做註解.

敘述不是很完整, 但可以激勵你對這範例做實驗, 以延生出合於你所需應用程序的最佳解.

別忘記要把序列埠的權限設定正確 (也就是: chmod a+rw /dev/ttyS1)!

 

 

3.1 標準輸入程序

 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

/* 鮑率設定被定義在 <asm/termbits.h>, 這在 <termios.h> 被引入 */
#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);
}

 

3.2 非標準輸入程序

在非標準的輸入程序模式下, 輸入的資料不會被組合成一行而輸入後的處理功能 (清除, 殺掉, 刪除, 等等.) 都不能使用. 這個模式有兩個功能控制參數: 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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#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);
}

 

3.3 非同步式輸入

 

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

#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;
}

 

3.4 等待來自多個信號來源的輸入

這一段很短. 它只能被拿來當成寫程序時的提示, 故範例程序也很簡短. 但這個範例不只能用在序列埠上, 還可以用在被當成文檔來使用的裝置上.

select 呼叫及伴隨它所引發的巨集共用 fd_setfd_set 則是一個位元陣列, 而其中每一個位元代表一個有效的文檔敘述結構. select 呼叫接受一個有效的文檔敘述結構並傳回 fd_set 位元陣列, 而該位元陣列中若有某一個位元爲 1, 就表示相對映的文檔敘述結構的文檔發生了輸入, 輸出或有例外事件. 而這些巨集提供了所有處理 fd_set 的功能. 亦可參考手冊 select(2).

 

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h> // 文件控制定義
#include <errno.h>
#include <termios.h> //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);

 

文章出處:http://blogold.chinaunix.net/u3/103428/showart_2087259.html

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