Java串口通信詳解_第1頁
Java串口通信詳解_第2頁
Java串口通信詳解_第3頁
Java串口通信詳解_第4頁
Java串口通信詳解_第5頁
已閱讀5頁,還剩29頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

Java串口通信詳解

序言說到開源,恐怕很少有人不挑大指稱贊。學(xué)生通過開源代碼學(xué)到了知識,程序員通過開源類庫獲得了別人的成功經(jīng)驗及能夠按時完成手頭的工程,商家通過開源軟件賺到了錢……,總之是皆大歡喜。然而開源軟件或類庫的首要缺點(diǎn)就是大多缺乏詳細(xì)的說明文檔和使用的例子,或者就是軟件代碼隨便你用,就是文檔,例子和后期服務(wù)收錢。這也難怪,畢竟就像某個著名NBA球員說的那樣:“我還要養(yǎng)家,所以千萬美元以下的合同別找我談,否則我寧可待業(yè)”。是啊,支持開源的人也要養(yǎng)家,收點(diǎn)錢也不過分。要想既不花錢又學(xué)到知識就只能借助網(wǎng)絡(luò)和了,我只是想拋磚引玉,為開源事業(yè)做出點(diǎn)微薄共獻(xiàn),能為你的工程解決哪怕一個小問題,也就足夠了。雖然我的這個系列介紹的東西不是什么Web框架,也不是什么開源服務(wù)器,但是我相信,作為一個程序員,什么樣的問題都會遇到。有時候越是簡單的問題反而越棘手;越是小的地方就越是找不到稱手的家伙。只要你不是整天只與“架構(gòu)”、“構(gòu)件”、“框架”打交道的話,相信我所說的東西你一定會用到。

1

串口通信簡介1.1

常見的Java串口包1.2

串口包的安裝(Windows下)2

串口API概覽2.1

m.CommPort2.2

m.CommPortIdentifier2.3

m.SerialPort2.4

串口API實例2.4.1

列舉出本機(jī)所有可用串口2.4.2

串口參數(shù)的配置2.4.3

串口的讀寫3

串口通信的通用模式及其問題3.1

事件監(jiān)聽模型3.2

串口讀數(shù)據(jù)的線程模型3.3

第三種方法4

結(jié)束語

1

串口通信簡介嵌入式系統(tǒng)或傳感器網(wǎng)絡(luò)的很多應(yīng)用和測試都需要通過PC機(jī)與嵌入式設(shè)備或傳感器節(jié)點(diǎn)進(jìn)行通信。其中,最常用的接口就是RS-232串口和并口(鑒于USB接口的復(fù)雜性以及不需要很大的數(shù)據(jù)傳輸量,USB接口用在這里還是顯得過于奢侈,況且目前除了SUN有一個支持USB的包之外,我還沒有看到其他直接支持USB的Java類庫)。SUN的CommAPI分別提供了對常用的RS232串行端口和IEEE1284并行端口通訊的支持。RS-232-C(又稱EIARS-232-C,以下簡稱RS232)是在1970年由美國電子工業(yè)協(xié)會(EIA)聯(lián)合貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn)。RS232是一個全雙工的通訊協(xié)議,它可以同時進(jìn)行數(shù)據(jù)接收和發(fā)送的工作。1.1

常見的Java串口包目前,常見的Java串口包有SUN在1998年發(fā)布的串口通信API:comm2.0.jar(Windows下)、comm3.0.jar(Linux/Solaris);IBM的串口通信API以及一個開源的實現(xiàn)。鑒于在Windows下SUN的API比較常用以及IBM的實現(xiàn)和SUN的在API層面都是一樣的,那個開源的實現(xiàn)又不像兩家大廠的產(chǎn)品那樣讓人放心,這里就只介紹SUN的串口通信API在Windows平臺下的使用。1.2

串口包的安裝(Windows下)到SUN的網(wǎng)站下載javacomm20-win32.zip,包含的東西如下所示:按照其使用說明(Readme.html)的說法,要想使用串口包進(jìn)行串口通信,除了設(shè)置好環(huán)境變量之外,還要將win32com.dll復(fù)制到<JDK>\bin目錄下;將comm.jar復(fù)制到<JDK>\lib;把perties也同樣拷貝到<JDK>\lib目錄下。然而在真正運(yùn)行使用串口包的時候,僅作這些是不夠的。因為通常當(dāng)運(yùn)行“javaMyApp”的時候,是由JRE下的虛擬機(jī)啟動MyApp的。而我們只復(fù)制上述文件到JDK相應(yīng)目錄下,所以應(yīng)用程序?qū)崾菊也坏酱凇=鉀Q這個問題的方法很簡單,我們只須將上面提到的文件放到JRE相應(yīng)的目錄下就可以了。值得注意的是,在網(wǎng)絡(luò)應(yīng)用程序中使用串口API的時候,還會遇到其他更復(fù)雜問題。有興趣的話,你可以查看CSDN社區(qū)中“關(guān)于網(wǎng)頁上Applet用javacomm20讀取客戶端串口的問題”的帖子。2

串口API概覽2.1

m.CommPort這是用于描述一個被底層系統(tǒng)支持的端口的抽象類。它包含一些高層的IO控制方法,這些方法對于所有不同的通訊端口來說是通用的。SerialPort和ParallelPort都是它的子類,前者用于控制串行端口而后者用于控這并口,二者對于各自底層的物理端口都有不同的控制方法。這里我們只關(guān)心SerialPort。2.2

m.CommPortIdentifier這個類主要用于對串口進(jìn)行管理和設(shè)置,是對串口進(jìn)行訪問控制的核心類。主要包括以下方法l

確定是否有可用的通信端口l

為IO操作打開通信端口l

決定端口的所有權(quán)l(xiāng)

處理端口所有權(quán)的爭用l

管理端口所有權(quán)變化引發(fā)的事件(Event)2.3

