序言
說到開源,恐怕很少有人不挑大指稱讚。學生透過開源程式碼學到了知識,程式設計師透過開源類別庫獲得了別人的成功經驗及能夠按時完成手頭上的工程,商家透過開源軟體賺到了錢……,總之是皆大歡喜。然而開源軟體或類別庫的首要缺點就是大多缺乏詳細的說明文檔和使用的例子,或者就是軟體程式碼隨便你用,就是文檔,例子和後期服務收錢。這也難怪,畢竟就像某個著名NBA球員說的那樣:「我還要養家,所以千萬美元以下的合約別找我談,否則我寧可待業」。是啊,支持開源的人也要養家,收點錢也不過分。要想既不花錢又學到知識就只能藉網路和了,我只是想拋磚引玉,為開源事業做出點微薄共獻,能為你的工程解決哪怕一個小問題,也就足夠了。
雖然我的這個系列介紹的東西不是什麼Web框架,也不是什麼開源伺服器,但是我相信,作為一個程式設計師,什麼樣的問題都會遇到。有時候越是簡單的問題反而越棘手;越是小的地方越是找不到稱手的傢伙。只要你不是整天只與「架構」、「構件」、「框架」打交道的話,相信我所說的東西你一定會用到。
1 串口通訊簡介
1.1 常見的Java串口包
2.1 javax.comm.CommPort2.2 javax.comm.CommPortIdentifier 2.3 javax.comm.SerialPort2.4 應串口API實例2.4.1 串口API實例2.4.1 串口API實例2.4.1 串口參數的配置2.4.3 串口的讀寫3 串口通訊的一般模式及其問題3.1 事件監聽模型3.2 串口讀資料的執行緒模式3.3 串口讀資料的執行緒模式3.3 串口讀資料的執行緒模式3.3 串口讀資料第三種方法
值得注意的是,在網路應用程式中使用串列埠API的時候,還會遇到其他更複雜問題。有興趣的話,可以查看CSDN社群中「關於網頁上Applet用javacomm20讀取客戶端串口的問題」的貼文。
2 串口API概覽
2.1 javax.comm.CommPort
這是用來描述一個由底層系統支援的連接埠的抽象類別。它包含一些高層的IO控制方法,這些方法對於所有不同的通訊連接埠來說是通用的。 SerialPort 和ParallelPort都是它的子類,前者用來控制串列埠而後者用來控這並口,二者對於各自底層的實體埠都有不同的控制方法。這裡我們只關心SerialPort。
2.2 javax.comm.CommPortIdentifier
這個類別主要用於對串口進行管理和設置,是對串口進行存取控制的核心類別。主要包括以下方法
l 確定是否有可用的通訊埠
l 為IO作業開啟 處理埠所有權的爭用
l 管理連接埠所有權變更所引發的事件( Event)
2.3 javax.comm.SerialPort
這個類別用於描述一個RS-232串行通訊埠的底層接口,它定義了串口通訊所需的最小功能集。透過它,使用者可以直接對串口進行讀取、寫入及設定工作。
2.4 串口API實例
大段的文字怎麼也不如一個小例子來的清晰,下面我們就一起看一下串口包自帶的例子---SerialDemo中的一小段程式碼來加深對串口API核心類的使用方法的認識。
2.4.1 列出本機所有可用串口
void listPortChoices() {
= CommPortIdentifier.getPortIdentifiers();
// iterate through the ports.
hasMoreElements()) {
portId = (CommPortIdentifier) en.nextElement(); Identifier.PORT_SERIAL) {
System.out.println(portId.getName());
}
}
}以上程式碼可以列出目前系統所有可用的串列名稱,而我的機器上輸出的結果是COM1和COM3。 2.4.2 串口參數的配置串口一般有以下參數可在該串列口開啟先前配置進行設定:包含波特率,輸入/輸出流控制,數據
包含波特率,輸入/輸出流控制,資料位數,偶校驗。
SerialPort sPort;
try {
sPort.setSerialPortParams(Baud //設定輸入/輸出控制流 UnsupportedCommOperationException e) {}2.4.3 串列口的讀寫對串口讀取和寫入前需要先開啟一個串列埠:CommPortIdentifier portId = CommPortNifier.getPortPort; SerialPort sPort = (SerialPort ) portId.open("串口所有者名稱", 逾時等待時間); } catch (PortInUseException e) {//如果連接埠被佔用就拋出這個異常throw new SerialConnectionException(e.getMessage());
}
//用於對StreamStreamStreamStreamStreamM應用程式所寫入資料)
); os.write(int data); //用於從串口讀取資料InputStream is = new BufferedInputStream(sPort.getInputStream());int receivedData = is.read();int receivedData = is.read();
int receivedData = is.read();int型,你可以把它讀出來的是把它讀出來的是把它讀出來的是把它讀出來的是把它讀出來的是把它讀出來。成需要的其他類型。
這裡要注意的是,由於Java語言沒有無符號類型,即所有的類型都是帶符號的,在由byte到int的時候應該尤其註意。因為如果byte的最高位是1,則轉成int型別時會用1來佔位。這樣,原本是10000000的byte類型的數變成int型就成了1111111110000000,這是很嚴重的問題,應該要注意避免。
3 串口通訊的通用模式及其問題
終於嘮叨完我最討厭的基礎知識了,下面開始我們本次的重點--串口應用的研究。由於向串列埠寫資料很簡單,所以這裡我們只關注從串列口讀取資料的情況。通常,串口通訊應用程式有兩種模式,一種是實作SerialPortEventListener接口,監聽各種串列埠事件並作相應處理;另一種就是建立一個獨立的接收執行緒專門負責資料的接收。由於這兩種方法在某些情況下存在著很嚴重的問題(至於什麼問題這裡先賣個關子J),所以我的實作是採用第三種方法來解決這個問題。
3.1 事件監聽模型
現在我們來看看事件監聽模型是如何運作的
:
l 初始化時新增下列程式碼:
try {
SerialPort sPort.addEventListener(SManager);
sPort.close();
throw new SerialConnectionException("too many listeners added");
}
sPort.notifyOnDataAvailable(true);
l
CD -載波檢測.
CTS -清除發送.
DATA_AVAILABLE -有資料到達.
DSR -資料裝置準備好.
FE -幀錯誤.已清除緩衝區. .
PE -奇偶校驗錯.
RI - 振鈴指示.
一般最常用的就是DATA_AVAILABLE--串口有資料到達事件。也就是說當串口有資料到達時,你可以在serialEvent中接收並處理所收到的資料。然而在我的實踐中,遇到了一個十分嚴重的問題。
首先描述我的實驗:我的應用程式需要接收感測器節點從串口發回的查詢數據,並將結果以圖示的形式顯示出來。串列埠設定的波特率是115200,川口每隔128毫秒回傳一組資料(大約是30位元組左右),週期(即持續時間)為31秒。實測的時候在一個週期內應該返回4900多個字節,而用事件監聽模型我最多只能收到不到1500字節,不知道這些字節都跑哪裡去了,也不清楚到底丟失的是那部分數據。值得注意的是,這是我將serialEvent()中所有處理程式碼都注掉,只剩下列印程式碼所得的結果。資料遺失的如此嚴重是我所不能忍受的,於是我決定採用其他方法。
3.2 串口讀取資料的執行緒模型
這個模型顧名思義,就是將接收資料的運算寫成一個執行緒的形式:
public void startReadingDataThread() {
public void run() {
try {
System.out.println(newData);
🎠 //其他的處理流程 …. System.err.println(ex); } } 放到一個快取中,然後啟動另一個執行緒從快取中取得並處理資料。兩個執行緒以生產者—消費者模式協同工作,資料的流向如下圖所示:這樣,我就圓滿解決了丟資料問題。然而,沒高興多久我就又發現了一個同樣嚴重的問題:雖然這回不再丟數據了,可是原本一個週期(31秒)之後,感測器節電已經停止傳送數據了,但我的串口線程依然在努力的執行讀取串列埠操作,在控制台也可以看見收到的資料仍在不斷的列印。原來,由於感測器節點發送的資料過快,而我的接收線程處理不過來,所以InputStream就先把已到達卻還沒處理的字節緩存起來,於是就導致了明明感測器節點已經不再發數據了,而控制台卻還能看見資料不斷印製這奇怪的現象。唯一值得慶幸的是最後收到資料確實是4900左右字節,沒出現遺失現象。然而當處理完最後一個資料的時候已經快1分半鐘了,這個時間遠大於節點運作週期。這一延遲對於一個即時的顯示系統來說簡直是災難!
後來我想,是不是由於兩個執行緒之間的同步和通訊導致了資料接收緩慢呢?於是我在接收線程的程式碼中去掉了所有處理程式碼,僅保留列印收到資料的語句,結果依然如故。看來並不是線程間的通訊阻礙了資料的接收速度,而是用線程模型導致了對於發送端資料發送速率過快的情況下的資料接收延遲。這裡申明一點,就是對於資料發送速率不是如此快的情況下前面者兩種模型應該還是好用的,只是特殊情況還是應該特殊處理。
3.3 第三種方法
痛苦了許久(Boss天天催我L)之後,偶然的機會,我聽說TinyOS中(又是開源的)有一部分是和我的應用程序類似的串口通信部分,於是我下載了它的1.x版的Java程式碼部分,參考了它的處理方法。解決問題的方法說穿了其實很簡單,就是從根源著手。根源不就是接收線程導致的嗎,那好,我就乾脆取消接收線程和作為中介的共享緩存,而直接在處理線程中調用串口讀數據的方法來解決問題(什麼,為什麼不把處理線程也一並取消? / / PacketLength為資料包長度
byte[] msgPack = new byte for(int i = 0; iif( (newData = is.read()) != -1){ msgPack[i] = (byte) newData;
}
return msgPack;
}在處理執行緒中呼叫這個方法返回所需的資料序列並處理之,這樣不但沒有遺失資料的現象行出現,也沒有資料接收延遲了。這裡唯一要注意的就是當串口停止發送資料或沒有資料的時候is.read()一直都回傳-1,如果一旦在開始接收資料的時候發現-1就不要理它,繼續接收,直到收到真正的數據為止。
4 結論
本文介紹了串口通訊的基本知識,以及常用的幾種模式。透過實踐,提出了一些問題,並在最後加以解決。值得注意的是對於第一種方法,我曾經將感測器發送的時間由128毫秒增加到512毫秒,仍然有很嚴重的資料遺失現象發生,所以如果你的應用程式需要很精密的結果,傳輸資料的速率又很快的話,就最好不要用第一種方法。對於第二種方法,由於是執行緒導致的問題,所以對於不同的機器應該會有不同的表現,對於那些處理多執行緒比較好的機器來說,應該會好一些。但是我的機器是Inter 奔四3.0雙核心CPU+512DDR內存,這樣都延遲這麼厲害,還得多強的CPU才行啊?所以對於資料量比較大的傳輸來說,還是用第三種方法吧。不過這個世界問題是很多的,而且未知的問題比已知的問題多的多,說不定還有什麼其他問題存在
更多Java串口通信詳解相關文章請關注PHP中文網!