嵌入式系統或傳感器網絡的很多應用和測試都需要通過PC機與嵌入式設備或傳感器節點進行通信其中最常用的接口就是RS串口和並口(鑒於USB接口的復雜性以及不需要很大的數據傳輸量USB接口用在這裡還是顯得過於奢侈況且目前除了SUN有一個支持USB的包之外我還沒有看到其他直接支持USB的Java類庫)SUN的CommAPI分別提供了對常用的RS串行端口和IEEE並行端口通訊的支持RSC(又稱EIA RSC以下簡稱RS)是在年由美國電子工業協會(EIA)聯合貝爾系統調制解調器廠家及計算機終端生產廠家共同制定的用於串行通訊的標准RS是一個全雙工的通訊協議它可以同時進行數據接收和發送的工作
常見的Java串口包
目前常見的Java串口包有SUN在年發布的串口通信APIcommjar(Windows下)commjar(Linux/Solaris);IBM的串口通信API以及一個開源的實現鑒於在Windows下SUN的API比較常用以及IBM的實現和SUN的在API層面都是一樣的那個開源的實現又不像兩家大廠的產品那樣讓人放心這裡就只介紹SUN的串口通信API在Windows平台下的使用
串口包的安裝(Windows下)
到SUN的網站下載javacommwinzip包含的東西如下所示
按照其使用說明(l)的說法要想使用串口包進行串口通信除了設置好環境變量之外還要將wincomdll復制到\bin目錄下;將commjar復制到\lib;把mproperties也同樣拷貝到\lib目錄下然而在真正運行使用串口包的時候僅作這些是不夠的因為通常當運行java MyApp的時候是由JRE下的虛擬機啟動MyApp的而我們只復制上述文件到JDK相應目錄下所以應用程序將會提示找不到串口解決這個問題的方法很簡單我們只須將上面提到的文件放到JRE相應的目錄下就可以了
值得注意的是在網絡應用程序中使用串口API的時候還會遇到其他更復雜問題有興趣的話你可以查看CSDN社區中關於網頁上Applet用javacomm讀取客戶端串口的問題的帖子
串口API概覽
mCommPort
這是用於描述一個被底層系統支持的端口的抽象類它包含一些高層的IO控制方法這些方法對於所有不同的通訊端口來說是通用的SerialPort 和ParallelPort都是它的子類前者用於控制串行端口而後者用於控這並口二者對於各自底層的物理端口都有不同的控制方法這裡我們只關心SerialPort
mCommPortIdentifier
這個類主要用於對串口進行管理和設置是對串口進行訪問控制的核心類主要包括以下方法
l 確定是否有可用的通信端口
l 為IO操作打開通信端口
l 決定端口的所有權
l 處理端口所有權的爭用
l 管理端口所有權變化引發的事件(Event)
mSerialPort
這個類用於描述一個RS串行通信端口的底層接口它定義了串口通信所需的最小功能集通過它用戶可以直接對串口進行讀寫及設置工作
串口API實例
大段的文字怎麼也不如一個小例子來的清晰下面我們就一起看一下串口包自帶的例子SerialDemo中的一小段代碼來加深對串口API核心類的使用方法的認識
列舉出本機所有可用串口
void listPortChoices() {
CommPortIdentifier portId;
Enumeration en = CommPortIdentifiergetPortIdentifiers();
// iterate through the ports
while (enhasMoreElements()) {
portId = (CommPortIdentifier) ennextElement();
if (portIdgetPortType() == CommPortIdentifierPORT_SERIAL) {
Systemoutprintln(portIdgetName());
}
}
portChoiceselect(parametersgetPortName());
}
以上代碼可以列舉出當前系統所有可用的串口名稱我的機器上輸出的結果是COM和COM
串口參數的配置
串口一般有如下參數可以在該串口打開以前配置進行配置
包括波特率輸入/輸出流控制數據位數停止位和齊偶校驗
SerialPort sPort;
try {
sPortsetSerialPortParams(BaudRateDatabitsStopbitsParity);
//設置輸入/輸出控制流
sPortsetFlowControlMode(FlowControlIn | FlowControlOut);
} catch (UnsupportedCommOperationException e) {}
串口的讀寫
對串口讀寫之前需要先打開一個串口
CommPortIdentifier portId = CommPortIdentifiergetPortIdentifier(PortName);
try {
SerialPort sPort = (SerialPort) portIdopen(串口所有者名稱 超時等待時間);
} catch (PortInUseException e) {//如果端口被占用就拋出這個異常
throw new SerialConnectionException(egetMessage());
}
//用於對串口寫數據
OutputStream os = new BufferedOutputStream(sPortgetOutputStream());
oswrite(int data);
//用於從串口讀數據
InputStream is = new BufferedInputStream(sPortgetInputStream());
int receivedData = isread();
讀出來的是int型你可以把它轉換成需要的其他類型
這裡要注意的是由於Java語言沒有無符號類型即所有的類型都是帶符號的在由byte到int的時候應該尤其注意因為如果byte的最高位是則轉成int類型時將用來占位這樣原本是的byte類型的數變成int型就成了這是很嚴重的問題應該注意避免
串口通信的通用模式及其問題
終於唠叨完我最討厭的基礎知識了下面開始我們本次的重點串口應用的研究由於向串口寫數據很簡單所以這裡我們只關注於從串口讀數據的情況通常串口通信應用程序有兩種模式一種是實現SerialPortEventListener接口監聽各種串口事件並作相應處理;另一種就是建立一個獨立的接收線程專門負責數據的接收由於這兩種方法在某些情況下存在很嚴重的問題(至於什麼問題這裡先賣個關子J)所以我的實現是采用第三種方法來解決這個問題
事件監聽模型
現在我們來看看事件監聽模型是如何運作的
l 首先需要在你的端口控制類(例如SManager)加上implements SerialPortEventListener
l 在初始化時加入如下代碼
try {
SerialPort sPortaddEventListener(SManager);
} catch (TooManyListenersException e) {
sPortclose();
throw new SerialConnectionException(too many listeners added);
}
sPortnotifyOnDataAvailable(true);
l 覆寫public void serialEvent(SerialPortEvent e)方法在其中對如下事件進行判斷
BI 通訊中斷
CD 載波檢測
CTS 清除發送
DATA_AVAILABLE 有數據到達
DSR 數據設備准備好
FE 幀錯誤
OE 溢位錯誤
OUTPUT_BUFFER_EMPTY 輸出緩沖區已清空
PE 奇偶校驗錯
RI 振鈴指示
一般最常用的就是DATA_AVAILABLE串口有數據到達事件也就是說當串口有數據到達時你可以在serialEvent中接收並處理所收到的數據然而在我的實踐中遇到了一個十分嚴重的問題
首先描述一下我的實驗我的應用程序需要接收傳感器節點從串口發回的查詢數據並將結果以圖標的形式顯示出來串口設定的波特率是川口每隔毫秒返回一組數據(大約是字節左右)周期(即持續時間)為秒實測的時候在一個周期內應該返回多個字節而用事件監聽模型我最多只能收到不到字節不知道這些字節都跑哪裡去了也不清楚到底丟失的是那部分數據值得注意的是這是我將serialEvent()中所有處理代碼都注掉只剩下打印代碼所得的結果數據丟失的如此嚴重是我所不能忍受的於是我決定采用其他方法
串口讀數據的線程模型
這個模型顧名思義就是將接收數據的操作寫成一個線程的形式:
public void startReadingDataThread() {
Thread readDataProcess = new Thread(new Runnable() {
public void run() {
while (newData != ) {
try {
newData = isread();
Systemoutprintln(newData);
//其他的處理過程
………
} catch (IOException ex) {
Systemerrprintln(ex);
return;
}
}
readDataProcessstart();
}
在我的應用程序中我將收到的數據打包放到一個緩存中然後啟動另一個線程從緩存中獲取並處理數據兩個線程以生產者—消費者模式協同工作數據的流向如下圖所示
這樣我就圓滿解決了丟數據問題然而沒高興多久我就又發現了一個同樣嚴重的問題雖然這回不再丟數據了可是原本一個周期(秒)之後傳感器節電已經停止傳送數據了但我的串口線程依然在努力的執行讀串口操作在控制台也可以看見收到的數據仍在不斷的打印原來由於傳感器節點發送的數據過快而我的接收線程處理不過來所以InputStream就先把已到達卻還沒處理的字節緩存起來於是就導致了明明傳感器節點已經不再發數據了而控制台卻還能看見數據不斷打印這一奇怪的現象唯一值得慶幸的是最後收到數據確實是左右字節沒出現丟失現象然而當處理完最後一個數據的時候已經快分半鐘了這個時間遠遠大於節點運行周期這一延遲對於一個實時的顯示系統來說簡直是災難!
後來我想是不是由於兩個線程之間的同步和通信導致了數據接收緩慢呢?於是我在接收線程的代碼中去掉了所有處理代碼僅保留打印收到數據的語句結果依然如故看來並不是線程間的通信阻礙了數據的接收速度而是用線程模型導致了對於發送端數據發送速率過快的情況下的數據接收延遲這裡申明一點就是對於數據發送速率不是如此快的情況下前面者兩種模型應該還是好用的只是特殊情況還是應該特殊處理
第三種方法
痛苦了許久(Boss天天催我L)之後偶然的機會我聽說TinyOS中(又是開源的)有一部分是和我的應用程序類似的串口通信部分於是我下載了它的x版的Java代碼部分參考了它的處理方法解決問題的方法說穿了其實很簡單就是從根源入手根源不就是接收線程導致的嗎那好我就干脆取消接收線程和作為中介的共享緩存而直接在處理線程中調用串口讀數據的方法來解決問題(什麼為什麼不把處理線程也一並取消?都取消應用程序界面不就鎖死了嗎?所以必須保留)於是程序變成了這樣
public byte[] getPack(){
while (true) {
// PacketLength為數據包長度
byte[] msgPack = new byte[PacketLength];
for(int i = ; i < PacketLength; i++){
if( (newData = isread()) != ){
msgPack[i] = (byte) newData;
Systemoutprintln(msgPack[i]);
}
}
return msgPack;
}
}
在處理線程中調用這個方法返回所需要的數據序列並處理之這樣不但沒有丟失數據的現象行出現也沒有數據接收延遲了這裡唯一需要注意的就是當串口停止發送數據或沒有數據的時候isread()一直都返回如果一旦在開始接收數據的時候發現就不要理它繼續接收直到收到真正的數據為止
結束語
本文介紹了串口通信的基本知識以及常用的幾種模式通過實踐提出了一些問題並在最後加以解決值得注意的是對於第一種方法我曾將傳感器發送的時間由毫秒增加到毫秒仍然有很嚴重的數據丟失現象發生所以如果你的應用程序需要很精密的結果傳輸數據的速率又很快的話就最好不要用第一種方法對於第二種方法由於是線程導致的問題所以對於不同的機器應該會有不同的表現對於那些處理多線程比較好的機器來說應該會好一些但是我的機器是Inter 奔四雙核CPU+DDR內存這樣都延遲這麼厲害還得多強的CPU才行啊?所以對於數據量比較大的傳輸來說還是用第三種方法吧不過這個世界問題是很多的而且未知的問題比已知的問題多的多說不定還有什麼其他問題存在歡迎你通過下面的聯系方式和我一起研究
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26605.html