一些背景 ? ? ? ? ? ? ? ? ? ??? ? ?


在大部分講解?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ǔ)以及分類,如下所示:


圖片1.png


對(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)的定義如下:


圖片2.png


如定義所示,影響終端的參數(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è)試截圖:


圖片3.png