前言 :
在暑假作業學長要求寫一個對 RS232 讀寫的程式. 在 Java Google 到的解決方法有兩個. 一個是透過 Java Communications. 底下是官方網站對該 API 說明的部分擷取 :
The Java Communications API (also known as javax.comm) provides applications access to RS-232 hardware (serial ports) and limited access to IEEE-1284 (parallel ports), SPP mode.
這個是目前Lab 程式使用的 library. 相關的範例代碼與說明, 網路上文章不少, 有興趣的可以自行 Google. 我要使用的是另外一個 Library RXTX. 底下是該 Library 的說明與使用介紹.
SUN DOESN'T SUPPORT SERIAL COMMUNICATION EXTENSION FOR WINDOWS :
很大一部分要介紹 RTXT 的原因是因為原本的 Java Communication API (JavaComm) 原本是由 Sun 公司維護的, 但是現在已經 EOF 並且後續版本只支持 Solaris SPARC, Solaris x86, and Linux x86. 而替代方案便是 RXTX (使用原生開發的的 library 並支援 serial, parallel communication 的 Java solution. 並支援多個 OS 版本包括 Windows. 而這個 Library 是使用 GPL 的 license. 另外使用的 API 與 Sun 的 Java Communication API 是相同的. 故從 Java Communication API 移值到 RXTX 並不用花很大的 effort.)
建立範例程式 :
- 建立開發環境
接著我們便要來寫一個簡單的範例程式, 這邊使用的 IDE 是 Eclipse. 再開始之前請先到 RTXT 上 download library. 接著第一步便是建立一個新的專案 JavaSerial 然後在 Project root 路徑上建立目錄 "lib". 並將下載的檔案解壓縮後, 將 dll 檔丟到剛剛建立的 "lib" 目錄下 :
接著請在 JavaSerial 專案上點擊右鍵 > 選擇 "Properties". 在出現的 Windows 上左方的 Sub Windows 選擇 Java Build Path, 並在右方的 Sub Windows 選擇 "Source". 並設定 "Native Library Location" 為剛剛建立的 "lib" 目錄 :
- 環境測試
接著我們要建立一個簡單的測試代碼, 主要是檢視剛剛建立的開發環境是否成果, 與列出開發主機上的 COM Port. 參考代碼如下 :
- ListAvailablePorts.java
- package john.comm;
-
- import gnu.io.CommPortIdentifier;
- import java.util.Enumeration;
-
- public class ListAvailablePorts {
- public void list() {
- Enumeration ports = CommPortIdentifier.getPortIdentifiers();
-
- while(ports.hasMoreElements())
- {
- CommPortIdentifier cpIdentifier = (CommPortIdentifier)ports.nextElement();
- System.out.println(cpIdentifier.getName());
- }
- }
-
- public static void main(String[] args) {
- new ListAvailablePorts().list();
- }
- }
執行結果會將所有 COM Port 列出 :
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version = RXTX-2.1-7
COM3
COM6
COM7
...略...
- PHYSICAL EQUIPMENT
在開始開發 Com Port 的程式前, 必須知道我們要進行溝通的 End device 的規格與 Protocol, 這樣才能正確的進行資料的傳遞與讀取. 底下是幾個提供參考的問題 :
* do I have a RS232 specification of communication protocol between end-point device and application which I develop?
* is my laptop or PC equipped with serial ports (one or two?)
* do I have access to the end-point device or maybe I should write an emulator of end-point device?
- RS232 APPLICATION EXAMPLE
接著我們要使用 RXTX library 開發簡單的 RS232 程式. 你可以基於這個範例延伸應用到複雜的使用情況. 底下是使用 RXTX library 的 flow :
* receive CommPortIdentifier from the system with specific name (e.g. COM1) - received identifier could be in use by any other application - check it invoking portIdentifier.isCurrentlyOwned() method
* if received CommPortIdentifier is not in use by different application, you can open CommPort (which in our case is instance of SerialPort)
* the next step is to pass all setup communication parameters: baudrate, number of data bits, number of stop bits and possible parity bit
* the last step is to retrieve InputStream and OutputStream for sending and receiving raw bytes
底下代碼為符合使用 RXTX library 的 flow, 並使用Console 第一個參數來決定開啟的 COM Port 並建立 Thread (CommPortReceiver) 來接收 RS232 的 InputStream :
- RS232Example.java :
- package john.comm;
-
- import gnu.io.CommPortIdentifier;
- import gnu.io.SerialPort;
-
- public class RS232Example {
- public void connect(String portName) throws Exception {
- CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
-
- if (portIdentifier.isCurrentlyOwned()) {
- System.out.println("Port in use!");
- } else {
-
- SerialPort serialPort = (SerialPort) portIdentifier.open("RS232Example", 2000);
-
-
- serialPort.setSerialPortParams(
- 38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
-
-
- CommPortSender.setWriterStream(serialPort.getOutputStream());
-
-
- new CommPortReceiver(serialPort.getInputStream()).start();
- }
- }
-
- public static void main(String[] args) throws Exception {
-
-
- new RS232Example().connect(args[0]);
-
-
- CommPortSender.send(new ProtocolImpl().getMessage("HELO"));
- }
- }
接著我們來看看 Data Receiver. 由 COM Port 接收到的資料為 Raw data (bytes), 如果沒有經過特定的 Protocol 解析對我們並沒有特別意義, 所以我們這邊建立了 ProtocolImpl 類別來為我們分析出有用的資訊 :
- CommPortReceiver.java
- package john.comm;
-
- import java.io.IOException;
- import java.io.InputStream;
-
- public class CommPortReceiver extends Thread{
- InputStream in;
- Protocol protocol = new ProtocolImpl();
-
- public CommPortReceiver(InputStream in) {
- this.in = in;
- }
-
- public void run() {
- try {
- int b;
- while(true) {
-
-
- while((b = in.read()) != -1) {
- protocol.onReceive((byte) b);
- }
- protocol.onStreamClosed();
-
-
- sleep(10);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
為了未來有不同的訊息解析可能, 我們定義了介面 Protocol 作為後續其他訊息解析的解耦合, 避免資料的收集與特定的 ProtocolImpl 過度耦合造成過度依賴 :
- Protocol.java :
- package john.comm;
-
- public interface Protocol {
-
- void onReceive(byte b);
-
-
- void onStreamClosed();
- }
接著我們來看 Raw data 的解析部分, 在這個例子每個 Message 都是以 "換行" 符號作為區隔, 因此我們將換行間的 Raw data (bytes) 丟到 String 物件的建構子. 而實現方式就是在ProtocolImpl.onReceive() 會不斷收到一個個 byte 的 Raw data. 當收到 "換行字符" ('\n') 便會呼叫 ProtocolImpl.onMessage(), 而該函式便是將到目前為止收到的 byte buffer 傳送到 String 建構子並將該字串列印出來. 如果收到 "HELO" 則回覆 "OK", 如果收到 "OK" 則回覆 "OK ACK" :
- ProtocolImpl.java :
- package john.comm;
-
- public class ProtocolImpl implements Protocol{
- byte[] buffer = new byte[1024];
- int tail = 0;
-
- public void onReceive(byte b) {
-
- if (b=='\n') {
- onMessage();
- } else {
- buffer[tail] = b;
- tail++;
- }
- }
-
- public void onStreamClosed() {
- onMessage();
- }
-
-
-
-
- private void onMessage() {
- if (tail!=0) {
-
- String message = getMessage(buffer, tail);
- System.out.println("RECEIVED MESSAGE: " + message);
-
-
-
- if ("HELO".equals(message)) {
- CommPortSender.send(getMessage("OK"));
- } else if ("OK".equals(message)) {
- CommPortSender.send(getMessage("OK ACK"));
- }
- tail = 0;
- }
- }
-
-
- public byte[] getMessage(String message) {
- return (message+"\n").getBytes();
- }
-
- public String getMessage(byte[] buffer, int len) {
- return new String(buffer, 0, tail);
- }
- }
接著來看看 Sender (CommPortSender) 的部分, 它的功能便是透過 send(byte[] bytes) 將傳進的 byte array 傳送到之前建立的 COM Port 中 :
- CommPortSender.java :
- package john.comm;
-
- import java.io.IOException;
- import java.io.OutputStream;
-
- public class CommPortSender {
- static OutputStream out;
-
- public static void setWriterStream(OutputStream out) {
- CommPortSender.out = out;
- }
-
- public static void send(byte[] bytes) {
- try {
- System.out.println("SENDING: " + new String(bytes, 0, bytes.length));
-
-
- out.write(bytes);
- out.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
接著我們將專案編譯並導出 JavaSerial.jar. 然後我們將使用 COM3 作為測試的 COM Port, 接著我們在 Console 鍵入以下命令 :
接著你便可以透過這個簡單範例開發出屬於你自己的 COM Port 程式. 如果原先你是使用 Java Communication API, 也可以很容易的移植到 RXTX 的 library 上.
補充說明 :
* RS232 in Java for Windows
* Java串行端口通訊技術慨論
* JAVA RS232實作