m.SerialPort這個類用于描述一個RS-232串行通信端口的底層接口,它定義了串口通信所需的最小功能集。通過它,用戶可以直接對串口進(jìn)行讀、寫及設(shè)置工作。2.4

串口API實例大段的文字怎么也不如一個小例子來的清晰,下面我們就一起看一下串口包自帶的例子---SerialDemo中的一小段代碼來加深對串口API核心類的使用方法的認(rèn)識。2.4.1

列舉出本機(jī)所有可用串口voidlistPortChoices(){

CommPortIdentifierportId;

Enumerationen=CommPortIdentifier.getPortIdentifiers();

//iteratethroughtheports.

while(en.hasMoreElements()){

portId=(CommPortIdentifier)en.nextElement();

if(portId.getPortType()==CommPortIdentifier.PORT_SERIAL){

System.out.println(portId.getName());

}

}

portChoice.select(parameters.getPortName());

}以上代碼可以列舉出當(dāng)前系統(tǒng)所有可用的串口名稱,我的機(jī)器上輸出的結(jié)果是COM1和COM3。2.4.2

串口參數(shù)的配置串口一般有如下參數(shù)可以在該串口打開以前配置進(jìn)行配置:包括波特率,輸入/輸出流控制,數(shù)據(jù)位數(shù),停止位和齊偶校驗。SerialPortsPort;try{

sPort.setSerialPortParams(BaudRate,Databits,Stopbits,Parity);

//設(shè)置輸入/輸出控制流

sPort.setFlowControlMode(FlowControlIn|FlowControlOut);

}catch(UnsupportedCommOperationExceptione){}2.4.3

串口的讀寫對串口讀寫之前需要先打開一個串口:CommPortIdentifierportId=CommPortIdentifier.getPortIdentifier(PortName);try{

SerialPort

sPort=(SerialPort)portId.open("串口所有者名稱",超時等待時間);

}catch(PortInUseExceptione){//如果端口被占用就拋出這個異常

thrownewSerialConnectionException(e.getMessage());

}//用于對串口寫數(shù)據(jù)OutputStreamos=newBufferedOutputStream(sPort.getOutputStream());os.write(intdata);//用于從串口讀數(shù)據(jù)InputStreamis=newBufferedInputStream(sPort.getInputStream());intreceivedData=is.read();讀出來的是int型,你可以把它轉(zhuǎn)換成需要的其他類型。這里要注意的是,由于Java語言沒有無符號類型,即所有的類型都是帶符號的,在由byte到int的時候應(yīng)該尤其注意。因為如果byte的最高位是1,則轉(zhuǎn)成int類型時將用1來占位。這樣,原本是10000000的byte類型的數(shù)變成int型就成了1111111110000000,這是很嚴(yán)重的問題,應(yīng)該注意避免。3

串口通信的通用模式及其問題終于嘮叨完我最討厭的基礎(chǔ)知識了,下面開始我們本次的重點(diǎn)--串口應(yīng)用的研究。由于向串口寫數(shù)據(jù)很簡單,所以這里我們只關(guān)注于從串口讀數(shù)據(jù)的情況。通常,串口通信應(yīng)用程序有兩種模式,一種是實現(xiàn)SerialPortEventListener接口,監(jiān)聽各種串口事件并作相應(yīng)處理;另一種就是建立一個獨(dú)立的接收線程專門負(fù)責(zé)數(shù)據(jù)的接收。由于這兩種方法在某些情況下存在很嚴(yán)重的問題(至于什么問題這里先賣個關(guān)子J),所以我的實現(xiàn)是采用第三種方法來解決這個問題。3.1

事件監(jiān)聽模型現(xiàn)在我們來看看事件監(jiān)聽模型是如何運(yùn)作的:l

首先需要在你的端口控制類(例如SManager)加上“implementsSerialPortEventListener”l

在初始化時加入如下代碼:try{

SerialPortsPort.addEventListener(SManager);

}catch(TooManyListenersExceptione){

sPort.close();

thrownewSerialConnectionException("toomanylistenersadded");

}

sPort.notifyOnDataAvailable(true);l

覆寫publicvoidserialEvent(SerialPortEvente)方法,在其中對如下事件進(jìn)行判斷:BI-通訊中斷.CD-載波檢測.CTS-清除發(fā)送.DATA_AVAILABLE-有數(shù)據(jù)到達(dá).DSR-數(shù)據(jù)設(shè)備準(zhǔn)備好.FE-幀錯誤.OE-溢位錯誤.OUTPUT_BUFFER_EMPTY-輸出緩沖區(qū)已清空.PE-奇偶校驗錯.RI-振鈴指示.一般最常用的就是DATA_AVAILABLE--串口有數(shù)據(jù)到達(dá)事件。也就是說當(dāng)串口有數(shù)據(jù)到達(dá)時,你可以在serialEvent中接收并處理所收到的數(shù)據(jù)。然而在我的實踐中,遇到了一個十分嚴(yán)重的問題。首先描述一下我的實驗:我的應(yīng)用程序需要接收傳感器節(jié)點(diǎn)從串口發(fā)回的查詢數(shù)據(jù),并將結(jié)果以圖標(biāo)的形式顯示出來。串口設(shè)定的波特率是115200,川口每隔128毫秒返回一組數(shù)據(jù)(大約是30字節(jié)左右),周期(即持續(xù)時間)為31秒。實測的時候在一個周期內(nèi)應(yīng)該返回4900多個字節(jié),而用事件監(jiān)聽模型我最多只能收到不到1500字節(jié),不知道這些字節(jié)都跑哪里去了,也不清楚到底丟失的是那部分?jǐn)?shù)據(jù)。值得注意的是,這是我將serialEvent()中所有處理代碼都注掉,只剩下打印代碼所得的結(jié)果。數(shù)據(jù)丟失的如此嚴(yán)重是我所不能忍受的,于是我決定采用其他方法。3.2

