一些背景 ? ? ? ? ? ? ? ? ? ??? ? ?
在大部分講解?Linux?編程書(shū)籍的時(shí)候會(huì)發(fā)現(xiàn)沒(méi)有單獨(dú)的串口編程章節(jié),實(shí)際上串口編程已經(jīng)被概括在了“終端”或者“終端IO”章節(jié)里面。在系統(tǒng)中會(huì)經(jīng)常出現(xiàn)的幾個(gè)容易混淆的概念:tty,串口,控制臺(tái)與驅(qū)動(dòng)程序。后面會(huì)在實(shí)際使用過(guò)程中對(duì)幾種設(shè)備的原理與使用進(jìn)行詳解。
在系統(tǒng)下面通過(guò)執(zhí)行 "ls /dev" 或者 "cat /proc/tty/drivers" 可以看到經(jīng)常碰到的一些術(shù)語(yǔ)以及分類,如下所示:
對(duì)開(kāi)發(fā)者而言,比較熟悉的有?console 控制臺(tái)、tty 終端、ttyS serial串口設(shè)備、pty 偽終端等。由于 pty 成對(duì)使用,所以又細(xì)分為了主從兩類。這些設(shè)備類對(duì)應(yīng)的系統(tǒng)設(shè)備文件名參見(jiàn)第二列,可以輸入 "ls /dev" 進(jìn)行查看。
需要理清這些概念的關(guān)系就需要追溯早起計(jì)算機(jī)的使用歷史,最初計(jì)算機(jī)成本高昂,通常需要連接多套鍵盤(pán)顯示器供多人使用,因此就出現(xiàn)了這樣一種專門連接計(jì)算機(jī)的設(shè)備,它只有顯示器和鍵盤(pán),外加簡(jiǎn)單處理電路。用戶可以通過(guò)這套設(shè)備連接到計(jì)算機(jī)上(通常是通過(guò)串口連接),然后登錄系統(tǒng),并對(duì)計(jì)算機(jī)進(jìn)行操作。這樣一臺(tái)只有輸入、顯示器件并能連接到計(jì)算機(jī)的設(shè)備就稱為終端。tty 設(shè)備的名稱是從過(guò)去的電傳打字機(jī)(Teletype)縮寫(xiě)而來(lái),也是最早出現(xiàn)的一種終端設(shè)備,因此現(xiàn)在在 Linux 系統(tǒng)中,就用 tty 來(lái)表示 “終端”。而 console 控制臺(tái),pty 偽終端等可以理解為虛擬 tty。總之,在 Unix 系統(tǒng)中 tty 就可以理解為連接到系統(tǒng)的物理或者虛擬終端。
“console”控制臺(tái)用于用戶和系統(tǒng)進(jìn)行交互的設(shè)備,與終端作用類似。該虛擬 tty 與普通終端相比,多了一些功能:如顯示系統(tǒng)內(nèi)核消息,后臺(tái)服務(wù)日志等。從硬件上看,控制臺(tái)與終端等都是具備輸入顯示功能的設(shè)備,沒(méi)有區(qū)別。實(shí)際上他們表達(dá)的意思相同。控制臺(tái)與終端的區(qū)別體現(xiàn)在軟件上,在啟動(dòng) Linux 內(nèi)核前傳入的命令行參數(shù) "console=..." 就是用來(lái)指定具體的控制臺(tái)??刂婆_(tái)在 tty 驅(qū)動(dòng)初始化之前就可以使用了,最開(kāi)始被用來(lái)顯示內(nèi)核消息。我們?cè)谟?jì)算機(jī)或者嵌入式系統(tǒng)中經(jīng)常會(huì)看到 "console = ttySAC0"、"console = ttyS1" 等語(yǔ)句,實(shí)際就是選取某個(gè)虛擬或者物理終端作為控制臺(tái)與用戶交互。
當(dāng) tty 驅(qū)動(dòng)初始化結(jié)束,用戶程序就可以通過(guò) tty 驅(qū)動(dòng)的接口來(lái)操作各類終端設(shè)備,包括控制臺(tái)。而后面要介紹的應(yīng)用程序操作接口也由此而來(lái)。之前內(nèi)容講了終端,tty,控制臺(tái)等概念以及區(qū)別,因此在串口編程相關(guān)章節(jié)中提及串口,有時(shí)也會(huì)用終端,tty等來(lái)替代,注意它們實(shí)際上所指是相同的。
在?Linux?中可以通過(guò)一組函數(shù)調(diào)用(通用終端接口,簡(jiǎn)稱GTI)來(lái)控制終端,這組函數(shù)調(diào)用與用于讀寫(xiě)數(shù)據(jù)的函數(shù)是分離的,這就使得讀寫(xiě)數(shù)據(jù)的接口非常簡(jiǎn)潔,同時(shí)又允許可以對(duì)終端或串口的行為進(jìn)行更精細(xì)地控制。但由于需要支持大量不同類型的硬件,GTI 中實(shí)現(xiàn)的 IO 接口卻不簡(jiǎn)潔。
詳解 termios
termios 是在 POSIX 規(guī)范中定義的標(biāo)準(zhǔn)接口,它類似與 System V 中的 termio 接口。通過(guò)設(shè)置 termios 類型的數(shù)據(jù)結(jié)構(gòu)中的值和使用一小組函數(shù)調(diào)用,就可以對(duì)終端接口進(jìn)行控制。termios 的結(jié)構(gòu)體定義以及相關(guān)函數(shù)調(diào)用參見(jiàn) termios.h 頭文件。termios 結(jié)構(gòu)的定義如下:
如定義所示,影響終端的參數(shù)按照不同模式分成如下幾類:
????輸入模式
????輸出模式
????控制模式
????本地模式
????線路規(guī)程
????特殊控制字符
????輸入速率
????輸出速率
輸入模式
輸入模式控制輸入數(shù)據(jù)(終端驅(qū)動(dòng)程序從串行口或鍵盤(pán)接收到的字符)在被傳遞給程序之前的處理方式。通過(guò)設(shè)置 termios 結(jié)構(gòu)中 c_iflag 成員的標(biāo)志對(duì)它們進(jìn)行控制。所有的標(biāo)志都被定義為宏,這也是所有終端模式都采用的方法??捎糜?c_iflag 成員的宏如下所示:
????BRKINT:當(dāng)在輸入行中檢測(cè)到一個(gè)終止?fàn)顟B(tài)(連接丟失)時(shí),產(chǎn)生一個(gè)中斷。
????IGNBRK:忽略輸入行中的終止?fàn)顟B(tài)。
????ICRNL:將接收到的回車符轉(zhuǎn)換為新行符。????
????IGNCR:忽略接收到的回車符。
????INLCR:將接收到的新行符轉(zhuǎn)換為回車符。
????IGNPAR:忽略奇偶校驗(yàn)錯(cuò)誤的字符。????
????INPCK:對(duì)接收到的字符執(zhí)行奇偶校驗(yàn)。
????PARMRK:對(duì)奇偶校驗(yàn)錯(cuò)誤做出標(biāo)記。
????ISTRIP:將所有接收到的字符裁剪為 7 比特位。
????IXOFF:對(duì)輸入啟動(dòng)軟件流控。????
????IXON:對(duì)輸出啟動(dòng)軟件流控。
如果 BRKINT 和 IGNBRK 標(biāo)志都未被設(shè)置,則輸入行中的終止?fàn)顟B(tài)就被讀取為 NULL 字符。
輸出模式
輸出模式控制輸出字符的處理方式,即由程序發(fā)送出去的字符在傳遞到串行口或屏幕之前是如何處理的??捎糜?/span> c_oflag 成員的宏如下所示:
????OPOST:打開(kāi)輸出處理功能。
????ONLCR:將輸出中的換行符轉(zhuǎn)換為回車/換行符。
????OCRNL:將輸出中的回車符轉(zhuǎn)換為新行符。
????ONOCR:在第0列不輸出回車符。
????ONLRET:不輸出回車符。
????OFILL:發(fā)送填充字符以提供延時(shí)。
????OFDEL:用DEL而不是NULL字符作為填充字符。
????NLDLY:新行符延時(shí)選擇。
????CRDLY:回車符延時(shí)選擇。
????TABDLY:制表符延時(shí)選擇。
????BSDLY:退格符延時(shí)選擇。
????VTDLY:垂直制表符延時(shí)選擇。
????FFDLY:換頁(yè)符延時(shí)選擇。
如果沒(méi)有設(shè)置 OPOST,其他標(biāo)志都被忽略,輸出模式使用頻率較小。
控制模式
?
控制模式控制終端的硬件特性。通過(guò)設(shè)置 termios 結(jié)構(gòu)中 c_cflag 標(biāo)志對(duì)控制模式進(jìn)行配置??捎糜?c_cflag 成員宏如下所示:????
????CLOCAL:忽略所有調(diào)制解調(diào)器的狀態(tài)行。
????CREAD:?jiǎn)?dòng)字符接收器。
????CS5:發(fā)收采用5位數(shù)據(jù)位。????
????CS6:發(fā)收采用6位數(shù)據(jù)位。
????CS7:發(fā)收采用7位數(shù)據(jù)位。
????CS8:發(fā)收采用8位數(shù)據(jù)位。
????CSTOPB:字符采用兩位停止位。
????HUPCL:關(guān)閉時(shí)掛斷調(diào)制解調(diào)器。
????PARENB:使能奇偶校驗(yàn)。????
????PARODD:使用奇校驗(yàn)。
若設(shè)置了 HUPCL,當(dāng)終端驅(qū)動(dòng)程序檢測(cè)到與終端對(duì)應(yīng)的最后一個(gè)文件描述符被關(guān)閉時(shí),它將通過(guò)設(shè)置調(diào)制解調(diào)器控制線來(lái)掛斷線路??刂颇J街饕糜诖芯€連接的物理模型中,是在串口編程中十分重要的標(biāo)志。
本地模式
本地模式控制終端的各種特性。通過(guò)設(shè)置 termios 結(jié)構(gòu)中 c_lflag 標(biāo)志對(duì)本地模式進(jìn)行配置。可用于 c_lflag 成員宏如下所示:
????ECHO:?jiǎn)⒂幂斎胱址谋镜鼗仫@功能。
????ECHOE:接收到 ERASE 時(shí)執(zhí)行退格、空格、退格的動(dòng)作組合。
????ECHOK:接收到 KILL 字符時(shí)執(zhí)行行刪除操作。
????ECHONL:回顯新行符。
????ICANON:?jiǎn)⒂脴?biāo)準(zhǔn)輸入處理。
????IEXTEN:?jiǎn)⒂没谔囟▽?shí)現(xiàn)的函數(shù)。
????ISIG:?jiǎn)⒂眯绿?hào)。
????NOFLSH:禁止清空隊(duì)列。
????TOSTOP:在試圖進(jìn)行寫(xiě)操作之前給后臺(tái)進(jìn)程發(fā)送一個(gè)信號(hào)。
這里最重要的標(biāo)志是 ECHO 和 ICANON。如果設(shè)置了 ICANON 標(biāo)志,就啟用標(biāo)準(zhǔn)輸入行處理模式,否則,就啟動(dòng)非標(biāo)準(zhǔn)模式。
特殊控制字符
特殊控制字符是一些字符組合,如 Ctrl+C,當(dāng)用戶鍵入這樣的組合鍵,終端會(huì)采取特殊處理方式。termios 中 c_cc 數(shù)組將各種特殊字符映射到對(duì)應(yīng)的支持函數(shù)。每個(gè)字符位置(數(shù)組下標(biāo))由對(duì)應(yīng)的宏定義的。根據(jù)終端是否被設(shè)置為標(biāo)準(zhǔn)模式(即上節(jié)提到的 ICANON 標(biāo)志),數(shù)組使用也分為標(biāo)準(zhǔn)與非標(biāo)準(zhǔn)兩種情形。
標(biāo)準(zhǔn)模式可以使用的數(shù)組下標(biāo):
????VEOF:EOF 字符。
????VEOL:EOL 字符。
????VERASE:ERASE 字符。
????VINTR:INTR 字符。
????VKILL:KILL 字符。
????VQUIT:QUIT 字符。
????VSUSP:SUSP 字符。
????VSTART:START 字符。
????VSTOP:STOP 字符。
非標(biāo)準(zhǔn)模式可以使用的數(shù)組下標(biāo):????
????VINTR:INTR 字符。
????VMIN:MIN 值。???
????VQUIT:QUIT 字符。
????VSUSP:SUSP 字符。
????VTIME:TIME 值。
????VSTART:START 字符。
????VSTOP:STOP 字符。
字符的詳細(xì)解釋如下表所示:
????INTR:該字符使終端驅(qū)動(dòng)程序向與終端相連的進(jìn)程發(fā)送 SIGINT 信號(hào)
????QUIT:該字符使終端驅(qū)動(dòng)程序向與終端相連的進(jìn)程發(fā)送 SIGQUIT 信號(hào)????
????ERASE:該字符使終端驅(qū)動(dòng)程序刪除輸入行中的最后一個(gè)字符
????KILL:該字符使終端驅(qū)動(dòng)程序刪除整個(gè)輸入行
????EOF:該字符使終端驅(qū)動(dòng)程序?qū)⑤斎胄兄械娜孔址麄鬟f給正在讀取輸入的應(yīng)用程序。若輸入行為空,read為0????
????EOL:作用類似于行結(jié)束符,效果和常用的新行符相同
????SUSP:該字符使終端驅(qū)動(dòng)程序向與終端相連的進(jìn)程發(fā)送SIGSUSP信號(hào),用于掛起當(dāng)前應(yīng)用程序
????STOP:字符作用“截流”,即阻止向終端的進(jìn)一步輸出。用于支持 XON/XOFF 流控,通常被設(shè)置為 ASCII 的XOFF????
????START:重新啟動(dòng)被 STOP 暫停的輸出,通過(guò)被設(shè)置為 ASCII 的 XON 字符。
VTIME 和 VMIN
TIME值和MIN值只能用于非標(biāo)準(zhǔn)模式,關(guān)于二者的使用詳解參見(jiàn)BBS其他帖子,或者發(fā)郵件至 tech@wch.cn 進(jìn)行咨詢了解。
?
SHELL下使用 stty 訪問(wèn)終端模式
在 shell 下可以使用 stty 可以訪問(wèn)終端 termios。如:
1.?#打印串口設(shè)備?ttyUSB0?設(shè)置情況。
2.?root@ubuntu:/#?stty?-F?/dev/ttyUSB0?-a?
3.?#設(shè)置?ttyUSB0?為?115200?波特率,8位數(shù)據(jù)位。?
4.?root@ubuntu:/#?stty?-F?/dev/ttyUSB0?ispeed?115200?ospeed?115200?cs8?
在設(shè)置成功之后就可以通過(guò) cat、echo 等 shell 命令對(duì)設(shè)備進(jìn)行讀寫(xiě)了。
在 termios 結(jié)構(gòu)體以及內(nèi)部終端控制標(biāo)志中,并非所有的參數(shù)對(duì)于實(shí)際的物理串口都是有效的,在使用過(guò)程中也不需要對(duì)于所有標(biāo)志的作用都有所理解。事實(shí)上,快速掌握一項(xiàng)技術(shù)的核心點(diǎn)也是一種學(xué)習(xí)能力。對(duì)于使用,熟悉并掌握操作框架十分有用。對(duì)于串口編程,核心步驟也十分鮮明,下面首先介紹 termios 相關(guān)的 API 函數(shù)。
核心配置函數(shù)
1. int tcgetattr(int fd, struct termios *termios_p);
函數(shù)功能:獲取當(dāng)前終端接口配置并將配置寫(xiě)入?yún)?shù) termios_p 指向的 termios 結(jié)構(gòu)體。一般操作時(shí)將配置保存為 old_termios,可以在需要時(shí)通過(guò) tcsetattr 函數(shù)對(duì)終端接口進(jìn)行重新配置。
2.?int tcsetattr(int fd, int actions, const struct termios *termios_p);
函數(shù)功能:使用 termios_p 指向的 termios 結(jié)構(gòu)體對(duì)終端接口進(jìn)行配置,參數(shù) actions 控制修改方式,共有3種修改方式,如下所示:
????TCSANOW:立刻對(duì)配置進(jìn)行修改。
????TCSADRAIN:等當(dāng)前輸出完成后再對(duì)配置進(jìn)行修改。
????TCSAFLUSH:等當(dāng)前輸出完成后再對(duì)配置進(jìn)行修改,但丟棄還未從 read 調(diào)用返回的當(dāng)前可用的任何輸入。
Note:如果需要在程序操作結(jié)束恢復(fù)終端或者串口的初始狀態(tài),那么就需要使用 tcgetattr 介紹中的操作步驟進(jìn)行恢復(fù)。
終端速度函數(shù)
1.speed_t cfgetispeed(const struct termios *);
函數(shù)功能:獲取終端讀取速度。
2.speed_t cfgetospeed(const struct termios *);
函數(shù)功能:獲取終端輸出速度。
3.int cfsetispeed(const struct termios *, speed_t speed);
函數(shù)功能:設(shè)置終端讀取速度。
4.int cfgetispeed(const struct termios *, speed_t speed);
函數(shù)功能:設(shè)置終端輸出速度。
Note:輸入與輸出速度是分開(kāi)控制的;根據(jù)函數(shù)形參,這些函數(shù)只作用于termios結(jié)構(gòu),而不是直接作用于設(shè)備。因此如果要設(shè)置速度,就要首先使用tcgetattr獲取當(dāng)前終端配置,然后使用上述函數(shù)設(shè)置速度,最后使用tcsetattr將termios配置寫(xiě)入設(shè)備。此外,還要注意操作系統(tǒng)支持的波特率范圍,通過(guò)查看 termios.h 可以獲取到。
其他控制函數(shù)
1.int tcdrain(int fd);
函數(shù)功能:讓調(diào)用程序一直等待,直到所有排隊(duì)的輸出都已發(fā)送完畢。
2.int tcflow(int fd, int flowtype);
函數(shù)功能:用于暫?;蛑匦麻_(kāi)始輸出。
3.int tcflush(int fd, int in_out_selector);
函數(shù)功能:用于清空輸入、輸出或者兩者同時(shí)清空。
串口編程和程序相對(duì)來(lái)說(shuō)是很簡(jiǎn)單的,之所以用較長(zhǎng)篇幅展示,主要是想在編程的基礎(chǔ)上掌握相關(guān)背景,原理以及注意事項(xiàng)。相信在遇到問(wèn)題的時(shí)候,就不會(huì)對(duì)于技術(shù)的概念和 API 的使用淺嘗輒止了。下面進(jìn)入具體應(yīng)用案例,由于現(xiàn)在很多電腦已經(jīng)沒(méi)有引出串口以及波特率范圍會(huì)受到限制,這里以 CH340 USB 轉(zhuǎn)串口芯片制作的模塊為基礎(chǔ)講解串口應(yīng)用程序開(kāi)發(fā),芯片的?Linux 驅(qū)動(dòng)鏈接如下:http://m.findthetime.net/download/CH341SER_LINUX_ZIP.html?
Notes:如果串口程序發(fā)生阻塞,檢查程序中是否調(diào)用了上述API。在打開(kāi)終端或者串口設(shè)備之前,對(duì)應(yīng)輸入或者待輸出數(shù)據(jù)緩存在驅(qū)動(dòng)程序中,因此要根據(jù)實(shí)際需求選擇是否調(diào)用tcflush清空相應(yīng)緩沖區(qū)數(shù)據(jù)。在實(shí)際應(yīng)用開(kāi)發(fā)中必須明確程序中配置的標(biāo)志位和函數(shù)的作用,在不確定作用的情況下最好保持默認(rèn)設(shè)備。
設(shè)備的打開(kāi)與關(guān)閉
1. int libtty_open(const char *devname);
函數(shù)功能:根據(jù)傳入的串口設(shè)備名打開(kāi)相應(yīng)的設(shè)備。成功返回設(shè)備句柄,失敗返回-1。
2. int libtty_close(int fd);
函數(shù)功能:關(guān)閉打開(kāi)的設(shè)備句柄。成功返回0,失敗返回負(fù)值。
設(shè)備的配置
1. int libtty_setopt(int fd, int speed, char databits, char stopbits, char parity);
函數(shù)功能:配置串口設(shè)備,傳入?yún)?shù)依次為波特率設(shè)置、數(shù)據(jù)位設(shè)置、停止位設(shè)置、檢驗(yàn)設(shè)置。
Notes:設(shè)備打開(kāi)前,可以通過(guò) ls /dev 確認(rèn)自己的硬件設(shè)備名,對(duì)于 USB 轉(zhuǎn)串口 IC,在系統(tǒng)下名稱為 "ttyUSBx",設(shè)備序號(hào)是根據(jù)插入主機(jī)的先后順序自動(dòng)分配的,這里我的為 "ttyUSB0",讀者根據(jù)自己的需要修改。
1.?/**?
2.??*?libtty_open?-?open?tty?device?
3.??*?@devname:?the?device?name?to?open?
4.??*?
5.??*?In?this?demo?device?is?opened?blocked,?you?could?modify?it?at?will.?
6.??*/??
7.?int?libtty_open(const?char?*devname)??
8.?{??
9.?????int?fd?=?open(devname,?O_RDWR?|?O_NOCTTY?|?O_NDELAY);???
10.?????int?flags?=?0;??
11.???????
12.?????if?(fd?==?-1)?{??????????????????????????
13.?????????perror("open?device?failed");??
14.?????????return?-1;??????????????
15.?????}??
16.???????
17.?????flags?=?fcntl(fd,?F_GETFL,?0);??
18.?????flags?&=?~O_NONBLOCK;??
19.?????if?(fcntl(fd,?F_SETFL,?flags)?<?0)?{??
20.?????????printf("fcntl?failed.\n");??
21.?????????return?-1;??
22.?????}??
23.???????????
24.?????if?(isatty(fd)?==?0)??
25.?????{??
26.?????????printf("not?tty?device.\n");??
27.?????????return?-1;??
28.?????}??
29.?????else??
30.?????????printf("tty?device?test?ok.\n");??
31.???????
32.?????return?fd;??
33.?} ?
Notes:
????傳入的 devname 參數(shù)為設(shè)備絕對(duì)路徑;
????O_NOCTTY 標(biāo)志用于通知系統(tǒng),這個(gè)程序不會(huì)成為對(duì)應(yīng)這個(gè)設(shè)備的控制終端。如果沒(méi)有指定這個(gè)標(biāo)志,那么任何一個(gè)輸入(如SIGINT等)都將會(huì)影響用戶的進(jìn)程;
????O_NDELAY標(biāo)志與O_NONBLOCK等效,但這里不僅僅是設(shè)置為非阻塞,還用于通知系統(tǒng),這個(gè)程序不關(guān)系DCD信號(hào)線所處的狀態(tài)(即與設(shè)備相連的另一端是否激活或者停止)。如果用戶指定了這一標(biāo)志,則進(jìn)程將會(huì)一直處在休眠狀態(tài),直到DCD信號(hào)線被激活;
????使用?fcntl?函數(shù)恢復(fù)設(shè)備為阻塞狀態(tài),在數(shù)據(jù)收發(fā)時(shí)就會(huì)進(jìn)行等待;
????使用?isatty?函數(shù)測(cè)試當(dāng)前打開(kāi)的設(shè)備句柄是否關(guān)聯(lián)到終端設(shè)備,如果不是tty設(shè)備,那么返回出錯(cuò);
1.?/**?
2.??*?libtty_setopt?-?config?tty?device?
3.??*?@fd:?device?handle?
4.??*?@speed:?baud?rate?to?set?
5.??*?@databits:?data?bits?to?set?
6.??*?@stopbits:?stop?bits?to?set?
7.??*?@parity:?parity?set?
8.??*?
9.??*?The?function?return?0?if?success,?or?-1?if?fail.?
10.??*/??
11.?int?libtty_setopt(int?fd,?int?speed,?int?databits,?int?stopbits,?char?parity)??
12.?{??
13.?????struct?termios?newtio;??
14.?????struct?termios?oldtio;??
15.?????int?i;??
16.???????
17.?????bzero(&newtio,?sizeof(newtio));??
18.?????bzero(&oldtio,?sizeof(oldtio));??
19.???????
20.?????if?(tcgetattr(fd,?&oldtio)?!=?0)?{??
21.?????????perror("tcgetattr");??????
22.?????????return?-1;???
23.?????}??
24.?????newtio.c_cflag?|=?CLOCAL?|?CREAD;??
25.?????newtio.c_cflag?&=?~CSIZE;??
26.????
27.?????/*?set?tty?speed?*/??
28.?????for?(i?=?0;?i?<?sizeof(speed_arr)?/?sizeof(int);?i++)?{??
29.?????????if?(speed?==?name_arr[i])?{????????
30.?????????????cfsetispeed(&newtio,?speed_arr[i]);???
31.?????????????cfsetospeed(&newtio,?speed_arr[i]);?????
32.?????????}???
33.?????}??
34.???????
35.?????/*?set?data?bits?*/??
36.?????switch?(databits)?{??
37.?????case?5:??????????????????
38.?????????newtio.c_cflag?|=?CS5;??
39.?????????break;??
40.?????case?6:??????????????????
41.?????????newtio.c_cflag?|=?CS6;??
42.?????????break;??
43.?????case?7:??????????????????
44.?????????newtio.c_cflag?|=?CS7;??
45.?????????break;??
46.?????case?8:??????
47.?????????newtio.c_cflag?|=?CS8;??
48.?????????break;????
49.?????default:?????
50.?????????fprintf(stderr,?"unsupported?data?size\n");??
51.?????????return?-1;???
52.?????}??
53.???????
54.?????/*?set?parity?*/??
55.?????switch?(parity)?{????
56.?????case?'n':??
57.?????case?'N':??
58.?????????newtio.c_cflag?&=?~PARENB;????/*?Clear?parity?enable?*/??
59.?????????newtio.c_iflag?&=?~INPCK;?????/*?Disable?input?parity?check?*/??
60.?????????break;???
61.?????case?'o':????
62.?????case?'O':??????
63.?????????newtio.c_cflag?|=?(PARODD?|?PARENB);?/*?Odd?parity?instead?of?even?*/??
64.?????????newtio.c_iflag?|=?INPCK;?????/*?Enable?input?parity?check?*/??
65.?????????break;???
66.?????case?'e':???
67.?????case?'E':????
68.?????????newtio.c_cflag?|=?PARENB;????/*?Enable?parity?*/?????
69.?????????newtio.c_cflag?&=?~PARODD;???/*?Even?parity?instead?of?odd?*/????
70.?????????newtio.c_iflag?|=?INPCK;?????/*?Enable?input?parity?check?*/??
71.?????????break;??
72.?????default:????
73.?????????fprintf(stderr,?"unsupported?parity\n");??
74.?????????return?-1;???
75.?????}???
76.???????
77.?????/*?set?stop?bits?*/???
78.?????switch?(stopbits)?{????
79.?????case?1:?????
80.?????????newtio.c_cflag?&=?~CSTOPB;???
81.?????????break;??
82.?????case?2:?????
83.?????????newtio.c_cflag?|=?CSTOPB;???
84.?????????break;??
85.?????default:?????
86.?????????perror("unsupported?stop?bits\n");???
87.?????????return?-1;??
88.?????}??
89.???
90.?????newtio.c_cc[VTIME]?=?0;???/*?Time-out?value?(tenths?of?a?second)?[!ICANON].?*/??
91.?????newtio.c_cc[VMIN]?=?0;????/*?Minimum?number?of?bytes?read?at?once?[!ICANON].?*/??
92.???????
93.?????tcflush(fd,?TCIOFLUSH);????
94.???????
95.?????if?(tcsetattr(fd,?TCSANOW,?&newtio)?!=?0)????
96.?????{??
97.?????????perror("tcsetattr");??
98.?????????return?-1;??
99.?????}??
100.?????return?0;??
101.?}??
Notes:
????首先保存了原先串口配置,為了方便演示,這里保存為局部變量,實(shí)際使用時(shí)是需要把配置保存到全局 termios 結(jié)構(gòu)體中的。使用?tcgetattr?還可以測(cè)試配置是否正確、串口是否可用等。返回值參見(jiàn) man 手冊(cè);
????使用 CLOCAL 用于忽略所有 MODEM 狀態(tài)信號(hào)線,CREAD 標(biāo)志用于使能接收。CSIZE 為數(shù)據(jù)位掩碼;
????調(diào)用?cfsetispeed?與cfsetospeed?參數(shù)設(shè)置波特率,函數(shù)中引用了兩個(gè)數(shù)組,在后面的完整代碼中會(huì)看到,這樣書(shū)寫(xiě)可以簡(jiǎn)化設(shè)置代碼;
????通過(guò)控制 c_cflag 與 c_iflag 配置串口數(shù)據(jù)位、停止位以及校驗(yàn)設(shè)置等;
????VTIME?與?VMIN?作用已經(jīng)講解多次,不再贅述,值得注意的是,TIME 值的單位是十分之一秒;
????通過(guò)?tcflush?清空輸入和輸出緩沖區(qū),根據(jù)實(shí)際需要修改;
????最后通過(guò)?tcsetattr?函數(shù)對(duì)將配置實(shí)際作用于串口;
數(shù)據(jù)讀寫(xiě)直接使用?read、write?函數(shù)接口就可以了,因此沒(méi)有列舉出來(lái)。下面給出完整的串口讀寫(xiě)測(cè)試代碼:
1.?/*?TTY?testing?utility?(using?tty?driver)?
2.??*?Copyright?(c)?2017?
3.??*?This?program?is?free?software;?you?can?redistribute?it?and/or?modify?
4.??*?it?under?the?terms?of?the?GNU?General?Public?License?as?published?by?
5.??*?the?Free?Software?Foundation;?either?version?2?of?the?License.?
6.??*?
7.??*?Cross-compile?with?cross-gcc?-I?/path/to/cross-kernel/include?
8.??*/??
9.???
10.?#include???
11.?#include???
12.?#include???
13.?#include?????
14.?#include?????
15.?#include??????
16.?#include???
17.?#include????
18.?#include?????
19.???
20.?int?speed_arr[]?=?{??
21.?????B115200,??
22.?????B57600,??
23.?????B38400,??
24.?????B19200,??
25.?????B9600,??
26.?????B4800,??
27.?????B2400,??
28.?????B1200,??
29.?????B300??
30.?};??
31.???
32.?int?name_arr[]?=?{??
33.?????115200,??
34.?????57600,??
35.?????38400,??
36.?????19200,??
37.?????9600,??
38.?????4800,??
39.?????2400,??
40.?????1200,??
41.?????300??
42.?};??
43.???
44.?/**?
45.??*?libtty_setopt?-?config?tty?device?
46.??*?@fd:?device?handle?
47.??*?@speed:?baud?rate?to?set?
48.??*?@databits:?data?bits?to?set?
49.??*?@stopbits:?stop?bits?to?set?
50.??*?@parity:?parity?set?
51.??*?
52.??*?The?function?return?0?if?success,?or?-1?if?fail.?
53.??*/??
54.?int?libtty_setopt(int?fd,?int?speed,?int?databits,?int?stopbits,?char?parity)??
55.?{??
56.?????struct?termios?newtio;??
57.?????struct?termios?oldtio;??
58.?????int?i;??
59.???????
60.?????bzero(&newtio,?sizeof(newtio));??
61.?????bzero(&oldtio,?sizeof(oldtio));??
62.???????
63.?????if?(tcgetattr(fd,?&oldtio)?!=?0)?{??
64.?????????perror("tcgetattr");??????
65.?????????return?-1;???
66.?????}??
67.?????newtio.c_cflag?|=?CLOCAL?|?CREAD;??
68.?????newtio.c_cflag?&=?~CSIZE;??
69.????
70.?????/*?set?tty?speed?*/??
71.?????for?(i?=?0;?i?<?sizeof(speed_arr)?/?sizeof(int);?i++)?{??
72.?????????if?(speed?==?name_arr[i])?{????????
73.?????????????cfsetispeed(&newtio,?speed_arr[i]);???
74.?????????????cfsetospeed(&newtio,?speed_arr[i]);?????
75.?????????}???
76.?????}??
77.???????
78.?????/*?set?data?bits?*/??
79.?????switch?(databits)?{??
80.?????case?5:??????????????????
81.?????????newtio.c_cflag?|=?CS5;??
82.?????????break;??
83.?????case?6:??????????????????
84.?????????newtio.c_cflag?|=?CS6;??
85.?????????break;??
86.?????case?7:??????????????????
87.?????????newtio.c_cflag?|=?CS7;??
88.?????????break;??
89.?????case?8:??????
90.?????????newtio.c_cflag?|=?CS8;??
91.?????????break;????
92.?????default:?????
93.?????????fprintf(stderr,?"unsupported?data?size\n");??
94.?????????return?-1;???
95.?????}??
96.???????
97.?????/*?set?parity?*/??
98.?????switch?(parity)?{????
99.?????case?'n':??
100.?????case?'N':??
101.?????????newtio.c_cflag?&=?~PARENB;????/*?Clear?parity?enable?*/??
102.?????????newtio.c_iflag?&=?~INPCK;?????/*?Disable?input?parity?check?*/??
103.?????????break;???
104.?????case?'o':????
105.?????case?'O':??????
106.?????????newtio.c_cflag?|=?(PARODD?|?PARENB);?/*?Odd?parity?instead?of?even?*/??
107.?????????newtio.c_iflag?|=?INPCK;?????/*?Enable?input?parity?check?*/??
108.?????????break;???
109.?????case?'e':???
110.?????case?'E':????
111.?????????newtio.c_cflag?|=?PARENB;????/*?Enable?parity?*/?????
112.?????????newtio.c_cflag?&=?~PARODD;???/*?Even?parity?instead?of?odd?*/????
113.?????????newtio.c_iflag?|=?INPCK;?????/*?Enable?input?parity?check?*/??
114.?????????break;??
115.?????default:????
116.?????????fprintf(stderr,?"unsupported?parity\n");??
117.?????????return?-1;???
118.?????}???
119.???????
120.?????/*?set?stop?bits?*/???
121.?????switch?(stopbits)?{????
122.?????case?1:?????
123.?????????newtio.c_cflag?&=?~CSTOPB;???
124.?????????break;??
125.?????case?2:?????
126.?????????newtio.c_cflag?|=?CSTOPB;???
127.?????????break;??
128.?????default:?????
129.?????????perror("unsupported?stop?bits\n");???
130.?????????return?-1;??
131.?????}??
132.???
133.?????newtio.c_cc[VTIME]?=?0;???/*?Time-out?value?(tenths?of?a?second)?[!ICANON].?*/??
134.?????newtio.c_cc[VMIN]?=?0;????/*?Minimum?number?of?bytes?read?at?once?[!ICANON].?*/??
135.???????
136.?????tcflush(fd,?TCIOFLUSH);????
137.???????
138.?????if?(tcsetattr(fd,?TCSANOW,?&newtio)?!=?0)????
139.?????{??
140.?????????perror("tcsetattr");??
141.?????????return?-1;??
142.?????}??
143.?????return?0;??
144.?}??
145.???
146.?/**?
147.??*?libtty_open?-?open?tty?device?
148.??*?@devname:?the?device?name?to?open?
149.??*?
150.??*?In?this?demo?device?is?opened?blocked,?you?could?modify?it?at?will.?
151.??*/??
152.?int?libtty_open(const?char?*devname)??
153.?{??
154.?????int?fd?=?open(devname,?O_RDWR?|?O_NOCTTY?|?O_NDELAY);???
155.?????int?flags?=?0;??
156.???????
157.?????if?(fd?==?-1)?{??????????????????????????
158.?????????perror("open?device?failed");??
159.?????????return?-1;??????????????
160.?????}??
161.???????
162.?????flags?=?fcntl(fd,?F_GETFL,?0);??
163.?????flags?&=?~O_NONBLOCK;??
164.?????if?(fcntl(fd,?F_SETFL,?flags)?<?0)?{??
165.?????????printf("fcntl?failed.\n");??
166.?????????return?-1;??
167.?????}??
168.???????????
169.?????if?(isatty(fd)?==?0)??
170.?????{??
171.?????????printf("not?tty?device.\n");??
172.?????????return?-1;??
173.?????}??
174.?????else??
175.?????????printf("tty?device?test?ok.\n");??
176.???????
177.?????return?fd;??
178.?}??
179.???
180.?/**?
181.??*?libtty_close?-?close?tty?device?
182.??*?@fd:?the?device?handle?
183.??*?
184.??*/??
185.?int?libtty_close(int?fd)??
186.?{??
187.?????return?close(fd);??
188.?}??
189.???
190.?void?tty_test(int?fd)??
191.?{??
192.?????int?nwrite,?nread;??
193.?????char?buf[100];??
194.???????
195.?????memset(buf,?0x32,?sizeof(buf));??
196.???????
197.?????while?(1)?{??
198.?????????nwrite?=?write(fd,?buf,?sizeof(buf));??
199.?????????printf("wrote?%d?bytes?already.\n",?nwrite);??
200.?????????nread?=?read(fd,?buf,?sizeof(buf));??
201.?????????printf("read?%d?bytes?already.\n",?nread);??
202.?????????sleep(2);??
203.?????}??
204.???????
205.?}??
206.???
207.?int?main(int?argc,?char?*argv[])??
208.?{??
209.?????int?fd;??
210.?????int?ret;??
211.???????
212.?????fd?=?libtty_open("/dev/ttyUSB0");??
213.?????if?(fd?<?0)?{??
214.?????????printf("libtty_open?error.\n");??
215.?????????exit(0);??
216.?????}??
217.???????
218.?????ret?=?libtty_setopt(fd,?9600,?8,?1,?'n');??
219.?????if?(!ret)?{??
220.?????????printf("libtty_setopt?error.\n");??
221.?????????exit(0);??
222.?????}??
223.???
224.?????tty_test(fd);??
225.???????
226.?????ret?=?libtty_close(fd);??
227.?????if?(!ret)?{??
228.?????????printf("libtty_close?error.\n");??
229.?????????exit(0);??
230.?????}??
231.?} ?
執(zhí)行成功的話,會(huì)在終端屏幕上看到每隔兩秒輸出串口成功發(fā)送和接收的字節(jié)數(shù),測(cè)試時(shí)可以直接短接串口的發(fā)送和接收引腳進(jìn)行測(cè)試。以下為成功測(cè)試截圖: