日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Linux串口編程詳解

 fym0121 2010-12-25

串口本身,標(biāo)準(zhǔn)和硬件 ?

串口是計(jì)算機(jī)上的串行通訊的物理接口。計(jì)算機(jī)歷史上,串口曾經(jīng)被廣泛用于連接計(jì)算機(jī)和終端設(shè)備和各種外部設(shè)備。雖然以太網(wǎng)接口和USB接口也是以一個(gè)串行流進(jìn)行數(shù)據(jù)傳送的,但是串口連接通常特指那些與RS-232標(biāo)準(zhǔn)兼容的硬件或者調(diào)制解調(diào)器的接口。雖然現(xiàn)在在很多個(gè)人計(jì)算機(jī)上,原來用以連接外部設(shè)備的串口已經(jīng)廣泛的被USB和Firewire替代;而原來用以連接網(wǎng)絡(luò)的串口則被以太網(wǎng)替代,還有用以連接終端的串口設(shè)備則已經(jīng)被MDA或者VGA取而代之。但是,一方面因?yàn)榇诒旧碓靸r(jià)便宜技術(shù)成熟,另一方面因?yàn)榇诘目刂婆_(tái)功能RS-232標(biāo)準(zhǔn)高度標(biāo)準(zhǔn)化并且非常普及,所以直到現(xiàn)在它仍然被廣泛應(yīng)用到各種設(shè)備上。某些計(jì)算機(jī)使用一個(gè)叫做UART的集成電路來作為串口設(shè)備。這個(gè)集成電路可以進(jìn)行字符和異步串行通訊序列之間的轉(zhuǎn)換,并且可以自動(dòng)地處理數(shù)據(jù)的時(shí)序。而某些低端設(shè)備則會(huì)讓CPU直接通過輸出針來傳送數(shù)據(jù),這種技術(shù)叫做bit-banging。因?yàn)椤按凇保琑S-232和UARTs基本上總是在同一個(gè)語境中出現(xiàn),所以這些名詞通常會(huì)被搞混。下面逐一解釋以下一些重要的名詞和術(shù)語。

什么是串行通信 ?

計(jì)算機(jī)可以每次傳送一個(gè)或者多個(gè)位(bit)的數(shù)據(jù)?!按小敝傅氖矫看沃粋鬏斠晃?1bit)數(shù)據(jù)。當(dāng)需要通過串行通訊傳輸一個(gè)字(word)的數(shù)據(jù)時(shí),只能以每次一位的方式接收或者發(fā)送。每個(gè)位可能是on(1)或者off(0)。很多技術(shù)術(shù)語中經(jīng)常用mark表示on,而space表示off。

串行數(shù)據(jù)的速度通常用每秒傳輸?shù)淖止?jié)數(shù)bits-per-second(bps)或者波特率(baud)表示。這個(gè)值表示的是每秒鐘被送出的0和1 的個(gè)數(shù)。很久很久以前,300bps就是很快的速度了,而現(xiàn)在的電腦可以處理高達(dá)430,800的RS-232速率。表示波特率的單位還有kpbs和 Mbps,1kps=1000bps而1Mbps=1000kbps。一般有人提到串行設(shè)備的時(shí)候,它通常說可能是某種數(shù)據(jù)通訊設(shè)備-DCE(Data Communications Equipment)或者數(shù)據(jù)終端設(shè)備-DTE(Data Terminal Equipment)。它們之間的區(qū)別非常簡(jiǎn)單,每個(gè)信號(hào)對(duì),比如傳送和接收,它們倆正好是相反的。如果需要將兩個(gè)DTE或者DCE設(shè)備連接起來的話,需要適配器或者交叉線纜將信號(hào)對(duì)交換。

什么是RS-232 ?

RS-232是EIA(Electronic Industries Association)定義的串行通信的電器接口。RS-232事實(shí)上有三種(A,B和C),它們分別采用不同的電壓來表示on和off。最被廣泛使用的是RS-232C,它將mark(on)比特的電壓定義為-3V到-12V之間,而將space(off)的電壓定義到+3V到+12V之間。雖然 RS-232C標(biāo)準(zhǔn)說信號(hào)最遠(yuǎn)被傳輸8m,但事實(shí)上你可以使用它傳輸更長(zhǎng)的距離,直到信號(hào)波特率已經(jīng)小到不行了為止。 RS-232的連結(jié)線中除去用來傳入傳出數(shù)據(jù)的電線,還有一些用來提供時(shí)序,狀態(tài)和握手的電線:

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

針腳 名稱 全名 方向(主機(jī) 外設(shè))
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

另外兩個(gè)比較常見的串行接口的標(biāo)準(zhǔn)式RS-422和RS-574。RS-422使用更低的電壓和差分信號(hào),這樣可以將傳輸距離擴(kuò)張到300m。而RS-574定義了通??梢砸姷降挠迷陔娔X上的9針連接器和電壓。

信號(hào)定義 ?