串口讀數(shù)據(jù)的線程模型這個模型顧名思義,就是將接收數(shù)據(jù)的操作寫成一個線程的形式:publicvoidstartReadingDataThread(){

ThreadreadDataProcess=newThread(newRunnable(){

publicvoidrun(){

while(newData!=-1){

try{

newData=is.read();

System.out.println(newData);

//其他的處理過程

……….

}catch(IOExceptionex){

System.err.println(ex);

return;

}

}

readDataProcess.start();}在我的應(yīng)用程序中,我將收到的數(shù)據(jù)打包放到一個緩存中,然后啟動另一個線程從緩存中獲取并處理數(shù)據(jù)。兩個線程以生產(chǎn)者—消費(fèi)者模式協(xié)同工作,數(shù)據(jù)的流向如下圖所示:

這樣,我就圓滿解決了丟數(shù)據(jù)問題。然而,沒高興多久我就又發(fā)現(xiàn)了一個同樣嚴(yán)重的問題:雖然這回不再丟數(shù)據(jù)了,可是原本一個周期(31秒)之后,傳感器節(jié)電已經(jīng)停止傳送數(shù)據(jù)了,但我的串口線程依然在努力的執(zhí)行讀串口操作,在控制臺也可以看見收到的數(shù)據(jù)仍在不斷的打印。原來,由于傳感器節(jié)點(diǎn)發(fā)送的數(shù)據(jù)過快,而我的接收線程處理不過來,所以InputStream就先把已到達(dá)卻還沒處理的字節(jié)緩存起來,于是就導(dǎo)致了明明傳感器節(jié)點(diǎn)已經(jīng)不再發(fā)數(shù)據(jù)了,而控制臺卻還能看見數(shù)據(jù)不斷打印這一奇怪的現(xiàn)象。唯一值得慶幸的是最后收到數(shù)據(jù)確實是4900左右字節(jié),沒出現(xiàn)丟失現(xiàn)象。然而當(dāng)處理完最后一個數(shù)據(jù)的時候已經(jīng)快1分半鐘了,這個時間遠(yuǎn)遠(yuǎn)大于節(jié)點(diǎn)運(yùn)行周期。這一延遲對于一個實時的顯示系統(tǒng)來說簡直是災(zāi)難!后來我想,是不是由于兩個線程之間的同步和通信導(dǎo)致了數(shù)據(jù)接收緩慢呢?于是我在接收線程的代碼中去掉了所有處理代碼,僅保留打印收到數(shù)據(jù)的語句,結(jié)果依然如故??磥聿⒉皇蔷€程間的通信阻礙了數(shù)據(jù)的接收速度,而是用線程模型導(dǎo)致了對于發(fā)送端數(shù)據(jù)發(fā)送速率過快的情況下的數(shù)據(jù)接收延遲。這里申明一點(diǎn),就是對于數(shù)據(jù)發(fā)送速率不是如此快的情況下前面者兩種模型應(yīng)該還是好用的,只是特殊情況還是應(yīng)該特殊處理。3.3

第三種方法痛苦了許久(Boss天天催我L)之后,偶然的機(jī)會,我聽說TinyOS中(又是開源的)有一部分是和我的應(yīng)用程序類似的串口通信部分,于是我下載了它的1.x版的Java代碼部分,參考了它的處理方法。解決問題的方法說穿了其實很簡單,就是從根源入手。根源不就是接收線程導(dǎo)致的嗎,那好,我就干脆取消接收線程和作為中介的共享緩存,而直接在處理線程中調(diào)用串口讀數(shù)據(jù)的方法來解決問題(什么,為什么不把處理線程也一并取消?----都取消應(yīng)用程序界面不就鎖死了嗎?所以必須保留)于是程序變成了這樣:publicbyte[]getPack(){

while(true){

//PacketLength為數(shù)據(jù)包長度

byte[]msgPack=newbyte[PacketLength];

for(inti=0;i<PacketLength;i++){

if((newData=is.read())!=-1){

msgPack[i]=(byte)newData;

System.out.println(msgPack[i]);

}

}

returnmsgPack;

}}在處理線程中調(diào)用這個方法返回所需要的數(shù)據(jù)序列并處理之,這樣不但沒有丟失數(shù)據(jù)的現(xiàn)象行出現(xiàn),也沒有數(shù)據(jù)接收延遲了。這里唯一需要注意的就是當(dāng)串口停止發(fā)送數(shù)據(jù)或沒有數(shù)據(jù)的時候is.read()一直都返回-1,如果一旦在開始接收數(shù)據(jù)的時候發(fā)現(xiàn)-1就不要理它,繼續(xù)接收,直到收到真正的數(shù)據(jù)為止。

4

結(jié)束語本文介紹了串口通信的基本知識,以及常用的幾種模式。通過實踐,提出了一些問題,并在最后加以解決。值得注意的是對于第一種方法,我曾將傳感器發(fā)送的時間由128毫秒增加到512毫秒,仍然有很嚴(yán)重的數(shù)據(jù)丟失現(xiàn)象發(fā)生,所以如果你的應(yīng)用程序需要很精密的結(jié)果,傳輸數(shù)據(jù)的速率又很快的話,就最好不要用第一種方法。對于第二種方法,由于是線程導(dǎo)致的問題,所以對于不同的機(jī)器應(yīng)該會有不同的表現(xiàn),對于那些處理多線程比較好的機(jī)器來說,應(yīng)該會好一些。但是我的機(jī)器是Inter奔四3.0雙核CPU+512DDR內(nèi)存,這樣都延遲這么厲害,還得多強(qiáng)的CPU才行???所以對于數(shù)據(jù)量比較大的傳輸來說,還是用第三種方法吧。不過這個世界問題是很多的,而且未知的問題比已知的問題多的多,說不定還有什么其他問題存在,歡迎你通過下面的聯(lián)系方式和我一起研究。

關(guān)于java使用javacomm20-win32實踐總結(jié)

由于這幾天要通過java調(diào)用通過串口或并口連接的硬件資源,所以我就要用到和底層的硬件進(jìn)行通訊。通過RS-232的通訊協(xié)議,了解電腦和外設(shè)是怎樣進(jìn)行通訊的。在應(yīng)用中我們也可以通過JNI來實現(xiàn)(詳情請見/blog/31508),這樣的話,就必須知道更多的知識。由于java已經(jīng)提供我們一個javacomm20-win32通用的API我們還是實行“拿來主義”吧。我就把整個應(yīng)用的過程詳細(xì)的說一下,希望給需要的人一點(diǎn)幫助。

我們經(jīng)過串口和外設(shè)通訊,下面我就以串口為例進(jìn)行解說。

1)我們要準(zhǔn)備相應(yīng)的設(shè)備。

電腦,外設(shè),通過數(shù)據(jù)線把他們連接起來。

2)檢驗外設(shè)到底是用的那個COM口和電腦通訊的.

也就是說,他們有沒有真確的連接上。我們可以通過下載串口通訊口測試軟件,我用的是"SuperCommTool.exe"的綠色軟件,進(jìn)行測試的。這軟件很適應(yīng),如果選中的某個COM已經(jīng)被使用了,它會給你一個相應(yīng)的提示(端口以被占用)。如果你不知道到底是使用的那個端口,那么你可以通過superCommTool軟件一個一個的試,如果正常的話,那么你可以看到有數(shù)據(jù)顯示在數(shù)據(jù)接收窗口。也許,有些主板的串口壞了,那么你就要買一個轉(zhuǎn)接卡,通過PCI插口轉(zhuǎn)接。

3)察看外設(shè)使用說明書知道外設(shè)的相關(guān)參數(shù).

