Windows下利用Javax.comm實現對串口的讀寫
Javax.comm簡介
Javax.comm是Sun公司提供的,用於開發平台獨立的通訊應用程式的擴充API。(ps:這裡javax的x很準確地表明了它是一個擴充包,而不是核心包(core package),但由於曆史原因,javax下的並不都是擴充包,比如swing包已經是Java核心架構的一部分了,不過為了與Java1.1編碼相容,仍使用javax.swing。)javax.comm可以訪問RS232介面(串口)及有限制地訪問IEEE-1284(並口)。
下載
需要到其官方首頁http://java.sun.com/products/javacomm/下載這個API,目前的最新版本是3.0。不過可惜的是,Sun目前沒有推出此API在Windows平台下的3.0版本,首頁上列出的三個版本,分別是運行在x86和Sparc結構下的Solaris系統,以及x86下的Linux系統。要下載Windows版本只能去尋找較老的版本了。我所找到的2個網址是http://llk.media.mit.edu/projects/cricket/software/javaSerial.zip(兩個檔案夾裡面有所需的3個檔案),http://mdubuc.freeshell.org/Jolt/javacomm20-win32.zip和(完整的2.0版本,還有examples)。
安裝
這裡的所謂安裝就是把三個重要的檔案放到指定的目錄下。
將下載的檔案解壓縮後,在/javacomm20-win32/commapi目錄下有必需的三個檔案comm.jar,javax.comm. properties和win32comm.dll。將檔案comm.jar拷貝到%JAVA_HOME%/jre/lib/ext;檔案javax.comm. properties拷貝到%JAVA_HOME%/jre/lib; 檔案win32comm.dll拷貝到%JAVA_HOME%/bin。注意%JAVA_HOME%是jdk的路徑,而非jre。
API
在javax.comm下有13個類和介面,分別是
4個介面
CommDriver 可負載裝置(the loadable device)驅動程式介面的一部分
CommPortOwnershipListener 傳遞各種通訊連接埠的所有權事件
ParallelPortEventListener 傳遞並行連接埠事件
SerialPortEventListener 傳遞序列埠事件
6個類
CommPort 通訊連接埠
CommPortIdentifier通訊連接埠管理
ParallelPort 並行通訊連接埠
ParallelPortEvent 並行連接埠事件
SerialPort RS-232串列通訊連接埠
SerialPortEvent 序列埠事件
3個異常類
NoSuchPortException 當驅動程式不能找到指定連接埠時拋出
PortInUseException 當碰到指定連接埠正在使用中時拋出
UnsupportedCommOperationException 驅動程式不允許指定操作時拋出
執行個體
同API一起下載的還有一個examples檔案,裡面有6個程式。首先看最簡單的讀、寫程式。
讀串口的常式
import java.io.*;
import java.util.*;
import javax.comm.*;
public class SimpleRead implements Runnable, SerialPortEventListener {
static CommPortIdentifier portId;
static Enumeration portList;//枚舉類
InputStream inputStream;
SerialPort serialPort;
Thread readThread;
public static void main(String[] args) {
portList = CommPortIdentifier.getPortIdentifiers();/*不帶參數的getPortIdentifiers方法獲得一個枚舉對象,該對象又包含了系統中管理每個連接埠的CommPortIdentifier對象。注意這裡的連接埠不僅僅是指串口,也包括並口。這個方法還可以帶參數。getPortIdentifiers(CommPort)獲得與已經被應用程式開啟的連接埠相對應的CommPortIdentifier對象。getPortIdentifier(String portName)擷取指定連接埠名(比如“COM1”)的CommPortIdentifier對象。*/
while (portList.hasMoreElements()) {
portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL)/*getPortType方法返回連接埠類型*/ {
// if (portId.getName().equals("COM1"))/* 找Windows下的第一個串口*/ {
if (portId.getName().equals("/dev/term/a"))/*找Unix-like系統下的第一個串口*/ {
SimpleRead reader = new SimpleRead();
}
}
}
}
public SimpleRead() {
try {
serialPort = (SerialPort) portId.open("SimpleReadApp", 2000);/* open方法開啟通訊連接埠,獲得一個CommPort對象。它使程式獨佔連接埠。如果連接埠正被其他應用程式佔用,將使用CommPortOwnershipListener事件機制,傳遞一個PORT_OWNERSHIP_REQUESTED事件。每個連接埠都關聯一個InputStream 何一個OutputStream。如果連接埠是用open方法開啟的,那麼任何的getInputStream都將返回相同的資料流對象,除非有close被調用。有兩個參數,第一個為應用程式名稱;第二個參數是在連接埠開啟時阻塞等待的毫秒數。*/
} catch (PortInUseException e) {}
try {
inputStream = serialPort.getInputStream();/*擷取連接埠的輸入資料流對象*/
} catch (IOException e) {}
try {
serialPort.addEventListener(this);/*註冊一個SerialPortEventListener事件來監聽串口事件*/
} catch (TooManyListenersException e) {}
serialPort.notifyOnDataAvailable(true);/*資料可用*/
try {
serialPort.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);/*設定串口初始化參數,依次是傳輸速率,資料位元,停止位和校正*/
} catch (UnsupportedCommOperationException e) {}
readThread = new Thread(this);
readThread.start();
}
public void run() {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {}
}
//串口事件
public void serialEvent(SerialPortEvent event) {
switch(event.getEventType()) {
case SerialPortEvent.BI:/*Break interrupt,通訊中斷*/
case SerialPortEvent.OE:/*Overrun error,溢位錯誤*/
case SerialPortEvent.FE:/*Framing error,傳幀錯誤*/
case SerialPortEvent.PE:/*Parity error,校正錯誤*/
case SerialPortEvent.CD:/*Carrier detect,偵測載波*/
case SerialPortEvent.CTS:/*Clear to send,清除發送*/
case SerialPortEvent.DSR:/*Data set ready,資料裝置就緒*/
case SerialPortEvent.RI:/*Ring indicator,響鈴指示*/
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:/*Output buffer is empty,輸出緩衝區清空*/
break;
case SerialPortEvent.DATA_AVAILABLE:/*Data available at the serial port,連接埠有可用資料。讀到緩衝數組,輸出到終端*/
byte[] readBuffer = new byte[20];
try {
while (inputStream.available() > 0) {
int numBytes = inputStream.read(readBuffer);
}
System.out.print(new String(readBuffer));
} catch (IOException e) {}
break;
}
}
}
(PS:不推薦Thread的這種用法,詳見《Core Java VolumeII》)
寫串口的常式
把字串"Hello, world!/n"寫到系統的第一個串口
import java.io.*;
import java.util.*;
import javax.comm.*;
public class SimpleWrite {
static Enumeration portList;
static CommPortIdentifier portId;
static String messageString = "Hello, world!/n";
static SerialPort serialPort;
static OutputStream outputStream;
public static void main(String[] args) {
portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
// if (portId.getName().equals("COM1")) {
if (portId.getName().equals("/dev/term/a")) {
try {
serialPort = (SerialPort)
portId.open("SimpleWriteApp", 2000);
} catch (PortInUseException e) {}
try {
outputStream = serialPort.getOutputStream();
} catch (IOException e) {}
try {
serialPort.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {}
try {
outputStream.write(messageString.getBytes());
} catch (IOException e) {}
}
}
}
}
}
上面兩個常式都經過了簡化,在開啟連接埠,並且傳輸結束後沒有關閉資料流和串口。在常式中我們看到CommPortIdentifier提供了開啟通訊連接埠的方法open,但卻沒有相應關閉連接埠的方法,關閉連接埠需要調用javax.comm.CommPort類的close()。CommPort是這個包中的一個進階抽象,它定義了連接埠可作的各種事情:擷取I/O資料流對象,控制緩衝區大小,調整輸入的處理。