RS-232標(biāo)準(zhǔn)定義了18個(gè)不同的串行通信的信號(hào)。而這些之中,僅僅有如下6個(gè)可以在UNIX環(huán)境中使用。

  • GND - Logic Ground

    從技術(shù)角度講,GND不能算是信號(hào)。但是沒有它其他信號(hào)都不能用了?;旧?,logic ground有點(diǎn)像一個(gè)參考電壓,通過它來判斷哪個(gè)電壓表示正哪個(gè)電壓表示負(fù)。

  • TXD - Transmitted Data

    TXD信號(hào)負(fù)載著從你的電腦或者設(shè)備到另一端(比如調(diào)制解調(diào)器)的數(shù)據(jù)。Mark范圍的電壓被解析成1,而space范圍電壓被解析成0。

  • RXD - Received Data

    RXD于TXD正好相反。它負(fù)載著從另一端的電腦或者設(shè)備上傳到你的工作站的數(shù)據(jù)。Mark和space的解析方法于TXD一致。

  • DCD - Data Carrier Detect

    DCD信號(hào)通常來自串口連結(jié)線的另一端。這條信號(hào)線上的space電壓表示另一端的電腦或者設(shè)備現(xiàn)在已經(jīng)連接。但是,DCD信號(hào)線卻不是總可以得到的,有些設(shè)備上有這條信號(hào)線,而有的則沒有。

  • DTR - Data Terminal Ready

    DTR信號(hào)是你的工作站產(chǎn)生的,用以告訴另一端的電腦或者設(shè)備你已經(jīng)是否已經(jīng)準(zhǔn)備好了。Space電壓表示準(zhǔn)備好了,而mark電壓表示沒有準(zhǔn)備好。當(dāng)你在工作站上打開串行接口時(shí),DTR通常自動(dòng)被設(shè)置位有效。

  • CTS - Clear To Send

    CTS則通常來自連結(jié)線的另一端。Space電壓表示你可以從工作站送出更多的數(shù)據(jù)。CTS通常用來協(xié)調(diào)你的工作站和另一端之間的串行數(shù)據(jù)流。

  • RTS - Request To Send

    如果RTS信號(hào)被設(shè)置成space電壓,這表示你準(zhǔn)備好了一些數(shù)據(jù)需要傳送。和CTS一樣,RTS也被用來協(xié)調(diào)工作站和另一端的電腦或者設(shè)備之間的數(shù)據(jù)流。有些工作站上會(huì)一直將這個(gè)信號(hào)設(shè)置位space。

異步通訊 ?

計(jì)算機(jī)為了弄懂傳給它的串行數(shù)據(jù),它需要確定每個(gè)字符開始和結(jié)束的位置。這通常是用異步串行數(shù)據(jù)來完成的。

在異步模式中,除非有字符被傳輸,否則串行數(shù)據(jù)線總是處于mark(1)狀態(tài)。有一個(gè)start位會(huì)被加入傳輸字符的各個(gè)位之前,在字符本身的位之后會(huì)有一個(gè)可選的parity位和一個(gè)或者多個(gè)stop位。Start位總是space(0)并且它會(huì)告訴計(jì)算機(jī)新的串行數(shù)據(jù)過來了。數(shù)據(jù)可以隨時(shí)被送出或者接收,這就是所謂的異步。

#ref(): File not found: "async.gif" at page "Linux串口編程詳解"

那個(gè)可選的parity位僅僅是所有傳輸位的和,這個(gè)和用以表示傳輸字符中有奇數(shù)個(gè)1還是偶數(shù)個(gè)1。在偶數(shù)parity中,如果有傳輸字符中有偶數(shù)個(gè)1,那么parity位被設(shè)置成0,而傳輸字符中有奇數(shù)個(gè)1,那么parity位被設(shè)置成1。在奇數(shù)parity中,位設(shè)置與此相反。還有一些術(shù)語,比如space parity, mark parity和no parity。Space parity是指parity位會(huì)一直被設(shè)置位0,而mark parity正好與此相反,parity會(huì)一直是1。No parity的意思就是根本不會(huì)傳輸parity位。剩余的位叫做stop位。傳輸字符之間可以有1個(gè),1.5個(gè)或者2個(gè)stop位,而且,它們的值總是1。傳統(tǒng)上,Stop位式用給計(jì)算機(jī)一些時(shí)間處理前面的字符的,但是它只是被用來同步接收數(shù)據(jù)的計(jì)算機(jī)和接受的字符。異步數(shù)據(jù)通常被表示成"8N1","7E1",或者與此類似的形式。這表示“8數(shù)據(jù)位,no parity和1個(gè)stop bit”,還有相應(yīng)得,“7數(shù)據(jù)位,even parity和1個(gè)stop bit”。

什么是全雙工和半雙工 ?

全雙工(Full duplex)是說計(jì)算機(jī)可以同時(shí)接受和發(fā)送數(shù)據(jù)——也就是它有兩個(gè)分開的數(shù)據(jù)傳輸通道(一個(gè)傳入,一個(gè)傳出)。