比如,波特率,數(shù)據(jù)位,停止位,校驗位,等等。只有正確參數(shù),才能顯示正確的數(shù)據(jù)。當(dāng)然,你可以在通訊測試軟件上調(diào)試這些參數(shù)的。比如:波特率=2400,數(shù)據(jù)位=8,停止位=2,校驗位=1。

4)準(zhǔn)備開發(fā)環(huán)境。

最基本的JDK了,你可以使用自己鐘愛的IDE,幫助你開發(fā)。IDE可能自帶了JDK,那么

你要把相應(yīng)的javaComm20-win32放到運(yùn)行時使用的JDK中。

下載JAVAcomm20-win32。

5)了解javaComm20-win32。

你必須把win32com.dll復(fù)制到j(luò)ava.home/bin下;把perties復(fù)制到j(luò)ava.home/lib下;把comm.jar添加到你classPath下。前面兩個都是非常重要的。

下面說明用到的幾個類:

m.CommPortIdentifier

通訊端口管理器,CommPortIdentifier是控制訪問到通訊端口的中

心類。它包括的方法有:

a.通過驅(qū)動決定通訊端口是可用的。

b.打開通訊端口為了I/O操作。

c.決定端口的擁有者。

d.解析端口擁有者的爭奪。

e.管理事件顯示在端口擁有者的中的狀態(tài)改變。

一個應(yīng)用程序首先使用CommPortIdentifier中的方法,通過相關(guān)的驅(qū)動去獲取那些通訊端口是可用的

并且選擇一個端口便于開始。然后它使用方法在其它類中想

CommPort,ParallelPort和SerialPort通過

這個端口進(jìn)行通訊。

m.SerialPort

一個RS-232串口通訊端口。SerialPort描述底層的接口到一個串口通訊端口

變得有效的通過底層的系統(tǒng)。SerialPort定義最小的必需的功能便于串口通訊端口。

m.SerialPortEventListener串行端口事件傳播。

m.CommDriver

6)代碼的編寫。

a.獲取SerialPortsPort對象的兩種方法。

1)

2)java代碼System.loadLibrary("win32com");

m.CommDriver

driver

=

null;

String

driverName

=

"m.Win32Driver";

SerialPort

sPort

=

(SerialPort)

driver.getCommPort("COM4",

ommPortIdentifier.PORT_SERIAL);

java代碼CommPortIdentifier

portId

=

CommPortIdentifier.getPortIdentifier("COM4");

SerialPort

sPort

=

(SerialPort)portId.open("shipment",1000);

以上兩種方法都可以。不過一般都會采用第二種。方法說明我們獲取了對串行端口(COM4),可以和它進(jìn)行通訊了。

b.設(shè)置串行端口通訊參數(shù)。java代碼sPort.setSerialPortParams(2400,SerialPort.DATABITS_8,SerialPort.STOPBITS_2,SerialPort.PARITY_NONE);

c.獲取輸入(出)流。java代碼InputStream

is

=

sPort.getInputStream();//從外設(shè)獲取數(shù)據(jù)

OutputStream

os

=

sPort.getOutputStream();//發(fā)送命令到外設(shè)

d.通過監(jiān)聽器就可以得到數(shù)據(jù)了。java代碼//Set

notifyOnDataAvailable

to

true

to

allow

event

driven

input.

sPort.notifyOnDataAvailable(true);

//

Set

notifyOnBreakInterrup

to

allow

event

driven

break

handling.

sPort.notifyOnBreakInterrupt(true);

//

Set

receive

timeout

to

allow

breaking

out

of

polling

loop

during

input

handling.

sPort.enableReceiveTimeout(30);

StringBuffer

linkWgt

=

new

StringBuffer();//存放獲取的數(shù)據(jù)

