Java串行端口技術(shù)協(xié)議
發(fā)表時(shí)間:2024-01-15 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]串行通訊慨述 串行通訊協(xié)議有很多種,像RS232,RS485,RS422,甚至現(xiàn)今流行的USB等都是串行通訊協(xié)議。而串行通訊技術(shù)的應(yīng)用無(wú)處不在?赡艽蠹乙(jiàn)的最多就是電腦的串口與Modem的通訊。記得在PC機(jī)剛開(kāi)始在中國(guó)流行起來(lái)時(shí)(大約是在90年代前五年),那時(shí)甚至有人用一條串行線進(jìn)行兩臺(tái)電腦之...
串行通訊慨述
串行通訊協(xié)議有很多種,像RS232,RS485,RS422,甚至現(xiàn)今流行的USB等都是串行通訊協(xié)議。而串行通訊技術(shù)的應(yīng)用無(wú)處不在?赡艽蠹乙(jiàn)的最多就是電腦的串口與Modem的通訊。記得在PC機(jī)剛開(kāi)始在中國(guó)流行起來(lái)時(shí)(大約是在90年代前五年),那時(shí)甚至有人用一條串行線進(jìn)行兩臺(tái)電腦之間的數(shù)據(jù)共享。除了這些,手機(jī),PDA,USB鼠標(biāo)、鍵盤等等都是以串行通訊的方式與電腦連接。而筆者工作性質(zhì)的關(guān)系,所接觸到的就更多了,像多串口卡,各種種類的具有串口通訊接口的檢測(cè)與測(cè)量?jī)x器,串口通訊的網(wǎng)絡(luò)設(shè)備等。
雖然串行通訊有很多種,但筆者所知的在整個(gè)電子通訊產(chǎn)品方面,以RS232的通訊方式最為多見(jiàn)。雖然USB接口的電子產(chǎn)品也是層出不窮,但了解一下Java在串行通訊方面的技術(shù)還有有必要的,說(shuō)不定有哪位讀者還想用此技術(shù)寫一個(gè)PDA與電腦之間數(shù)據(jù)共享的程序呢。
本文主要以RS232為主來(lái)講解Java的串行通訊技術(shù)。
RS232通訊基礎(chǔ)
RS-232-C(又稱EIA RS-232-C,以下簡(jiǎn)稱RS232)是在1970年由美國(guó)電子工業(yè)協(xié)會(huì)(EIA)聯(lián)合貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計(jì)算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn)。RS232是一個(gè)全雙工的通訊協(xié)議,它可以同時(shí)進(jìn)行數(shù)據(jù)接收和發(fā)送的工作。RS232的端口通常有兩種:9針(DB9)和25針(DB25)。
DB9和DB25的常用針腳定義
常見(jiàn)的邊線方式
常見(jiàn)的通訊方式是三線式,這種方式是將兩個(gè)RS232設(shè)備的發(fā)送端(TXD)和接收端(RXD)及接地端(GND)互相連接,也是許多讀者所知道的連接方式:
這種方式分別將兩端的RS232接口的2--3,3---2,5(7)---5(7)針腳連接起來(lái)。其中2是數(shù)據(jù)接收線(RXD),3是數(shù)據(jù)發(fā)送線(TXD),5(7)是接地(RND)。如果有一臺(tái)式PC,和一部NoteBook電腦,就可以用這種方式連線了。用三線式可以將大多數(shù)的RS232設(shè)備連接起來(lái)。但如果你認(rèn)死了2--3,3--2,5(7)--5(7)對(duì)接這個(gè)理,會(huì)發(fā)現(xiàn)在連某些RS232設(shè)備時(shí)并不奏效。這是因?yàn)橛行┰O(shè)備在電路內(nèi)部已將2和3線調(diào)換過(guò)來(lái)了,你只要2,3,5(7)針一一對(duì)應(yīng)就行了。
小技巧:如何辨別TXD和RXD端口?
搞電子的人手邊應(yīng)該常備一個(gè)電表,用來(lái)測(cè)測(cè)電壓,電阻什么的會(huì)很有用。你只要分別測(cè)一下RS232端口的2--5或3--5針腳之間的電壓,通常TXD針腳與GND之間會(huì)有3~15V左右的負(fù)電壓,表示它是TXD針腳。
安裝Java Communications API
Sun的J2SE中并沒(méi)有直接提供以上提到的任何一種串行通訊協(xié)議的開(kāi)發(fā)包,而是以獨(dú)立的jar包形式發(fā)布在java.sun.com網(wǎng)站上(從這里下載)----即comm.jar,稱之為Javatm Communications API,它是J2SE的標(biāo)準(zhǔn)擴(kuò)展。comm.jar并不是最近才有,早在1998年時(shí),sun就已經(jīng)發(fā)布了這個(gè)開(kāi)發(fā)包。comm.jar分別提供了對(duì)常用的RS232串行端口和IEEE1284并行端口通訊的支持。目前sun發(fā)布的comm.jar只有Windows和Solaris平臺(tái)兩個(gè)版本,如果你需要Linux平臺(tái)下的,可以在http://www.geeksville.com/~kevinh/linuxcomm.html找到。
在使用comm.jar之前,必須知道如何安裝它。這也是困擾許多初學(xué)java RS232通訊者的一個(gè)難題。如果我們電腦上安裝了JDK,它將同時(shí)為我們安裝一份JRE(Java Runtime Entironment),通常我們運(yùn)行程序時(shí)都是以JRE來(lái)運(yùn)行的。所以以下的安裝適用于JRE。如果你是用JDK來(lái)運(yùn)行程序的,請(qǐng)將相應(yīng)的改成。
下載了comm.jar開(kāi)發(fā)包后,與之一起的還有兩個(gè)重要的文件,win32com.dll和javax.comm.properties。comm.jar提供了通訊用的java API,而win32com.dll提供了供comm.jar調(diào)用的本地驅(qū)動(dòng)接口。而javax.comm.properties是這個(gè)驅(qū)動(dòng)的類配置文件。首先將comm.jar復(fù)制到libext目錄。再將win21com.dll復(fù)制到你的RS232應(yīng)用程序運(yùn)行的目錄,即user.dir。然后將javax.comm.properties復(fù)制到lib目錄。
通訊前的準(zhǔn)備
如果你手頭上沒(méi)有現(xiàn)成的提供了標(biāo)準(zhǔn)RS232串口的設(shè)備,你可以將自己的電腦模擬成兩臺(tái)不同的串口設(shè)備。通常電腦主機(jī)后面的面板提供了兩個(gè)9針的串口,請(qǐng)將這兩個(gè)串口的2,3,5腳按前面介紹的方法連接。電子市場(chǎng)都有現(xiàn)成的連接頭賣,請(qǐng)不要買那種封裝的嚴(yán)嚴(yán)實(shí)實(shí)的接頭,而要買用螺絲封裝可以拆開(kāi)的連接頭,這樣可以方便自己根據(jù)需要連接各個(gè)針腳。
Comm API基礎(chǔ)
我無(wú)意于在此詳細(xì)描述Comm API每個(gè)類和接口的用法,但我會(huì)介紹Comm API的類結(jié)構(gòu)和幾個(gè)重要的API用法。
所有的comm API位于javax.comm包下面。從Comm API的javadoc來(lái)看,它介紹給我們的只有區(qū)區(qū)以下13個(gè)類或接口:
javax.comm.CommDriver
javax.comm.CommPort
javax.comm.ParallelPort
javax.comm.SerialPort
javax.comm.CommPortIdentifier
javax.comm.CommPortOwnershipListener
javax.comm.ParallelPortEvent
javax.comm.SerialPortEvent
javax.comm.ParallelPortEventListener (extends java.util.EventListener)
javax.comm.SerialPortEventListener (extends java.util.EventListener)
javax.comm.NoSuchPortException
javax.comm.PortInUseException
javax.comm.UnsupportedCommOperationException
下面講解一下幾個(gè)主要類或接口。
1.枚舉出系統(tǒng)所有的RS232端口
在開(kāi)始使用RS232端口通訊之前,我們想知道系統(tǒng)有哪些端口是可用的,以下代碼列出系統(tǒng)中所有可用的RS232端口:
Enumeration en = CommPortIdentifier.getPortIdentifiers();
CommPortIdentifier portId;
while (en.hasMoreElements())
{
portId = (CommPortIdentifier) en.nextElement();
/*如果端口類型是串口,則打印出其端口信息*/
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL)
{
System.out.println(portId.getName());
}
}
在我的電腦上以上程序輸出以下結(jié)果:
COM1
COM2
CommPortIdentifier類的getPortIdentifiers方法可以找到系統(tǒng)所有的串口,每個(gè)串口對(duì)應(yīng)一個(gè)CommPortIdentifier類的實(shí)例。
2.打開(kāi)端口
如果你使用端口,必須先打開(kāi)它。
try{
CommPort serialPort = portId.open("My App", 60);
/*從端口中讀取數(shù)據(jù)*/
InputStream input = serialPort.getInputStream();
input.read(...);
/*往端口中寫數(shù)據(jù)*/
OutputStream output = serialPort.getOutputStream();
output.write(...)
...
}catch(PortInUseException ex)
{ ... }
通過(guò)CommPortIdentifier的open方法可以返回一個(gè)CommPort對(duì)象。open方法有兩個(gè)參數(shù),第一個(gè)是String,通常設(shè)置為你的應(yīng)用程序的名字。第二個(gè)參數(shù)是時(shí)間,即開(kāi)啟端口超時(shí)的毫秒數(shù)。當(dāng)端口被另外的應(yīng)用程序占用時(shí),將拋出PortInUseException異常。
在這里CommPortIdentifier類和CommPort類有什么區(qū)別呢?其實(shí)它們兩者是一一對(duì)應(yīng)的關(guān)系。CommPortIdentifier主要負(fù)責(zé)端口的初始化和開(kāi)啟,以及管理它們的占有權(quán)。而CommPort則是跟實(shí)際的輸入和輸出功能有關(guān)的。通過(guò)CommPort的getInputStream()可以取得端口的輸入流,它是java.io.InputStream接口的一個(gè)實(shí)例。我們可以用標(biāo)準(zhǔn)的InputStream的操作接口來(lái)讀取流中的數(shù)據(jù),就像通過(guò)FileInputSteam讀取文件的內(nèi)容一樣。相應(yīng)的,CommPort的getOutputStream可以獲得端口的輸出流,這樣就可以往串口輸出數(shù)據(jù)了。
3.關(guān)閉端口
使用完的端口,必須記得將其關(guān)閉,這樣可以讓其它的程序有機(jī)會(huì)使用它,不然其它程序使用該端口時(shí)可能會(huì)拋出端口正在使用中的錯(cuò)誤。很奇怪的是,CommPortIdentifier類只提供了開(kāi)啟端口的方法,而要關(guān)閉端口,則要調(diào)用CommPort類的close()方法。
CommPort的輸入流的讀取方式與文件的輸入流有些不一樣,那就是你可能永遠(yuǎn)不知這個(gè)InputStream何時(shí)結(jié)束,除非對(duì)方的OutputStream向你發(fā)送了一個(gè)特定數(shù)據(jù)表示發(fā)送結(jié)束,你收到這個(gè)特定字符后,再行關(guān)閉你的InputStream。而comm.jar提供了兩種靈活的方式讓你讀取數(shù)據(jù)。
1.輪詢方式(Polling)
舉個(gè)例子,你同GF相約一起出門去看電影,但你的GF好打扮,這一打扮可能就是半小時(shí)甚至一小時(shí)以上。這時(shí)你就耐不住了,每?jī)煞昼娋痛邌?wèn)一次“好了沒(méi)?”,如此這樣,直到你的GF說(shuō)OK了才算完。這個(gè)就叫輪詢(Polling)。
在程序中,輪詢通常設(shè)計(jì)成一個(gè)封閉的循環(huán),當(dāng)滿足某個(gè)條件時(shí)即結(jié)束循環(huán)。剛才那個(gè)例子中,你的GF說(shuō)“OK了!”,這個(gè)就是結(jié)束你輪詢的條件。在單線程的程序中,當(dāng)循環(huán)一直執(zhí)行某項(xiàng)任務(wù)而又無(wú)法預(yù)知它何時(shí)結(jié)束時(shí),此時(shí)你的程序看起來(lái)可能就像死機(jī)一樣。在VB程序中,這個(gè)問(wèn)題可以用在循環(huán)結(jié)構(gòu)中插入一個(gè)doEvent語(yǔ)句來(lái)解決。而Java中,最好的方式是使用線程,就像以下代碼片斷一樣。
public TestPort extend Thread
{
...
InputStream input = serialPort.getInputStream();
StringBuffer buf = new StringBuffer();
boolean stopped = false;
...
public void run()
{
try {
while( !stopped )
int ch = input.read();
if ( ch=='q' ch=='Q' )
{
/*結(jié)束讀取,關(guān)閉端口...*/
stopped = true;
...
}
else
{
buf.append((char)ch);
...
}
}catch (InterruptedException e) { }
}
}
2.監(jiān)聽(tīng)方式(listening)
Comm API支持標(biāo)準(zhǔn)的Java Bean型的事件模型。也就是說(shuō),你可以使用類似AddXXXListener這樣的方法為一個(gè)串口注冊(cè)自己的監(jiān)聽(tīng)器,以監(jiān)聽(tīng)方式進(jìn)行數(shù)據(jù)讀取。
如要對(duì)端口監(jiān)聽(tīng),你必須先取得CommPortIdentifier類的一個(gè)實(shí)例,
CommPort serialPort = portId.open("My App", 60);
從而取得SerialPort,再調(diào)用它的addEventListener方法為它添加監(jiān)聽(tīng)器,
serialPort.addEventListener(new MyPortListener());
SerialPort的監(jiān)聽(tīng)器必須繼承于SerialPortEventListener接口。當(dāng)有任何SerialPort的事件發(fā)生時(shí),將自動(dòng)調(diào)用監(jiān)聽(tīng)器中的serialEvent方法。Serial Event有以下幾種類型:
BI -通訊中斷.
CD -載波檢測(cè).
CTS -清除發(fā)送.
DATA_AVAILABLE -有數(shù)據(jù)到達(dá).
DSR -數(shù)據(jù)設(shè)備準(zhǔn)備好.
FE -幀錯(cuò)誤.
OE -溢位錯(cuò)誤.
OUTPUT_BUFFER_EMPTY -輸出緩沖區(qū)已清空.
PE -奇偶校驗(yàn)錯(cuò).
RI - 振鈴指示.
下面是一個(gè)監(jiān)聽(tīng)器的示例:
public void MyPortListener implements SerialPortEventListener
{
public void serialEvent(SerialPortEvent evt)
{
switch (evt.getEventType())
{
case SerialPortEvent.CTS :
System.out.println("CTS event occured.");
break;
case SerialPortEvent.CD :
System.out.println("CD event occured.");
break;
case SerialPortEvent.BI :
System.out.println("BI event occured.");
break;
case SerialPortEvent.DSR :
System.out.println("DSR event occured.");
break;
case SerialPortEvent.FE :
System.out.println("FE event occured.");
break;
case SerialPortEvent.OE :
System.out.println("OE event occured.");
break;
case SerialPortEvent.PE :
System.out.println("PE event occured.");
break;
case SerialPortEvent.RI :
System.out.println("RI event occured.");
break;
case SerialPortEvent.OUTPUT_BUFFER_EMPTY :
System.out.println("OUTPUT_BUFFER_EMPTY event occured.");
break;
case SerialPortEvent.DATA_AVAILABLE :
System.out.println("DATA_AVAILABLE event occured.");
int ch;
StringBuffer buf = new StringBuffer();
InputStream input = serialPort.getInputStream
try {
while ( (ch=input.read()) > 0) {
buf.append((char)ch);
}
System.out.print(buf);
} catch (IOException e) {}
break;
}
}
這個(gè)監(jiān)聽(tīng)器只是簡(jiǎn)單打印每個(gè)發(fā)生的事件名稱。而對(duì)于大多數(shù)應(yīng)用程序來(lái)說(shuō),通常關(guān)心是DATA_AVAILABLE事件,當(dāng)數(shù)據(jù)從外部設(shè)備傳送到端口上來(lái)時(shí)將觸發(fā)此事件。此時(shí)就可以使用前面提到過(guò)的方法,serialPort.getInputStream()來(lái)從InputStream中讀取數(shù)據(jù)了。