半雙工(Half duplex)表示計(jì)算機(jī)不能同時(shí)接受和發(fā)送數(shù)據(jù),而在某一時(shí)刻它只能單一的傳送或者接收。這通常意味著,它只有一個(gè)數(shù)據(jù)通道。半雙工并不是說RS-232的某些信號(hào)不能使用,而是,它通常是使用了有別于RS-232的其他不支持全雙工的標(biāo)準(zhǔn)。

什么是流控制 ?

兩個(gè)串行接口之間的傳輸數(shù)據(jù)流通常需要協(xié)調(diào)一致才行。這可能是由于用以通信的某個(gè)串行接口或者某些存儲(chǔ)介質(zhì)的中間串行通信鏈路的限制造成的。對(duì)于異步數(shù)據(jù)這里有兩個(gè)方法做到這一點(diǎn)。

第一種方法通常被叫做“軟件”流控制。這種方法采用特殊字符來開始(XON,DC1,八進(jìn)制數(shù)021)或者結(jié)束(XOFF,DC3或者八進(jìn)制數(shù) 023)數(shù)據(jù)流。而這些字符都在ASCII中定義好了。雖然這些編碼對(duì)于傳輸文本信息非常有用,但是它們卻不能被用于在特殊程序中的其他類型的信息。

第二種方法叫做“硬件”流控制。這種方法使用RS-232標(biāo)準(zhǔn)的CTS和RTS信號(hào)來取代之前提到的特殊字符。當(dāng)準(zhǔn)備就緒時(shí),接受一方會(huì)將CTS信號(hào)設(shè)置成為space電壓,而尚未準(zhǔn)備就緒時(shí)它會(huì)被設(shè)置成為mark電壓。相應(yīng)得,發(fā)送方會(huì)在準(zhǔn)備就緒的情況下將RTS設(shè)置成space電壓。正因?yàn)橛布骺刂剖褂昧擞跀?shù)據(jù)分隔的信號(hào),所以與需要傳輸特殊字符的軟件流控制相比它的速度很快。但是,并不是所有的硬件和操作系統(tǒng)都支持CTS/RTS流控制。

什么是BREAK ?

通常,直到有數(shù)據(jù)傳輸時(shí),接收和傳輸信號(hào)會(huì)保持在mark電壓。如果一個(gè)信號(hào)掉到space電壓并且持續(xù)了很長(zhǎng)時(shí)間,一般來說是1/4到1/2秒,那么就說有一個(gè)break條件存在了。

BREAK經(jīng)常被用來重置一條數(shù)據(jù)線或者用來改變像調(diào)制解調(diào)器這樣的設(shè)備的通訊模式。

同步通訊 ?

與異步數(shù)據(jù)不同,同步數(shù)據(jù)是一個(gè)穩(wěn)定的字節(jié)流。為了能夠在線路上讀取到數(shù)據(jù),計(jì)算機(jī)必須提供或者接受一個(gè)時(shí)鐘,這樣才能保證發(fā)送端和接收端同步。盡管已經(jīng)有同步時(shí)鐘,計(jì)算機(jī)還是必須以某種方式標(biāo)志數(shù)據(jù)流的開端。做這件事情最常見的辦法就是使用像Serial Data Link Control("SDLC")或者High-Speed Data Link Control("HDLC")這樣的數(shù)據(jù)包通訊協(xié)議。

這些協(xié)議每個(gè)都定義了一個(gè)確定的比特序列來表示數(shù)據(jù)包的開始和結(jié)束。當(dāng)然,它們也定義了一個(gè)用來表示沒有數(shù)據(jù)傳輸?shù)谋忍匦蛄小_@些比特序列可以幫助計(jì)算機(jī)識(shí)別數(shù)據(jù)包的開端。

因?yàn)橥絽f(xié)議可以不使用每個(gè)字符的同步比特位,所以通常它們的性能比異步通訊快最少25%,而且一般比較適用于遠(yuǎn)距離的網(wǎng)絡(luò)鏈接或者有兩個(gè)串口接口的配置的情況。盡管同步通訊的速度有優(yōu)勢(shì),大部分RS-232硬件卻不支持它,因?yàn)橥酵ㄓ嵭枰渌挠布蛙浖?/p>

用戶看到的串口和用戶空間的串口編程 ?

和其他設(shè)備一樣,Linux也是通過設(shè)備文件來提供訪問串口的功能。當(dāng)需要訪問串口的時(shí)候,你只需要open相應(yīng)的文件。

串口的設(shè)備文件 ?

Linux系統(tǒng)上一般有一個(gè)或者多個(gè)串口,而這些串口設(shè)備文件名字比較奇怪,如比下面這樣

串口設(shè)備文件名

操作系統(tǒng) 串口1 串口2 USB/RS-232轉(zhuǎn)換器
Windows COM1 COM2 -
Linux /dev/ttyS0 /dev/ttyS1 /dev/ttyUSB0

打開串口 ?