sPort.addEventListener(

new

SerialPortEventListener(){

public

void

serialEvent(SerialPortEvent

e){

int

newData

=

0;

//

Determine

type

of

event.

switch

(e.getEventType())

{

//

Read

data

until

-1

is

returned.

If

\r

is

received

substitute

//

\n

for

correct

newline

handling.

case

SerialPortEvent.DATA_AVAILABLE:

while

(newData

!=

-1)

{

try

{

newData

=

is.read();

if

(newData

==

-1)

{

break;

}

if

('\r'

==

(char)newData)

{

}

else

{

linkWgt.append((char)newData);

}

}

catch

(IOException

ex)

{

System.err.println(ex);

return;

}

}

//

Append

received

data

to

messageAreaIn.

try{

System.out.println("linkWgt

---------|||||

"+Double.valueOf(linkWgt.toString()));

}catch(Exception

ew){

ew.printStackTrace();

}finally{

try{

//用完了,記得關(guān)閉端口。

is.close();

sPort.close();

}catch(Exception

c){

c.printStackTrace();

}

}

break;

//

If

break

event

append

BREAK

RECEIVED

message.

case

SerialPortEvent.BI:

System.out.println("\n---

BREAK

RECEIVED

---\n");

}

}

}

);

7)常見的異常

a.m.NoSuchPortException

這個說明你的perties沒有放到正確的位置。

如果有什么不正確的地方,歡迎批評指正,謝謝!Java串行端口通訊技術(shù)

了解串行通訊

串行通訊協(xié)議有很多種,像RS232,RS485,RS422,甚至現(xiàn)今流行的USB等都是串行通訊協(xié)議。而串行通訊技術(shù)的應(yīng)用無處不在??赡艽蠹乙姷淖疃嗑褪请娔X的串口與Modem的通訊。記得在PC機(jī)剛開始在中國流行起來時(大約是在90年代前五年),那時甚至有人用一條串行線進(jìn)行兩臺電腦之間的數(shù)據(jù)共享。除了這些,手機(jī),PDA,USB鼠標(biāo)、鍵盤等等都是以串行通訊的方式與電腦連接。而筆者工作性質(zhì)的關(guān)系,所接觸到的就更多了,像多串口卡,各種種類的具有串口通訊接口的檢測與測量儀器,串口通訊的網(wǎng)絡(luò)設(shè)備等。

雖然串行通訊有很多種,但筆者所知的在整個電子通訊產(chǎn)品方面,以RS232的通訊方式最為多見。雖然USB接口的電子產(chǎn)品也是層出不窮,但了解一下Java在串行通訊方面的技術(shù)還有有必要的,說不定有哪位讀者還想用此技術(shù)寫一個PDA與電腦之間數(shù)據(jù)共享的程序呢。

本文主要以RS232為主來講解JAVA的串行通訊技術(shù)。RS232通訊基礎(chǔ)

RS-232-C(又稱EIARS-232-C,以下簡稱RS232)是在1970年由美國電子工業(yè)協(xié)會(EIA)聯(lián)合貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn)。RS232是一個全雙工的通訊協(xié)議,它可以同時進(jìn)行數(shù)據(jù)接收和發(fā)送的工作。RS232的端口通常有兩種:9針(DB9)和25針(DB25)。DB9和DB25的常用針腳定義9針串口(DB9)25針串口(DB25)針號功能說明縮寫針號功能說明縮寫1數(shù)據(jù)載波檢測DCD8數(shù)據(jù)載波檢測DCD2接收數(shù)據(jù)RXD3接收數(shù)據(jù)RXD3發(fā)送數(shù)據(jù)TXD2發(fā)送數(shù)據(jù)TXD4數(shù)據(jù)終端準(zhǔn)備DTR20數(shù)據(jù)終端準(zhǔn)備DTR5信號地GND7信號地GND6數(shù)據(jù)設(shè)備準(zhǔn)備好DSR6

