嵌入式系統(tǒng)或傳感器網(wǎng)絡(luò)的很多應(yīng)用和測試都需要通過PC機(jī)與嵌入式設(shè)備或傳感器節(jié)點進(jìn)行通信 其中 最常用的接口就是RS 串口和并口(鑒于USB接口的復(fù)雜性以及不需要很大的數(shù)據(jù)傳輸量 USB接口用在這里還是顯得過于奢侈 況且目前除了SUN有一個支持USB的包之外 我還沒有看到其他直接支持USB的Java類庫) SUN的CommAPI分別提供了對常用的RS 串行端口和IEEE 并行端口通訊的支持 RS C(又稱EIA RS C 以下簡稱RS )是在 年由美國電子工業(yè)協(xié)會(EIA)聯(lián)合貝爾系統(tǒng) 調(diào)制解調(diào)器廠家及計算機(jī)終端生產(chǎn)廠家共同制定的用于串行通訊的標(biāo)準(zhǔn) RS 是一個全雙工的通訊協(xié)議 它可以同時進(jìn)行數(shù)據(jù)接收和發(fā)送的工作
目前創(chuàng)新互聯(lián)已為成百上千的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁空間、網(wǎng)站運(yùn)營、企業(yè)網(wǎng)站設(shè)計、甕安網(wǎng)站維護(hù)等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
常見的Java串口包
目前 常見的Java串口包有SUN在 年發(fā)布的串口通信API m jar(Windows下) m jar(Linux/Solaris);IBM的串口通信API以及一個開源的實現(xiàn) 鑒于在Windows下SUN的API比較常用以及IBM的實現(xiàn)和SUN的在API層面都是一樣的 那個開源的實現(xiàn)又不像兩家大廠的產(chǎn)品那樣讓人放心 這里就只介紹SUN的串口通信API在Windows平臺下的使用
串口包的安裝(Windows下)
到SUN的網(wǎng)站下載javam win zip 包含的東西如下所示
按照其使用說明(l)的說法 要想使用串口包進(jìn)行串口通信 除了設(shè)置好環(huán)境變量之外 還要將win dll復(fù)制到 \bin目錄下;將m jar復(fù)制到 \lib;把m properties也同樣拷貝到 \lib目錄下 然而在真正運(yùn)行使用串口包的時候 僅作這些是不夠的 因為通常當(dāng)運(yùn)行 java MyApp 的時候 是由JRE下的虛擬機(jī)啟動MyApp的 而我們只復(fù)制上述文件到JDK相應(yīng)目錄下 所以應(yīng)用程序?qū)崾菊也坏酱?解決這個問題的方法很簡單 我們只須將上面提到的文件放到JRE相應(yīng)的目錄下就可以了
值得注意的是 在網(wǎng)絡(luò)應(yīng)用程序中使用串口API的時候 還會遇到其他更復(fù)雜問題 有興趣的話 你可以查看CSDN社區(qū)中 關(guān)于網(wǎng)頁上Applet用javam 讀取客戶端串口的問題 的帖子
串口API概覽
m CommPort
這是用于描述一個被底層系統(tǒng)支持的端口的抽象類 它包含一些高層的IO控制方法 這些方法對于所有不同的通訊端口來說是通用的 SerialPort 和ParallelPort都是它的子類 前者用于控制串行端口而后者用于控這并口 二者對于各自底層的物理端口都有不同的控制方法 這里我們只關(guān)心SerialPort
m CommPortIdentifier
這個類主要用于對串口進(jìn)行管理和設(shè)置 是對串口進(jìn)行訪問控制的核心類 主要包括以下方法
l 確定是否有可用的通信端口
l 為IO操作打開通信端口
l 決定端口的所有權(quán)
l 處理端口所有權(quán)的爭用
l 管理端口所有權(quán)變化引發(fā)的事件(Event)
m SerialPort
這個類用于描述一個RS 串行通信端口的底層接口 它定義了串口通信所需的最小功能集 通過它 用戶可以直接對串口進(jìn)行讀 寫及設(shè)置工作
串口API實例
大段的文字怎么也不如一個小例子來的清晰 下面我們就一起看一下串口包自帶的例子 SerialDemo中的一小段代碼來加深對串口API核心類的使用方法的認(rèn)識
列舉出本機(jī)所有可用串口
void?listPortChoices()?{ CommPortIdentifier?portId; Enumeration?en?=?CommPortIdentifier getPortIdentifiers(); //?iterate?through?the?ports 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é)果是 和
串口參數(shù)的配置
串口一般有如下參數(shù)可以在該串口打開以前配置進(jìn)行配置
包括波特率 輸入/輸出流控制 數(shù)據(jù)位數(shù) 停止位和齊偶校驗
SerialPort?sPort; try?{ sPort setSerialPortParams(BaudRate Databits Stopbits Parity); //設(shè)置輸入/輸出控制流 sPort setFlowControlMode(FlowControlIn?|?FlowControlOut); }?catch?(UnsupportedCommOperationException?e)?{}
串口的讀寫
對串口讀寫之前需要先打開一個串口
CommPortIdentifier?portId?=?CommPortIdentifier getPortIdentifier(PortName); try?{ SerialPort?sPort?=?(SerialPort)?portId open( 串口所有者名稱 ?超時等待時間); }?catch?(PortInUseException?e)?{//如果端口被占用就拋出這個異常 throw?new?SerialConnectionException(e getMessage()); } //用于對串口寫數(shù)據(jù) OutputStream?os?=?new?BufferedOutputStream(sPort getOutputStream()); os write(int?data); //用于從串口讀數(shù)據(jù) InputStream?is?=?new?BufferedInputStream(sPort getInputStream()); int?receivedData?=?is read();
讀出來的是int型 你可以把它轉(zhuǎn)換成需要的其他類型
這里要注意的是 由于Java語言沒有無符號類型 即所有的類型都是帶符號的 在由byte到int的時候應(yīng)該尤其注意 因為如果byte的最高位是 則轉(zhuǎn)成int類型時將用 來占位 這樣 原本是 的byte類型的數(shù)變成int型就成了 這是很嚴(yán)重的問題 應(yīng)該注意避免
串口通信的通用模式及其問題
終于嘮叨完我最討厭的基礎(chǔ)知識了 下面開始我們本次的重點 串口應(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)是采用第三種方法來解決這個問題
事件監(jiān)聽模型
現(xiàn)在我們來看看事件監(jiān)聽模型是如何運(yùn)作的
l 首先需要在你的端口控制類(例如SManager)加上 implements SerialPortEventListener
l 在初始化時加入如下代碼
try?{ SerialPort?sPort addEventListener(SManager); }?catch?(TooManyListenersException?e)?{ sPort close(); throw?new?SerialConnectionException( too?many?listeners?added ); } sPort notifyOnDataAvailable(true);
l 覆寫public void serialEvent(SerialPortEvent e)方法 在其中對如下事件進(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é)點從串口發(fā)回的查詢數(shù)據(jù) 并將結(jié)果以圖標(biāo)的形式顯示出來 串口設(shè)定的波特率是 川口每隔 毫秒返回一組數(shù)據(jù)(大約是 字節(jié)左右) 周期(即持續(xù)時間)為 秒 實測的時候在一個周期內(nèi)應(yīng)該返回 多個字節(jié) 而用事件監(jiān)聽模型我最多只能收到不到 字節(jié) 不知道這些字節(jié)都跑哪里去了 也不清楚到底丟失的是那部分?jǐn)?shù)據(jù) 值得注意的是 這是我將serialEvent()中所有處理代碼都注掉 只剩下打印代碼所得的結(jié)果 數(shù)據(jù)丟失的如此嚴(yán)重是我所不能忍受的 于是我決定采用其他方法
串口讀數(shù)據(jù)的線程模型
這個模型顧名思義 就是將接收數(shù)據(jù)的操作寫成一個線程的形式:
public?void?startReadingDataThread()?{ Thread?readDataProcess?=?new?Thread(new?Runnable()?{ public?void?run()?{ while?(newData?!=? )?{ try?{ newData?=?is read(); System out println(newData); //其他的處理過程 ……… }?catch?(IOException?ex)?{ 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ù)了 可是原本一個周期( 秒)之后 傳感器節(jié)電已經(jīng)停止傳送數(shù)據(jù)了 但我的串口線程依然在努力的執(zhí)行讀串口操作 在控制臺也可以看見收到的數(shù)據(jù)仍在不斷的打印 原來 由于傳感器節(jié)點發(fā)送的數(shù)據(jù)過快 而我的接收線程處理不過來 所以InputStream就先把已到達(dá)卻還沒處理的字節(jié)緩存起來 于是就導(dǎo)致了明明傳感器節(jié)點已經(jīng)不再發(fā)數(shù)據(jù)了 而控制臺卻還能看見數(shù)據(jù)不斷打印這一奇怪的現(xiàn)象 唯一值得慶幸的是最后收到數(shù)據(jù)確實是 左右字節(jié) 沒出現(xiàn)丟失現(xiàn)象 然而當(dāng)處理完最后一個數(shù)據(jù)的時候已經(jīng)快 分半鐘了 這個時間遠(yuǎn)遠(yuǎn)大于節(jié)點運(yùn)行周期 這一延遲對于一個實時的顯示系統(tǒng)來說簡直是災(zāi)難!
后來我想 是不是由于兩個線程之間的同步和通信導(dǎo)致了數(shù)據(jù)接收緩慢呢?于是我在接收線程的代碼中去掉了所有處理代碼 僅保留打印收到數(shù)據(jù)的語句 結(jié)果依然如故 看來并不是線程間的通信阻礙了數(shù)據(jù)的接收速度 而是用線程模型導(dǎo)致了對于發(fā)送端數(shù)據(jù)發(fā)送速率過快的情況下的數(shù)據(jù)接收延遲 這里申明一點 就是對于數(shù)據(jù)發(fā)送速率不是如此快的情況下前面者兩種模型應(yīng)該還是好用的 只是特殊情況還是應(yīng)該特殊處理
第三種方法
痛苦了許久(Boss天天催我L)之后 偶然的機(jī)會 我聽說TinyOS中(又是開源的)有一部分是和我的應(yīng)用程序類似的串口通信部分 于是我下載了它的 x版的Java代碼部分 參考了它的處理方法 解決問題的方法說穿了其實很簡單 就是從根源入手 根源不就是接收線程導(dǎo)致的嗎 那好 我就干脆取消接收線程和作為中介的共享緩存 而直接在處理線程中調(diào)用串口讀數(shù)據(jù)的方法來解決問題(什么 為什么不把處理線程也一并取消? 都取消應(yīng)用程序界面不就鎖死了嗎?所以必須保留)于是程序變成了這樣
public?byte[]?getPack(){ while?(true)?{ //?PacketLength為數(shù)據(jù)包長度 byte[]?msgPack?=?new?byte[PacketLength]; for(int?i?=? ;?i??PacketLength;?i++){ if(?(newData?=?is read())?!=? ){ msgPack[i]?=?(byte)?newData; System out println(msgPack[i]); } } return?msgPack; } }
在處理線程中調(diào)用這個方法返回所需要的數(shù)據(jù)序列并處理之 這樣不但沒有丟失數(shù)據(jù)的現(xiàn)象行出現(xiàn) 也沒有數(shù)據(jù)接收延遲了 這里唯一需要注意的就是當(dāng)串口停止發(fā)送數(shù)據(jù)或沒有數(shù)據(jù)的時候is read()一直都返回 如果一旦在開始接收數(shù)據(jù)的時候發(fā)現(xiàn) 就不要理它 繼續(xù)接收 直到收到真正的數(shù)據(jù)為止
結(jié)束語
lishixinzhi/Article/program/Java/hx/201311/26605
最近在做java串口通訊,主要是用個人電腦通過串口從RS485讀取數(shù)據(jù),并通過crc循環(huán)冗余校驗,把接收正確的數(shù)據(jù)解析,插入數(shù)據(jù)庫mysql,并用SSH技術(shù)把數(shù)據(jù)庫數(shù)據(jù)以表格以及圖表形式顯示 \x0d\x0a 思路: \x0d\x0a1.為了從RS485讀取數(shù)據(jù),由于暫時沒有硬件設(shè)備,系統(tǒng)是win7,故采用Virtual Serial Port Drive(VSPD)這塊虛擬串口軟件代替。并下載sscom32.exe模擬串口通信軟件。 \x0d\x0a\x0d\x0a2. 要想實現(xiàn)串口通信,用Java實現(xiàn)串口通信(windows系統(tǒng)下),需要用到sun提供的串javacomm20-win32.zip。其中要用到三個文件,配置如下: \x0d\x0acomm.jar放置到 JAVA_HOME/jre/lib/ext; \x0d\x0awin32com.dll放置到 JAVA_HOME/bin; \x0d\x0ajavax.comm.properties 兩個地方都要放 \x0d\x0ajre/lib(也就是在JAVA文件夾下的jre),JAVA_HOME/jre/lib下 \x0d\x0a這個配置在我電腦上測試成功,也許不需要這樣麻煩。注意的是,如果你使用myeclipse,因為它自帶jre,你需要在它所在的jre相應(yīng)位置放dll以及properties文件。 \x0d\x0a\x0d\x0a是不是感覺這個很麻煩,還有windows的限制。后來我們下載rxtx這款開源包代替了剛才的comm。不僅windows下可以,linux下也可以。使用方法很簡單,配置如下: \x0d\x0a\x0d\x0aRXTXcomm.jar放到JAVA_HOME/jre/lib/ext \x0d\x0arxtxSerial.dll放到JAVA_HOME/bin \x0d\x0a如果你使用myeclipse工具,你需要把rxtxSerial.dll放到它自帶的jre里。 \x0d\x0a\x0d\x0a3.新建eclipse工程,添加comm.jar或者RXTXcomm.jar包。因為javacomm20-win32.zip包里有樣例SimpleRead.java,可以通過這個例子測試串口是否正確 \x0d\x0a\x0d\x0a4.接收數(shù)據(jù)正確后,根據(jù)傳送接收雙方的協(xié)議,采用CRC循環(huán)校驗,根據(jù)傳輸?shù)囊环降男r灪瘮?shù)判定是否是正確傳輸 \x0d\x0a\x0d\x0a5.把正確結(jié)束的數(shù)據(jù)解析,查看自己指定的通訊規(guī)則,然后解析 \x0d\x0a\x0d\x0a6.插入數(shù)據(jù)庫,jdbc插入 \x0d\x0a\x0d\x0a7.數(shù)據(jù)統(tǒng)計,定時統(tǒng)計每小時,每天,每月,每年的平均值,采用quartz服務(wù)來實現(xiàn)。 \x0d\x0a\x0d\x0a8.建立web工程,采用hibernate3,spring3,dwr技術(shù)把數(shù)據(jù)庫數(shù)據(jù)動態(tài)顯示,圖表采用jfreechart,以及AJAX的運(yùn)用
public static void process() {
try {
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements())
{
CommPortIdentifier portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL)//如果端口類型是串口則判斷名稱
{
if(portId.getName().equals("COM1")){//如果是COM1端口則退出循環(huán)
break;
}else{
portId=null;
}
}
}
SerialPort serialPort = (SerialPort)portId.open("Serial_Communication", 1000);//打開串口的超時時間為1000ms
serialPort.setSerialPortParams(9600,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);//設(shè)置串口速率為9600,數(shù)據(jù)位8位,停止位1們,奇偶校驗無
InputStream in = serialPort.getInputStream();//得到輸入流
OutputStream out = serialPort.getOutputStream();//得到輸出流
//進(jìn)行輸入輸出操作
//操作結(jié)束后
in.close();
out.close();
serialPort.close();//關(guān)閉串口
} catch (PortInUseException e) {
e.printStackTrace();
} catch (UnsupportedCommOperationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}