因?yàn)榇诤推渌O(shè)備一樣,在類Unix系統(tǒng)中都是以設(shè)備文件的形式存在的,所以,理所當(dāng)然得你可以使用open(2)系統(tǒng)調(diào)用/函數(shù)來訪問它。但 Linux系統(tǒng)中卻有一個(gè)稍微不方便的地方,那就是普通用戶一般不能直接訪問設(shè)備文件。你可以選擇以下方式做一些調(diào)整,以便你編寫的程序可以訪問串口。

  • 改變?cè)O(shè)備文件的訪問權(quán)限設(shè)置 [#cd9bd1e0]
  • 以root超級(jí)用戶的身份運(yùn)行程序 [#kdd0e577]
  • 將你的程序編寫位setuid程序,以串口設(shè)備所有者的身份運(yùn)行程序 [#s7b703ff]

OK.假如你已經(jīng)準(zhǔn)備好了讓串口設(shè)備文件可以被所有用戶訪問,你可以在Linux系統(tǒng)中實(shí)驗(yàn)一下下面這個(gè)程序,它可以打開計(jì)算機(jī)的串口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);
}
}

打開文件的選項(xiàng) ?

打開串口連接的時(shí)候,程序在open函數(shù)中除了Read+Write模式以外還指定了兩個(gè)選項(xiàng);

fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);

標(biāo)志O_NOCTTY可以告訴UNIX這個(gè)程序不會(huì)成為這個(gè)端口上的“控制終端”。如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信號(hào)等等,會(huì)影響到你的進(jìn)程。而有些程序比如getty(1M/8)則會(huì)在打開登錄進(jìn)程的時(shí)候使用這個(gè)特性,但是通常情況下,用戶程序不會(huì)使用這個(gè)行為。

O_NDELAY標(biāo)志則是告訴UNIX,這個(gè)程序并不關(guān)心DCD信號(hào)線的狀態(tài)——也就是不關(guān)心端口另一端是否已經(jīng)連接。如果不指定這個(gè)標(biāo)志的話,除非DCD信號(hào)線上有space電壓否則這個(gè)程序會(huì)一直睡眠。

給端口上寫數(shù)據(jù) ?

給端口上寫入數(shù)據(jù)也很簡(jiǎn)單,使用write(2)系統(tǒng)調(diào)用就可以發(fā)送數(shù)據(jù)了:

n = write(fd, "ATZ\r", 4);
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);

和寫入其他設(shè)備文件的方式相同,write函數(shù)也會(huì)返回發(fā)送數(shù)據(jù)的字節(jié)數(shù)或者在發(fā)生錯(cuò)誤的時(shí)候返回-1。通常,發(fā)送數(shù)據(jù)最常見的錯(cuò)誤就是EIO,當(dāng)調(diào)制解調(diào)器或者數(shù)據(jù)鏈路將Data Carrier Detect(DCD)信號(hào)線弄掉了,就會(huì)發(fā)生這個(gè)錯(cuò)誤。而且,直至關(guān)閉端口這個(gè)情況會(huì)一直持續(xù)。

從端口上讀取數(shù)據(jù) ?

從串口上讀取數(shù)據(jù)的時(shí)候就得?;ㄕ辛?。因?yàn)?,如果你在原?shù)據(jù)模式(raw data mode)操作端口的話,每個(gè)read(2)系統(tǒng)調(diào)用都會(huì)返回從串口輸入緩沖區(qū)中實(shí)際得到的字符的個(gè)數(shù)。在不能得到數(shù)據(jù)的情況下,read(2)系統(tǒng)調(diào)用就會(huì)一直等著,只到有端口上新的字符可以讀取或者發(fā)生超時(shí)或者錯(cuò)誤的情況發(fā)生。如果需要read(2)函數(shù)迅速返回的話,你可以使用下面這個(gè)方式:

fcntl(fd, F_SETFL, FNDELAY);

標(biāo)志FNDELAY可以保證read(2)函數(shù)在端口上讀不到字符的時(shí)候返回0。需要回到正常(阻塞)模式的時(shí)候,需要再次在不帶FNDELAY標(biāo)志的情況下調(diào)用fcntl(2)函數(shù):

fcntl(fd, F_SETFL, 0);

當(dāng)然,如果你最初就是以O(shè)_NDELAY標(biāo)志打開串口的,你也可在之后使用這個(gè)方法改變讀取的行為方式。

關(guān)閉串口 ?

可以使用close(2)系統(tǒng)調(diào)用關(guān)閉串口:

close(fd);

關(guān)閉串口會(huì)將DTR信號(hào)線設(shè)置成low,這會(huì)導(dǎo)致很多調(diào)制解調(diào)器掛起。

配置串口 ?

POSIX終端接口 ?

很多系統(tǒng)都支持POSIX終端(串口)接口。程序可以利用這個(gè)接口來改變終端的參數(shù),比如,波特率,字符大小等等。要使用這個(gè)端口的話,你必須將<termios.h>頭文件包含到你的程序中。這個(gè)頭文件中定義了終端控制結(jié)構(gòu)體和POSIX控制函數(shù)。