數(shù)據(jù)準(zhǔn)備好DSR7請求發(fā)送RTS4請求發(fā)送RTS8清除發(fā)送CTS5清除發(fā)送CTS9振鈴指示RI22振鈴指示RI常見的邊線方式常見的通訊方式是三線式,這種方式是將兩個RS232設(shè)備的發(fā)送端(TXD)和接收端(RXD)及接地端(GND)互相連接,也是許多讀者所知道的連接方式:(9針)2(RXD)---------3(TXD3(TXD)---------2(TXD)5(GND)---------5(GND)(25針)2(RXD)---------3(TXD3(TXD)---------2(RXD)7(GND)---------7(GND)

這種方式分別將兩端的RS232接口的2--3,3---2,5(7)---5(7)針腳連接起來。其中2是數(shù)據(jù)接收線(RXD),3是數(shù)據(jù)發(fā)送線(TXD),5(7)是接地(RND)。如果有一臺式PC,和一部NoteBook電腦,就可以用這種方式連線了。用三線式可以將大多數(shù)的RS232設(shè)備連接起來。但如果你認(rèn)死了2--3,3--2,5(7)--5(7)對接這個理,會發(fā)現(xiàn)在連某些RS232設(shè)備時并不奏效。這是因為有些設(shè)備在電路內(nèi)部已將2和3線調(diào)換過來了,你只要2,3,5(7)針一一對應(yīng)就行了。小技巧:如何辨別TXD和RXD端口?

搞電子的人手邊應(yīng)該常備一個電表,用來測測電壓,電阻什么的會很有用。你只要分別測一下RS232端口的2--5或3--5針腳之間的電壓,通常TXD針腳與GND之間會有3~15V左右的負(fù)電壓,表示它是TXD針腳。

安裝JavaCommunicationsAPI

Sun的J2SE中并沒有直接提供以上提到的任何一種串行通訊協(xié)議的開發(fā)包,而是以獨(dú)立的jar包形式發(fā)布在網(wǎng)站上(從這里下載)----即comm.jar,稱之為JavatmCommunicationsAPI,它是J2SE的標(biāo)準(zhǔn)擴(kuò)展。comm.jar并不是最近才有,早在1998年時,sun就已經(jīng)發(fā)布了這個開發(fā)包。comm.jar分別提供了對常用的RS232串行端口和IEEE1284并行端口通訊的支持。目前sun發(fā)布的comm.jar只有Windows和Solaris平臺兩個版本,如果你需要Linux平臺下的,可以在/~kevinh/linuxcomm.html找到。

在使用comm.jar之前,必須知道如何安裝它。這也是困擾許多初學(xué)javaRS232通訊者的一個難題。如果我們電腦上安裝了JDK,它將同時為我們安裝一份JRE(JavaRuntimeEntironment),通常我們運(yùn)行程序時都是以JRE來運(yùn)行的。所以以下的安裝適用于JRE。如果你是用JDK來運(yùn)行程序的,請將相應(yīng)的改成。

下載了comm.jar開發(fā)包后,與之一起的還有兩個重要的文件,win32com.dll和perties。comm.jar提供了通訊用的javaAPI,而win32com.dll提供了供comm.jar調(diào)用的本地驅(qū)動接口。而perties是這個驅(qū)動的類配置文件。首先將comm.jar復(fù)制到\lib\ext目錄。再將win21com.dll復(fù)制到你的RS232應(yīng)用程序運(yùn)行的目錄,即user.dir。然后將perties復(fù)制到\lib目錄。通訊前的準(zhǔn)備

如果你手頭上沒有現(xiàn)成的提供了標(biāo)準(zhǔn)RS232串口的設(shè)備,你可以將自己的電腦模擬成兩臺不同的串口設(shè)備。通常電腦主機(jī)后面的面板提供了兩個9針的串口,請將這兩個串口的2,3,5腳按前面介紹的方法連接。電子市場都有現(xiàn)成的連接頭賣,請不要買那種封裝的嚴(yán)嚴(yán)實實的接頭,而要買用螺絲封裝可以拆開的連接頭,這樣可以方便自己根據(jù)需要連接各個針腳。CommAPI基礎(chǔ)

我無意于在此詳細(xì)描述CommAPI每個類和接口的用法,但我會介紹CommAPI的類結(jié)構(gòu)和幾個重要的API用法。

所有的commAPI位于m包下面。從CommAPI的javadoc來看,它介紹給我們的只有區(qū)區(qū)以下13個類或接口:m.CommDriver

m.CommPort

m.ParallelPort

m.SerialPort

m.CommPortIdentifier

m.CommPortOwnershipListener

m.ParallelPortEvent

m.SerialPortEvent

m.ParallelPortEventListener(extendsjava.util.EventListener)

m.SerialPortEventListener(extendsjava.util.EventListener)

m.NoSuchPortException

m.PortInUseException

m.UnsupportedCommOperationException

下面講解一下幾個主要類或接口。

1.枚舉出系統(tǒng)所有的RS232端口

在開始使用RS232端口通訊之前,我們想知道系統(tǒng)有哪些端口是可用的,以下代碼列出系統(tǒng)中所有可用的RS232端口:Enumerationen=CommPortIdentifier.getPortIdentifiers();

CommPortIdentifierportId;

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)所有的串口,每個串口對應(yīng)一個CommPortIdentifier類的實例。

2.打開端口

如果你使用端口,必須先打開它。

try{

CommPortserialPort=portId.open("MyApp",60);

/*從端口中讀取數(shù)據(jù)*/

InputStreaminput=serialPort.getInputStream();

input.read(...);

/*往端口中寫數(shù)據(jù)*/

OutputStreamoutput=serialPort.getOutputStream();

output.write(...)

...

}catch(PortInUseExceptionex)

{...}

通過CommPortIdentifier的open方法可以返回一個CommPort對象。open方法有兩個參數(shù),第一個是String,通常設(shè)置為你的應(yīng)用程序的名字。第二個參數(shù)是時間,即開啟端口超時的毫秒數(shù)。當(dāng)端口被另外的應(yīng)用程序占用時,將拋出PortInUseException異常。

在這里CommPortIdentifier類和CommPort類有什么區(qū)別呢?其實它們兩者是一一對應(yīng)的關(guān)系。CommPortIdentifier主要負(fù)責(zé)端口的初始化和開啟,以及管理它們的占有權(quán)。而CommPort則是跟實際的輸入和輸出功能有關(guān)的。通過CommPort的getInputStream()可以取得端口的輸入流,它是java.io.InputStream接口的一個實例。我們可以用標(biāo)準(zhǔn)的InputStream的操作接口來讀取流中的數(shù)據(jù),就像通過FileInputSteam讀取文件的內(nèi)容一樣。相應(yīng)的,CommPort的getOutputStream可以獲得端口的輸出流,這樣就可以往串口輸出數(shù)據(jù)了。

3.關(guān)閉端口

使用完的端口,必須記得將其關(guān)閉,這樣可以讓其它的程序有機(jī)會使用它,不然其它程序使用該端口時可能會拋出端口正在使用中的錯誤。很奇怪的是,CommPortIdentifier類只提供了開啟端口的方法,而要關(guān)閉端口,則要調(diào)用CommPort類的close()方法。通訊方式

CommPort的輸入流的讀取方式與文件的輸入流有些不一樣,那就是你可能永遠(yuǎn)不知這個InputStream何時結(jié)束,除非對方的OutputStream向你發(fā)送了一個特定數(shù)據(jù)表示發(fā)送結(jié)束,你收到這個特定字符后,再行關(guān)閉你的InputStream。而comm.jar提供了兩種靈活的方式讓你讀取數(shù)據(jù)。

1.輪詢方式(Polling)

舉個例子,你同GF相約一起出門去看電影,但你的GF好打扮,這一打扮可能就是半小時甚至一小時以上。這時你就耐不住了,每兩分鐘就催問一次“好了沒?”,如此這樣,直到你的GF說OK了才算完。這個就叫輪詢(Polling)。

在程序中,輪詢通常設(shè)計成一個封閉的循環(huán),當(dāng)滿足某個條件時即結(jié)束循環(huán)。剛才那個例子中,你的GF說“OK了!”,這個就是結(jié)束你輪詢的條件。在單線程的程序中,當(dāng)循環(huán)一直執(zhí)行某項任務(wù)而又無法預(yù)知它何時結(jié)束時,此時你的程序看起來可能就像死機(jī)一樣。在VB程序中,這個問題可以用在循環(huán)結(jié)構(gòu)中插入一個doEvent語句來解決。而Java中,最好的方式是使用線程,就像以下代碼片斷一樣。

publicTestPortextendThread

{

...

InputStreaminput=serialPort.getInputStream();

StringBufferbuf=newStringBuffer();

booleanstopped=false;

...

publicvoidrun()

{

try{

while(!stopped)

intch=input.read();

if(ch=='q'||ch=='Q')

{

/*結(jié)束讀取,關(guān)閉端口...*/

stopped=true;

...

}

else

{

buf.append((char)ch);

...

}

}catch(InterruptedExceptione){}

}

}

2.監(jiān)聽方式(listening)

CommAPI支持標(biāo)準(zhǔn)的JavaBean型的事件模型。也就是說,你可以使用類似AddXXXListener這樣的方法為一個串口注冊自己的監(jiān)聽器,以監(jiān)聽方式進(jìn)行數(shù)據(jù)讀取。

如要對端口監(jiān)聽,你必須先取得CommPortIdentifier類的一個實例,

CommPortserialPort=portId.open("MyApp",60);

從而取得SerialPort,再調(diào)用它的addEventListener方法為它添加監(jiān)聽器,

serialPort.addEventListener(newMyPortListener());

SerialPort的監(jiān)聽器必須繼承于SerialPortEventListener接口。當(dāng)有任何SerialPort的事件發(fā)生時,將自動調(diào)用監(jiān)聽器中的serialEvent方法。SerialEvent有以下幾種類型:BI-通訊中斷.

CD-載波檢測.

CTS-清除發(fā)送.

DATA_AVAILABLE-有數(shù)據(jù)到達(dá).

DSR-數(shù)據(jù)設(shè)備準(zhǔn)備好.

FE-幀錯誤.

OE-溢位錯誤.

OUTPUT_BUFFER_EMPTY-輸出緩沖區(qū)已清空.

PE-奇偶校驗錯.

RI-振鈴指示.下面是一個監(jiān)聽器的示例,

publicvoidMyPortListenerimplementsSerialPortEventListener

{

這個監(jiān)聽器只是簡單打印每個發(fā)生的事件名稱。而對于大多數(shù)應(yīng)用程序來說,通常關(guān)心是DATA_AVAILABLE事件,當(dāng)數(shù)據(jù)從外部設(shè)備傳送到端口上來時將觸發(fā)此事件。此時就可以使用前面提到過的方法,serialPort.getInputStream()來從InputStream中讀取數(shù)據(jù)了。

完整的程序

為節(jié)省篇幅,本文只提供了一些代碼片斷來幫助讀者來理解CommAPI的用法。你可以從CommAPI的開發(fā)包中取得完整的可運(yùn)行的演示程序。請先下載了commAPI的開發(fā)包,解壓之后有一個名為Sample的目錄,里面有幾個演示程序,分別是:1)BlackBox:ASerialPortBlackBoxapplication.

2)ParallelBlackBox:A其中,第1),3),4)是關(guān)于rs232通訊的演示程序。而其它的,2)是并行端口的演示程序。5)和6)是開發(fā)自己的端口驅(qū)動程序的模板程序,有興趣的讀者可以自行研究。

