最近由於項目的需要,需要用到java串口和windows端java程序的通訊,筆者也是剛剛接觸串口這一模塊,在網上搜索了很多的串口編程實例之類的,幾乎前篇一律吧,但是串口通訊之前的配置是非常重要的,如果配置沒有成功,編程也顯得沒有意義。串口編程主要有兩種接口,第一種是利用sun提供的comm.jar包,這種方式比較古老了,這個包也沒有更新。第二種就是RXTX模式,這種模式其實和comm.jar包的模式幾乎是一樣的。下面就記下我學習和使用此模塊的記錄。RXTX資源包,網上有很多,但要注意的是看你的電腦配置,如果是win64位系統的,則要下載RXTX 64位的資源包,否則會出錯(這里最主要的區別就是rxtxSerial.dll文件不一樣)。
(1)編程環境的搭建和配置
本文是基於RXTX(提供串口和並口通信)開源類庫對串口進行操作的。使用准備(這里的JAVA_HOME是我的jdk的安裝路徑)
1.將RXTXcomm.jar放到%JAVA_HOME%\jre\lib\ext\下,如:E:\jdk\Java1.8\jdk1.8.0_45\jre\lib\ext,或者項目1.右鍵->2.Preperties(首選項)->3.Java Build Path->4.Libraries->5.Add External JARs引入(建議使用后面一種,前面一種有時候在項目中讀取不出來,在項目打包的過程中如果沒有將jre打包進去的話,項目移植到別的設備上時候是不會成功的)
2.把 rxtxSerial.dll放入到%JAVA_HOME%\jre\bin中,如:E:\jdk\Java1.8\jdk1.8.0_45\jre\bin,或C:\windows\system32
(2)RXTX介紹
RXTX是一個提供串口和並口通信的開源java類庫,由該項目發布的文件均遵循LGPL協議。
RXTX項目提供了Windows,Linux,Mac os X,Solaris操作系統下的兼容javax.comm串口通訊包API的實現,為其他開發人員在此類系統下開發串口應用提供了相當的方便。
RXTX的使用上與sun提供的comm.jar基本相同,編程時最明顯的不同是要包含的包名由javax.comm.*改成了gnu.io.*
RxtxAPI 的核心是抽象的CommPort類(用於描述一個被底層系統支持的端口的抽象類,它包含一些高層的IO控制方法,這些方法對於所有不同的通訊端口來說是通用的)及其兩個子類:SerialPort類和ParallePort類。其中,SerialPort類是用於串口通信的類,ParallePort類是用於並行口通信的類。CommPort類還提供了常規的通信模式和方法,例如:getInputStream( )方法和getOutputStream( )方法,專用於與端口上的設備進行通信。
然而,這些類的構造方法都被有意的設置為非公有的(non-public)。所以,不能直接構造對象,而是先通過靜態的CommPortIdentifer.getPortIdentifiers()獲得端口列表,再從這個端口列表中選擇所需要的端口,並調用CommPortIdentifer對象的Open( )方法,這樣,就能得到一個CommPort對象。當然,還要將這個CommPort對象的類型轉換為某個非抽象的子類,表明是特定的通訊設備,該子類可以是SerialPort類和ParallePort類中的一個。下面將分別對CommPortIdentifier類,串口類SerialPort進行詳細的介紹。
接口
CommDriver可負載設備(the loadable device)驅動程序接口的一部分
CommPortOwnershipListener傳遞各種通訊端口的所有權事件
ParallelPortEventListener傳遞並行端口事件
SerialPortEventListener傳遞串行端口事件
類
CommPort通訊端口
CommPortIdentifier通訊端口管理
ParallelPort並行通訊端口
ParallelPortEvent並行端口事件
SerialPortRS-232串行通訊端口
SerialPortEvent 串行端口事件
異常類
NoSuchPortException當驅動程序不能找到指定端口時拋出
PortInUseException當碰到指定端口正在使用中時拋出
UnsupportedCommOperationException驅動程序不允許指定操作時拋出
CommPortIdentifier類
這個類主要用於對通信端口進行管理和設置,是對端口進行訪問控制的核心類,主要包括以下方法:
addPortName(String,int, CommDriver) 添加端口名到端口列表里
addPortOwnershipListener(CommPortOwnershipListener)添加端口擁有的監聽器
removePortOwnershipListener(CommPortOwnershipListener)移除端口擁有的監聽器
getCurrentOwner()獲取當前占有端口的對象或應用程序
getName()獲取端口名稱
getPortIdentifier(CommPort)獲取指定打開的端口的CommPortIdentifier類型對象
getPortIdentifier(String)獲取以參數命名的端口的CommPortIdentifier類型對象
getPortIdentifiers()獲取系統中的端口列表
getPortType()獲取端口的類型
isCurrentlyOwned()判斷當前端口是否被占用
open(FileDescriptor)用文件描述的類型打開端口
open(String,int) 打開端口,兩個參數:程序名稱,延遲時間(毫秒數)
SerialPort類
這個類用於描述一個RS-232串行通信端口的底層接口,它定義了串口通信所需的最小功能集。通過它,用戶可以直接對串口進行讀、寫及設置工作。
SerialPort類中關於串口參數的靜態成員變量說明:
DATABITS_5 數據位為5
DATABITS_6 數據位為6
DATABITS_7 數據位為7
DATABITS_8 數據位為8
PARITY_NONE 空格檢驗
PARITY_ODD 奇檢驗
PARITY_EVEN 偶檢驗
PARITY_MARK 標記檢驗
PARITY_SPACE 無檢驗
STOPBITS_1 停止位為1
STOPBITS_2 停止位為2
STOPBITS_1_5 停止位為1.5
SerialPort類中關於串口參數的方法說明:
getBaudRate()得到波特率
getParity()得到檢驗類型
getDataBits()得到數據位數
getStopBits()得到停止位數
setSerialPortParams(int,int, int, int) 設置串口參數依次為(波特率,數據位,停止位,奇偶檢驗)
SerialPort類中關於事件的靜態成員變量說明:
BI Break interrupt 通訊中斷
FE Framing error 幀錯誤
CD Carrier detect 載波偵聽
OE Overrun error 溢位錯誤
CTS Clear to send 清除發送
PE Parity error 奇偶檢驗錯誤
DSR Data set ready 數據設備准備好
RI Ring indicator 響鈴偵測
DATA_AVAILABLE 串口中的可用數據
OUTPUT_BUFFER_EMPTY 輸出緩沖區已清空
SerialPort類中關於事件的方法說明:
isCD()是否有載波
isCTS()是否清除以傳送
isDSR()數據是否備妥
isDTR()是否數據端備妥
isRI()是否響鈴偵測
isRTS()是否要求傳送
addEventListener(SerialPortEventListener)向SerialPort對象中添加串口事件監聽器
removeEventListener()移除SerialPort對象中的串口事件監聽器
notifyOnBreakInterrupt(boolean)設置中斷事件true有效,false無效
notifyOnCarrierDetect(boolean)設置載波監聽事件true有效,false無效
notifyOnCTS(boolean)設置清除發送事件true有效,false無效
notifyOnDataAvailable(boolean)設置串口有數據的事件true有效,false無效
notifyOnDSR(boolean)設置數據備妥事件true有效,false無效
notifyOnFramingError(boolean)設置發生錯誤事件true有效,false無效
notifyOnOutputEmpty(boolean)設置發送緩沖區為空事件true有效,false無效
notifyOnParityError(boolean)設置發生奇偶檢驗錯誤事件true有效,false無效
notifyOnRingIndicator(boolean)設置響鈴偵測事件true有效,false無效
getEventType()得到發生的事件類型返回值為int型
sendBreak(int)設置中斷過程的時間,參數為毫秒值
setRTS(boolean)設置或清除RTS位
setDTR(boolean)設置或清除DTR位
SerialPort中的其他常用方法說明:
close()關閉串口
getOutputStream()得到OutputStream類型的輸出流
getInputStream()得到InputStream類型的輸入流
(3)列出串口以及其它通信端口類以及方法,下面貼出demo
import java.util.Enumeration; import java.util.HashSet; import gnu.io.CommPort; import gnu.io.CommPortIdentifier; import gnu.io.PortInUseException; public class ListPort { /** * @Description:列出所有可用串口 * @author:dengchaoqun * @date:2015-8-29 上午11:34:04 */ public static void listPorts() { HashSet<CommPortIdentifier> portSet = getAvailableSerialPorts(); for (CommPortIdentifier comm : portSet) { System.out.println(comm.getName() + " - " + getPortTypeName(comm.getPortType())); } } /** * @Description:列出所有通信端口 * @author:dengchaqun * @date:2015-8-29 下午2:06:17 */ @SuppressWarnings("unchecked") public static void listCommPorts() { CommPortIdentifier.getPortIdentifiers(); /* * 不帶參數的getPortIdentifiers方法可以獲得一個枚舉對象,該對象包含了 * 系統中每個端口的CommPortIdentifier對象。注意這里的端口不僅僅是指串口,也包括並口。 * 這個方法還可以帶參數,getPortIdentifiers(CommPort)獲得已經被應用程序打開的端口 * 相對應的CommPortIdentifier對象。getPortIdentifier(String portName) * 獲取指定端口名(比如“COM1”)的CommPortIdentifier對象。 */ java.util.Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers(); while (portEnum.hasMoreElements()) { CommPortIdentifier portIdentifier = portEnum.nextElement(); System.out.println(portIdentifier.getName() + " - " + getPortTypeName(portIdentifier.getPortType())); } } /** * @Description:獲取通信端口類型名稱 * @author:Lu * @date:2015-8-29 上午11:35:32 */ public static String getPortTypeName(int portType) { switch (portType) { case CommPortIdentifier.PORT_I2C: return "I2C"; case CommPortIdentifier.PORT_PARALLEL: // 並口 return "Parallel"; case CommPortIdentifier.PORT_RAW: return "Raw"; case CommPortIdentifier.PORT_RS485: // RS485端口 return "RS485"; case CommPortIdentifier.PORT_SERIAL: // 串口 return "Serial"; default: return "unknown type"; } } /** * @Description:獲取所有可用的串口集合 * @author:dengchaoqun * @date:2015-8-29 上午11:37:54 */ @SuppressWarnings("unchecked") public static HashSet<CommPortIdentifier> getAvailableSerialPorts() { HashSet<CommPortIdentifier> h = new HashSet<CommPortIdentifier>(); Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); while (portList.hasMoreElements()) { CommPortIdentifier com = (CommPortIdentifier) portList.nextElement(); switch (com.getPortType()) { case CommPortIdentifier.PORT_SERIAL: try { // open:(應用程序名【隨意命名】,阻塞時等待的毫秒數) /* * open方法打開通訊端口,獲得一個CommPort對象,它使程序獨占端口。 * 如果端口正被其他應用程序占用,將使用CommPortOwnershipListener事件機制 * 傳遞一個PORT_OWNERSHIP_REQUESTED事件。 * 每個端口都關聯一個InputStream和一個OutputStream,如果端口是用 * open方法打開的,那么任何的getInputStream都將返回相同的數據流對象,除非 有close被調用。 */ CommPort thePort = com.open(Object.class.getSimpleName(), 50); thePort.close(); h.add(com); } catch (PortInUseException e) { // 不可用串口 System.out.println("Port, " + com.getName() + ", is in use."); } catch (Exception e) { System.err.println("Failed to open port " + com.getName()); e.printStackTrace(); } } } return h; } public static void main(String[] args) { /** * 可列出當前系統所有可用的串口名稱,本機輸出COM1 - Serial, COM2 - Serial.....COM6 - Serial */ listPorts(); listCommPorts(); } }
(4)調試串口輸入輸出demo
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.Enumeration; import java.util.TooManyListenersException; import gnu.io.CommPortIdentifier; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import gnu.io.UnsupportedCommOperationException; public class SerialPortTest1 implements Runnable, SerialPortEventListener { // 檢測系統中可用的通訊端口類 private CommPortIdentifier portId; // 枚舉類型 private Enumeration<CommPortIdentifier> portList; // RS232串口 private SerialPort serialPort; // 輸入輸出流 private InputStream inputStream; private OutputStream outputStream; // 保存串口返回信息 private String test = ""; // 單例創建 private static SerialPortTest1 uniqueInstance = new SerialPortTest1(); // 初始化串口 @SuppressWarnings("unchecked") public void init() { // 獲取系統中所有的通訊端口 portList = CommPortIdentifier.getPortIdentifiers(); // 循環通訊端口 while (portList.hasMoreElements()) { portId = portList.nextElement(); // 判斷是否是串口 if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { // 比較串口名稱是否是"COM3" if ("COM2".equals(portId.getName())) { System.out.println("找到串口COM2"); // 打開串口 try { // open:(應用程序名【隨意命名】,阻塞時等待的毫秒數) serialPort = (SerialPort) portId.open(Object.class.getSimpleName(), 2000); System.out.println("獲取到串口對象,COM2"); //實例化輸入流 inputStream = serialPort.getInputStream(); // 設置串口監聽 serialPort.addEventListener(this); // 設置串口數據時間有效(可監聽) serialPort.notifyOnDataAvailable(true); // 設置串口通訊參數 // 波特率,數據位,停止位和校驗方式 // 波特率2400,偶校驗 serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, // SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); } catch (PortInUseException e) { e.printStackTrace(); } catch (TooManyListenersException e) { e.printStackTrace(); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } } } // 實現接口SerialPortEventListener中的方法 讀取從串口中接收的數據 @Override public void serialEvent(SerialPortEvent event) { switch (event.getEventType()) { case SerialPortEvent.BI: // 通訊中斷 case SerialPortEvent.OE: // 溢位錯誤 case SerialPortEvent.FE: // 幀錯誤 case SerialPortEvent.PE: // 奇偶校驗錯誤 case SerialPortEvent.CD: // 載波檢測 case SerialPortEvent.CTS: // 清除發送 case SerialPortEvent.DSR: // 數據設備准備好 case SerialPortEvent.RI: // 響鈴偵測 case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 輸出緩沖區已清空 break; case SerialPortEvent.DATA_AVAILABLE: // 有數據到達 readComm(); break; default: break; } } // 讀取串口返回信息 public void readComm() { byte[] readBuffer = new byte[1024]; try { inputStream = serialPort.getInputStream(); // 從線路上讀取數據流 int len = 0; while ((len = inputStream.read(readBuffer)) != -1) { System.out.println("實時反饋:" + new String(readBuffer, 0, len).trim() + new Date()); test += new String(readBuffer, 0, len).trim(); break; } System.out.println(test + " "); //closeSerialPort(); } catch (IOException e) { e.printStackTrace(); } } public void closeSerialPort() { uniqueInstance.serialPort.close(); } //向串口發送數據 public void sendMsg(){ String information = "AT\r"; try { //實例化輸出流 outputStream = serialPort.getOutputStream(); } catch (IOException e1) { e1.printStackTrace(); } try { outputStream.write(information.getBytes()); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { init(); sendMsg(); } }
(5)測試上面輸入輸出demo
public class TestDemo { public static void main(String[] args) { Thread thread=new Thread(new SerialPortTest1()); thread.start(); } }
串口測試的效果如圖所示
(6)后面的就是將此demo應用到項目中去,實際情況,根據自己的需求來定