與串口操作相關(guān)的最重要的兩個(gè)POSIX函數(shù)可能就是tcgetattr(3)和tcsetattr(3)。顧名思義,這兩個(gè)函數(shù)分別用來取得設(shè)設(shè)置終端的屬性。調(diào)用這兩個(gè)函數(shù)的時(shí)候,你需要提供一個(gè)包含著所有串口選項(xiàng)的termios結(jié)構(gòu)體:

termios結(jié)構(gòu)體成員

成員 描述
c_cflag 控制選項(xiàng)
c_lflag 行選項(xiàng)
c_iflag 輸入選項(xiàng)
c_oflag 輸出選項(xiàng)
c_cc 控制字符
c_ispeed 輸入波特率(NEW)
c_ospeed 輸出波特率(NEW)

控制選項(xiàng) ?

通過termios結(jié)構(gòu)體的c_cflag成員可以控制波特率,數(shù)據(jù)的比特?cái)?shù),parity,停止位和硬件流控制。下面這張表列出了所有可以使用的常數(shù)。

c_cflag常數(shù)

常量 描述
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)

在傳統(tǒng)的POSIX編程中,當(dāng)連接一個(gè)本地的(不通過調(diào)制解調(diào)器)或者遠(yuǎn)程的終端(通過調(diào)制解調(diào)器)時(shí),這里有兩個(gè)選項(xiàng)應(yīng)當(dāng)一直打開,一個(gè)是 CLOCAL,另一個(gè)是CREAD。這兩個(gè)選項(xiàng)可以保證你的程序不會(huì)變成端口的所有者,而端口所有者必須去處理發(fā)散性作業(yè)控制和掛斷信號(hào),同時(shí)還保證了串行接口驅(qū)動(dòng)會(huì)讀取過來的數(shù)據(jù)字節(jié)。

波特率常數(shù)(CBAUD,B9600等等)通常指用到那些不支持c_ispeed和c_ospeed成員的舊的接口上。后面文章將會(huì)提到如何使用其他POSIX函數(shù)來設(shè)置波特率。

千萬不要直接用使用數(shù)字來初始化c_cflag(當(dāng)然還有其他標(biāo)志),最好的方法是使用位運(yùn)算的與或非組合來設(shè)置或者清除這個(gè)標(biāo)志。不同的操作系統(tǒng)版本會(huì)使用不同的位模式,使用常數(shù)定義和位運(yùn)算組合來避免重復(fù)工作從而提高程序的可移植性。

設(shè)置波特率 ?

不同的操作系統(tǒng)會(huì)將波特率存儲(chǔ)在不同的位置。舊的編程接口將波特率存儲(chǔ)在上表所示的c_cflag成員中,而新的接口實(shí)裝則提供了c_ispeed和c_ospeed成員來保存實(shí)際波特率的值。

程序中可是使用cfsetospeed(3)和cfsetispeed(3)函數(shù)在termios結(jié)構(gòu)體中設(shè)置波特率而不用去管底層操作系統(tǒng)接口。下面的代碼是個(gè)非常典型的設(shè)置波特率的例子。

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

函數(shù)tcgetattr(3)會(huì)將當(dāng)前串口配置回填到termio結(jié)構(gòu)體option中。然后,程序設(shè)置了輸入輸出的波特率并且將本地模式 (CLOCAL)和串行數(shù)據(jù)接收(CREAD)設(shè)置為有效,接著將新的配置作為參數(shù)傳遞給函數(shù)tcsetattr(3)。常量TCSANOW標(biāo)志所有改變必須立刻生效而不用等到數(shù)據(jù)傳輸結(jié)束。其他另一些常數(shù)可以保證等待數(shù)據(jù)結(jié)束或者刷新輸入輸出之后再生效。

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

不同的系統(tǒng)上可能支持不同的輸入輸出速度,所以,通過串口連接兩臺(tái)機(jī)器或者設(shè)備的時(shí)候,應(yīng)該將波特率設(shè)置成兩者中較小的那個(gè),即MIN(speed1, speed2)。

設(shè)置字符大小 ?

設(shè)置字符大小的時(shí)候,這里卻沒有像設(shè)置波特率那么方便的函數(shù)。所以,程序中需要一些位掩碼運(yùn)算來把事情搞定。字符大小以比特為單位指定:

options.c_flag &= ~CSIZE; /* Mask the character size bits */
options.c_flag |= CS8; /* Select 8 data bits */

設(shè)置奇偶校驗(yàn) ?

與設(shè)置字符大小的方式差不多,這里仍然需要組合一些位掩碼來將奇偶校驗(yàn)設(shè)為有效和奇偶校驗(yàn)的類型。UNIX串口驅(qū)動(dòng)可以生成even,odd和no parity位碼。設(shè)置space奇偶校驗(yàn)需要耍點(diǎn)小手段。

  • 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;

設(shè)置硬件流控制 ?