publicvoidserialEvent(SerialPortEventevt)

{

switch(evt.getEventType())

{

caseSerialPortEvent.CTS:

System.out.println("CTSeventoccured.");

break;

caseSerialPortEvent.CD:

System.out.println("CDeventoccured.");

break;

caseSerialPortEvent.BI:

System.out.println("BIeventoccured.");

break;

caseSerialPortEvent.DSR:

System.out.println("DSReventoccured.");

break;

caseSerialPortEvent.FE:

System.out.println("FEeventoccured.");

break;

caseSerialPortEvent.OE:

System.out.println("OEeventoccured.");

break;

caseSerialPortEvent.PE:

System.out.println("PEeventoccured.");

break;

caseSerialPortEvent.RI:

System.out.println("RIeventoccured.");

break;

caseSerialPortEvent.OUTPUT_BUFFER_EMPTY:

System.out.println("OUTPUT_BUFFER_EMPTYeventoccured.");

break;

caseSerialPortEvent.DATA_AVAILABLE:

System.out.println("DATA_AVAILABLEeventoccured.");

intch;

StringBufferbuf=newStringBuffer();

InputStreaminput=serialPort.getInputStream

try{

while((ch=input.read())>0){

buf.append((char)ch);

}

System.out.print(buf);

}catch(IOExceptione){}

break;

}

}java串口編程1.SerialBean

SerialBean是本類庫與其他應(yīng)用程序的接口。該類庫中定義了SerialBean的構(gòu)造方法以及初始化串口,從串口讀取數(shù)據(jù),往串口寫入數(shù)據(jù)以及關(guān)閉串口的函數(shù)。具體介紹如下:

publicSerialBean(intPortID)

本函數(shù)構(gòu)造一個指向特定串口的SerialBean,該串口由參數(shù)PortID所指定。PortID=1表示COM1,PortID=2表示COM2,由此類推。

publicintInitialize()

本函數(shù)初始化所指定的串口并返回初始化結(jié)果。如果初始化成功返回1,否則返回-1。初始化的結(jié)果是該串口被SerialBean獨(dú)占性使用,其參數(shù)被設(shè)置為9600,N,8,1。如果串口被成功初始化,則打開一個進(jìn)程讀取從串口傳入的數(shù)據(jù)并將其保存在緩沖區(qū)中。

publicStringReadPort(intLength)

本函數(shù)從串口(緩沖區(qū))中讀取指定長度的一個字符串。參數(shù)Length指定所返回字符串的長度。

publicvoidWritePort(StringMsg)

本函數(shù)向串口發(fā)送一個字符串。參數(shù)Msg是需要發(fā)送的字符串。

publicvoidClosePort()

本函數(shù)停止串口檢測進(jìn)程并關(guān)閉串口。

packageserial;

importjava.io.*;

importjava.util.*;

importm.*;

/**

*

*Thisbeanprovidessomebasicfunctionstoimplementfulldulplex

*informationexchangethroughthesrialport.

*

*/

publicclassSerialBean

{

staticStringPortName;

CommPortIdentifierportId;

SerialPortserialPort;

staticOutputStreamout;

staticInputStream

in;

SerialBufferSB;

ReadSerial

RT;

/**

*

*Constructor

*

*@paramPortIDtheIDoftheserialtobeused.1forCOM1,

*2forCOM2,etc.

*

*/

publicSerialBean(intPortID)

{

PortName="COM"+PortID;

}

/**

*

*Thisfunctioninitializetheserialportforcommunication.Itstartssa

*threadwhichconsistentlymonitorstheserialport.Anysignalcapturred

*fromtheserialportisstoredintoabufferarea.

*

*/

publicintInitialize()

{

intInitSuccess=1;

intInitFail

=-1;

try

{

portId=CommPortIdentifier.getPortIdentifier(PortName);

try

{

serialPort=(SerialPort)

portId.open("Serial_Communication",2000);

}catch(PortInUseExceptione)

{

returnInitFail;

}

//UseInputStreamintoreadfromtheserialport,andOutputStream

//outtowritetotheserialport.

try

{

in

=serialPort.getInputStream();

out=serialPort.getOutputStream();

}catch(IOExceptione)

{

returnInitFail;

}

//Initializethecommunicationparametersto9600,8,1,none.

try

{

serialPort.setSerialPortParams(9600,

SerialPort.DATABITS_8,

SerialPort.STOPBITS_1,

SerialPort.PARITY_NONE);

}catch(UnsupportedCommOperationExceptione)

{

returnInitFail;

}

}catch(NoSuchPortExceptione)

{

returnInitFail;

}

//whensuccessfullyopentheserialport,

createanewserialbuffer,

//thencreateathreadthatconsistentlyacceptsincomingsignalsfrom

//theserialport.Incomingsignalsarestoredintheserialbuffer.

SB=newSerialBuffer();

RT=newReadSerial(SB,in);

RT.start();

//returnsuccessinformation

returnInitSuccess;

}

/**

*

*Thisfunctionreturnsastringwithacertainlengthfromtheincomin

*messages.

*

*@paramLengthThelengthofthestringtobereturned.

*

*/

publicStringReadPort(intLength)

{

StringMsg;

Msg=SB.GetMsg(Length);

returnMsg;

}

/**

*

*Thisfunctionsendsamessagethroughtheserialport.

*

*@paramMsgThestringtobesent.

*

*/

publicvoidWritePort(StringMsg)

{

intc;

try

{

for(inti=0;i<Msg.length();i++)

out.write(Msg.charAt(i));

}catch(IOExceptione)

{}

}

/**

*

*Thisfunctionclosestheserialportinuse.

*

*/

publicvoidClosePort()

{

RT.stop();

serialPort.close();

}

}2.SerialBuffer

SerialBuffer是本類庫中所定義的串口緩沖區(qū),它定義了往該緩沖區(qū)中寫入數(shù)據(jù)和從該緩沖區(qū)中讀取數(shù)據(jù)所需要的函數(shù)。

publicsynchronizedStringGetMsg(intLength)

本函數(shù)從串口(緩沖區(qū))中讀取指定長度的一個字符串。參數(shù)Length指定所返回字符串的長度。

publicsynchronizedvoidPutChar(intc)

本函數(shù)望串口緩沖區(qū)中寫入一個字符,參數(shù)c是需要寫入的字符。

在往緩沖區(qū)寫入數(shù)據(jù)或者是從緩沖區(qū)讀取數(shù)據(jù)的時候,必須保證數(shù)據(jù)的同步,因此GetMsg和PutChar函數(shù)均被聲明為synchronized并在具體實現(xiàn)中采取措施實現(xiàn)的數(shù)據(jù)的同步。

packageserial;

/**

*

*Thisclassimplementsthebufferareatostoreincomingdatafromtheserial

*port.

*

*/

publicclassSerialBuffer

{

privateStringContent="";

privateStringCurrentMsg,TempContent;

privatebooleanavailable=false;

privateintLengthNeeded=1;

/**

*

*Thisfunctionreturnsastringwithacertainlengthfromtheincomin

*messages.

*

*@paramLengthThelengthofthestringtobereturned.

*

*/

publicsynchronizedStringGetMsg(intLength)

{

LengthNeeded=Length;

notifyAll();

if(LengthNeeded>Content.length())

{

available=false;

while(available==false)

{

try

{

wait();

}catch(InterruptedExceptione){}

}

}

CurrentMsg

=Content.substring(0,LengthNeeded);

TempContent=Content.substring(LengthNeeded);

Content=TempContent;

LengthNeeded=1;

notifyAll();

returnCurrentMsg;

}

/**

*

*Thisfunctionstoresacharactercapturedfromtheserialporttothe

*bufferarea.

*

*@paramtThecharvalueofthecharactertobestored.

*

*/

publicsynchronizedvoidPutChar(intc)

{

Characterd=newCharacter((char)c);

Content=Content.concat(d.toString());

if(Length

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論