某些版本的UNIX系統(tǒng)支持通過CTS(Clear To Send)和RTS(Request To Send)信號(hào)線來設(shè)置硬件流控制。如果系統(tǒng)上定義了CNEW_RTSCTS和CRTSCTS常量,那么很可能它會(huì)支持硬件流控制。使用下面的方法將硬件流控制設(shè)置成有效:

options.c_cflag |= CNEW_RTSCTS;    /* Also called CRTSCTS

將它設(shè)置成為無效的方法與此類似:

options.c_cflag &= ~CNEW_RTSCTS;

本地設(shè)置 ?

本地模式成員變量c_lflag可以控制串口驅(qū)動(dòng)怎樣控制輸入字符。通常,你可能需要通過c_lflag成員來設(shè)置經(jīng)典輸入和原始輸入模式。

成員變量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

選擇經(jīng)典輸入 ?

經(jīng)典輸入是以面向行設(shè)計(jì)的。在經(jīng)典輸入模式中輸入字符會(huì)被放入一個(gè)緩沖之中,這樣可以以與用戶交互的方式編輯緩沖的內(nèi)容,直到收到CR(carriage return)或者LF(line feed)字符。

選擇使用經(jīng)典輸入模式的時(shí)候,你通常需要選擇ICANON,ECHO和ECHOE選項(xiàng):

options.c_lflag |= (ICANON | ECHO | ECHOE);

選擇原始輸入 ?

原始輸入根本不會(huì)被處理。輸入字符只是被原封不動(dòng)的接收。一般情況中,如果要使用原始輸入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG選項(xiàng):

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

輸入選項(xiàng) ?

可以通過輸入模式成員c_iflag來控制從端口上收到的字符的輸入過程。與c_cflag一樣,c_iflag的最終值是想要使用的所有狀態(tài)的位運(yùn)算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

設(shè)置輸入奇偶校驗(yàn)選項(xiàng) ?

當(dāng)程序在c_cflag中設(shè)置了奇偶校驗(yàn)成員(PARENB)的時(shí)候,程序就需要將輸入奇偶校驗(yàn)設(shè)置成為有效。與奇偶校驗(yàn)相關(guān)的常量有 INPCK,IGNPAR,PARMRK和ISTRIP。一般情況下,你可能需要選擇INPCK和ISTRIP將奇偶校驗(yàn)設(shè)置為有效同時(shí)從接收字串中脫去奇偶校驗(yàn)位:

options.c_iflag |= (INPCK | ISTRIP);

IGNPAR是一個(gè)比較危險(xiǎn)選項(xiàng),即便有錯(cuò)誤發(fā)生時(shí),它也會(huì)告訴串口驅(qū)動(dòng)直接忽略奇偶校驗(yàn)錯(cuò)誤給數(shù)據(jù)放行。這個(gè)選項(xiàng)在測(cè)試鏈接的通訊質(zhì)量時(shí)比較有用而通常不會(huì)被用在實(shí)際程序中。

PARMRK會(huì)導(dǎo)致奇偶校驗(yàn)錯(cuò)誤被標(biāo)志成特殊字符加入到輸入流之中。如果IGNPAR選項(xiàng)也是有效的,那么一個(gè)NUL(八進(jìn)制000)字符會(huì)被加入到發(fā)生奇偶校驗(yàn)錯(cuò)誤的字符前面。否則,DEL(八進(jìn)制177)和NUL字符會(huì)和出錯(cuò)的字符一起送出。

設(shè)置軟件流控制 ?

軟件流控制可以通過IXON,IXOFF和IXANY常量設(shè)置成有效:

options.c_iflag |= (IXON | IXOFF | IXANY);

將其設(shè)置為無效的時(shí)候,很簡(jiǎn)單,只需要對(duì)這些位取反:

options.c_iflag &= ~(IXON | IXOFF | IXANY);

XON(start data)和XOFF(stop data)字符卻是在c_cc數(shù)組中定義的,下面會(huì)詳細(xì)描述這個(gè)數(shù)組。

輸出選項(xiàng) ?

成員變量c_oflag之中包括了輸出過濾選項(xiàng)。和輸入模式相似,程序可以選擇使用經(jīng)過加工的或者原始的數(shù)據(jù)輸出。

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成員變量中設(shè)置OPOST選項(xiàng)的方法程序可以選擇加工過的輸入。

options.c_oflag |= OPOST;

在所有選項(xiàng)當(dāng)中,你可能只需要使用ONLCR選項(xiàng)來將行分隔符映射到CR-LF組合對(duì)上。其他選項(xiàng)主要是歷史遺留,僅僅與行打印機(jī)和終端跟不上串行數(shù)據(jù)的年代有關(guān)。

選擇原始輸出 ?

原始輸出方式可以通過在c_oflag中重置OPOST選項(xiàng)來選擇:

options.c_oflag &= ~OPOST;

如果OPOST選項(xiàng)被設(shè)置成無效的話,其他c_oflag中的選項(xiàng)都會(huì)失效。

控制字符 ?

字符數(shù)組c_cc里面包括了控制字符的定義和超時(shí)參數(shù)。這個(gè)數(shù)組的每個(gè)元素都是以常量定義的。

成員變量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) -

設(shè)置軟件流控制字符 ?

用來做軟件流控制的字符包含在數(shù)組c_cc的VSTART和VSTOP元素里面。通常情況下,它們應(yīng)該被設(shè)置成DC1(八進(jìn)制021)和DC3(八進(jìn)制023),它們?cè)贏SCII標(biāo)準(zhǔn)中代表著XON和XOFF字符。

設(shè)置讀取超時(shí) ?

UNIX串口驅(qū)動(dòng)提供了設(shè)置字符和包超時(shí)的能力。數(shù)組c_cc中有兩個(gè)元素可以用來設(shè)置超時(shí):VMIN和VTIME。在經(jīng)典輸入模式或者通過open(2)和fcntl(2)函數(shù)傳遞NDELAY選項(xiàng)時(shí),超時(shí)設(shè)置會(huì)被忽略。

VMIN可以指定讀取的最小字符數(shù)。如果它被設(shè)置為0,那么VTIME值則會(huì)指定每個(gè)字符讀取的等待時(shí)間。

如果VMIN不為零,VTIME會(huì)指定等待第一個(gè)字符讀取操作的時(shí)間。如果在這個(gè)指定時(shí)間中可以開始讀取某個(gè)字符,直到VMIN個(gè)數(shù)的所有字符全部被讀取,其他讀取操作將會(huì)被阻塞(等待)。也就是說,一旦讀取第一個(gè)字符,串口驅(qū)動(dòng)的預(yù)期就是接收到整個(gè)字符包(一共VMIN字節(jié))。如果在允許的時(shí)間內(nèi)沒有字符被讀取,那么read(2)調(diào)用就會(huì)返回0。通過這個(gè)方法可以確切得告訴串口驅(qū)動(dòng)程序需要讀取N個(gè)字節(jié),而且read(2)調(diào)用只會(huì)返回N或者 0。然而,超時(shí)設(shè)置只對(duì)第一個(gè)字符的讀取操作有效,所以,如果因?yàn)槟承┰蝌?qū)動(dòng)程序在N字節(jié)的包中丟失某個(gè)字符的話,read(2)調(diào)用將會(huì)一直等下去。

VTIME可以以十分之一秒為單位指定等待字符輸入的時(shí)間。如果VTIME設(shè)置為0(默認(rèn)情況),除非open(2)或者fcntl(2)函數(shù)設(shè)置了NDELAY選項(xiàng),否則read(2)將會(huì)永久得阻塞(等待)。

調(diào)制解調(diào)器通訊 ?

說到串口通訊就不得不提一下通過調(diào)劑解調(diào)器通訊的方式。這里給出的程序例子都適用于支持“事實(shí)上的”標(biāo)準(zhǔn)AT命令集的調(diào)制解調(diào)器。

什么是調(diào)制解調(diào)器 ?

調(diào)制解調(diào)器是一種可以將數(shù)字信號(hào)的串行數(shù)據(jù)轉(zhuǎn)化為模擬信號(hào)頻率的設(shè)備。通過這種轉(zhuǎn)換,信息就可以通過像電話線或者有線電視線纜那樣的模擬數(shù)據(jù)鏈路來傳輸了??谡Z中,經(jīng)常將調(diào)制解調(diào)器稱作“貓”。標(biāo)準(zhǔn)的電話調(diào)制解調(diào)器可以將串行數(shù)據(jù)轉(zhuǎn)化為能夠通過電話線傳輸?shù)囊纛l;因?yàn)檫@種轉(zhuǎn)化非常之快又非常復(fù)雜,所以如果你去聽一下的話,這些音頻很像是大聲尖叫時(shí)發(fā)出來的聲音。

今天可以見到的調(diào)制解調(diào)器可以通過電話線每秒傳輸53000比特——5.3Kbps——的數(shù)據(jù)。還有就是,大多數(shù)調(diào)制解調(diào)器都使用數(shù)據(jù)壓縮技術(shù),這樣就可以將某些類型數(shù)據(jù)的傳輸比特率提高到100kbps。

與調(diào)制解調(diào)器通訊 ?

于調(diào)制解調(diào)器通訊的第一步就是要以原始輸入模式打開和配置串口。

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

接下來就需要和調(diào)制解調(diào)器建立通訊連接。最好的辦法就是給調(diào)制解調(diào)器發(fā)送“AT”命令。這也會(huì)讓比較只能的調(diào)制解調(diào)器探測(cè)到你正在使用的波特率。如果正確地連接到調(diào)制解調(diào)器上,并且調(diào)制解調(diào)器開啟電源,它會(huì)返回一個(gè)回應(yīng)信號(hào)“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);
}

標(biāo)準(zhǔn)調(diào)制解調(diào)器命令 ?

大多數(shù)調(diào)制解調(diào)器都支持“AT”命令集。之所以這樣叫是因?yàn)檫@個(gè)命令集中的每個(gè)命令都是以“AT”字符開頭。每個(gè)命令都是以第一列的AT開頭字符后面跟上特殊命令參數(shù)和一個(gè)回車符CR(八進(jìn)制015)。調(diào)制解調(diào)器處理完這條命令之后會(huì)根據(jù)命令回復(fù)一些文本消息。

  • ATD 撥號(hào) [#b39592a6]

通過ATD命令可以撥打一個(gè)指定號(hào)碼。除過號(hào)碼和分隔符(-)以外,你還可以指定以音頻("T")或者脈沖("P")方式撥號(hào),暫停一秒(",")和等待撥號(hào)音("W"):

ATDT 555-1212
ATDT 18008008008W1234,1,1234
ATD T555-1212WP1234

調(diào)制解調(diào)器可能回復(fù)下面列出的某個(gè)消息:

NO DIALTONE
BUSY
NO CARRIER
CONNECT
CONNECT baud
  • ATH 掛斷

通過ATH命令可以讓調(diào)制解調(diào)器掛斷。因?yàn)?,調(diào)制解調(diào)器如果在“命令”模式的話,你可能就不能打普通電話了。

如果DTR信號(hào)線掉了的話,大部分調(diào)制解調(diào)器也會(huì)掛斷。你可以將波特率設(shè)置成0并且持續(xù)至少1秒來做到這一點(diǎn)。再次讓DTR掉落同樣也可以把調(diào)制解調(diào)器重新拉回命令模式。

調(diào)制解調(diào)器成功掛斷以后,它會(huì)回復(fù)一個(gè)"NO CARRIER"回來。如果調(diào)制解調(diào)器仍然保持連接,它則會(huì)發(fā)送"CONNECT"或者"CONNECT baud"這樣的消息。

  • ATZ 重置調(diào)制解調(diào)器

通過ATZ命令可以重置調(diào)制解調(diào)器。重置之后它會(huì)回復(fù)字符串"OK"。

  • 與調(diào)制解調(diào)器通訊的常見問題

首先,也是最重要的一點(diǎn),千萬不要使用回聲輸入(input echoing)。回聲輸入會(huì)導(dǎo)致調(diào)制解調(diào)器和計(jì)算機(jī)之間產(chǎn)生反饋循環(huán)。

其次,當(dāng)發(fā)送調(diào)制解調(diào)器命令時(shí),命令必須以回車(CR)而不是換行(NL)結(jié)束。C語言中回車的字符常量是"\r"。

最后,處理調(diào)制解調(diào)器通訊的時(shí)候,要一定保證你使用了調(diào)制解調(diào)器支持的波特率。雖然大多數(shù)調(diào)制解調(diào)器都支持自動(dòng)探測(cè)波特率,但你也會(huì)注意到某些(通常是19.2kbps或者比較老的調(diào)制解調(diào)器)有局限性。

高級(jí)串口編程 ?

所謂高級(jí)串口編程其實(shí)說的就是使用更直接的底層的ioctl(2)和select(2)系統(tǒng)調(diào)用來操作串口。

串口的ioctl ?

前文中曾經(jīng)提到使用tcgetattr和tcsetattr函數(shù)來配置串口。UNIX環(huán)境下,這些函數(shù)都是使用ioctl(2)系統(tǒng)調(diào)用來實(shí)現(xiàn)的。

系統(tǒng)調(diào)用ioctl可以帶三個(gè)參數(shù):

int ioctl(int fd, int request, ...); 

顯然,fd參數(shù)對(duì)于串口編程來說就是串口設(shè)備文件的文件描述符咯。而request參數(shù)是在<termios.h>頭文件中定義的常量,而且一般不會(huì)超出下表所列的范圍。

串口的IOCTL請(qǐng)求

REQUEST 描述 POSIX函數(shù)
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

取得控制信號(hào) ?

TIOCMGET ioctl可以取得當(dāng)前調(diào)制解調(diào)器的狀態(tài)位。這個(gè)狀態(tài)位囊括了除去RXDTXD信號(hào)線的所有RS-232信號(hào),這些都在下表中列出。

控制信號(hào)常量

常量 描述
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)

例如下面這個(gè)程序片段,你可以通過給ioctl帶一個(gè)用來保存狀態(tài)位的整形變量的指針來取得狀態(tài)位。

#include <unistd.h>
#include <termios.h>

int fd;
int status;

ioctl(fd, TIOCMGET, &status);

設(shè)置控制信號(hào) ?

TIOCMSET ioctl可以設(shè)置上面定義的調(diào)制解調(diào)器狀態(tài)位。下面的例子展示如何使用它來將DTR信號(hào)線設(shè)成掉線狀態(tài)。

#include <unistd.h>
#include <termios.h>

int fd;
int status;

ioctl(fd, TIOCMGET, &status);
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);

可能被設(shè)置的狀態(tài)位取決于操作系統(tǒng),驅(qū)動(dòng)和正在使用的模式。關(guān)于更詳細(xì)的信息應(yīng)該去看以下你所使用的操作系統(tǒng)的文檔。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多