程式扎記: [ Java 文章收集 ] Java 對 RS232 的操作

標籤

2011年7月31日 星期日

[ Java 文章收集 ] Java 對 RS232 的操作

前言 : 
在暑假作業學長要求寫一個對 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
  1. package john.comm;  
  2.   
  3. import gnu.io.CommPortIdentifier;  
  4. import java.util.Enumeration;  
  5.   
  6. public class ListAvailablePorts {  
  7.     public void list() {    
  8.         Enumeration ports = CommPortIdentifier.getPortIdentifiers();    
  9.             
  10.         while(ports.hasMoreElements())    
  11.         {  
  12.             CommPortIdentifier cpIdentifier = (CommPortIdentifier)ports.nextElement();  
  13.             System.out.println(cpIdentifier.getName());              
  14.         }  
  15.     }    
  16.      
  17.     public static void main(String[] args) {    
  18.         new ListAvailablePorts().list();    
  19.     }  
  20. }  

執行結果會將所有 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 :
  1. package john.comm;  
  2.   
  3. import gnu.io.CommPortIdentifier;  
  4. import gnu.io.SerialPort;  
  5.   
  6. public class RS232Example {  
  7.     public void connect(String portName) throws Exception {    
  8.         CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);    
  9.      
  10.         if (portIdentifier.isCurrentlyOwned()) {    
  11.             System.out.println("Port in use!");    
  12.         } else {    
  13.             // points who owns the port and connection timeout    
  14.             SerialPort serialPort = (SerialPort) portIdentifier.open("RS232Example"2000);    
  15.                 
  16.             // setup connection parameters    
  17.             serialPort.setSerialPortParams(    
  18.                 38400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);    
  19.      
  20.             // setup serial port writer    
  21.             CommPortSender.setWriterStream(serialPort.getOutputStream());    
  22.                 
  23.             // setup serial port reader    
  24.             new CommPortReceiver(serialPort.getInputStream()).start();    
  25.         }    
  26.     }    
  27.         
  28.     public static void main(String[] args) throws Exception {    
  29.             
  30.         // connects to the port which name (e.g. COM1) is in the first argument    
  31.         new RS232Example().connect(args[0]);    
  32.             
  33.         // send HELO message through serial port using protocol implementation    
  34.         CommPortSender.send(new ProtocolImpl().getMessage("HELO"));    
  35.     }   
  36. }  

接著我們來看看 Data Receiver. 由 COM Port 接收到的資料為 Raw data (bytes), 如果沒有經過特定的 Protocol 解析對我們並沒有特別意義, 所以我們這邊建立了 ProtocolImpl 類別來為我們分析出有用的資訊 : 

- CommPortReceiver.java
  1. package john.comm;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5.   
  6. public class CommPortReceiver extends Thread{  
  7.     InputStream in;    
  8.     Protocol protocol = new ProtocolImpl();    
  9.      
  10.     public CommPortReceiver(InputStream in) {    
  11.         this.in = in;    
  12.     }    
  13.         
  14.     public void run() {    
  15.         try {    
  16.             int b;    
  17.             while(true) {    
  18.                     
  19.                 // if stream is not bound in.read() method returns -1    
  20.                 while((b = in.read()) != -1) {    
  21.                     protocol.onReceive((byte) b);    
  22.                 }    
  23.                 protocol.onStreamClosed();    
  24.                     
  25.                 // wait 10ms when stream is broken and check again    
  26.                 sleep(10);    
  27.             }    
  28.         } catch (IOException e) {    
  29.             e.printStackTrace();    
  30.         } catch (InterruptedException e) {    
  31.             e.printStackTrace();    
  32.         }     
  33.     }  
  34. }  

為了未來有不同的訊息解析可能, 我們定義了介面 Protocol 作為後續其他訊息解析的解耦合, 避免資料的收集與特定的 ProtocolImpl 過度耦合造成過度依賴 : 

- Protocol.java :
  1. package john.comm;  
  2.   
  3. public interface Protocol {  
  4.      // protocol manager handles each received byte    
  5.     void onReceive(byte b);    
  6.         
  7.     // protocol manager handles broken stream    
  8.     void onStreamClosed();   
  9. }  

接著我們來看 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 :
  1. package john.comm;  
  2.   
  3. public class ProtocolImpl implements Protocol{  
  4.     byte[] buffer = new byte[1024];    
  5.     int tail = 0;    
  6.         
  7.     public void onReceive(byte b) {    
  8.         // simple protocol: each message ends with new line    
  9.         if (b=='\n') {    
  10.             onMessage();    
  11.         } else {    
  12.             buffer[tail] = b;    
  13.             tail++;    
  14.         }    
  15.     }    
  16.      
  17.     public void onStreamClosed() {    
  18.         onMessage();    
  19.     }    
  20.         
  21.     /*  
  22.      * When message is recognized onMessage is invoked   
  23.      */    
  24.     private void onMessage() {    
  25.         if (tail!=0) {    
  26.             // constructing message    
  27.             String message = getMessage(buffer, tail);    
  28.             System.out.println("RECEIVED MESSAGE: " + message);    
  29.                 
  30.             // this logic should be placed in some kind of     
  31.             // message interpreter class not here    
  32.             if ("HELO".equals(message)) {    
  33.                 CommPortSender.send(getMessage("OK"));    
  34.             } else if ("OK".equals(message)) {    
  35.                 CommPortSender.send(getMessage("OK ACK"));    
  36.             }    
  37.             tail = 0;    
  38.         }    
  39.     }    
  40.         
  41.     // helper methods     
  42.     public byte[] getMessage(String message) {    
  43.         return (message+"\n").getBytes();    
  44.     }    
  45.         
  46.     public String getMessage(byte[] buffer, int len) {    
  47.         return new String(buffer, 0, tail);    
  48.     }  
  49. }  

接著來看看 Sender (CommPortSender) 的部分, 它的功能便是透過 send(byte[] bytes) 將傳進的 byte array 傳送到之前建立的 COM Port 中 : 

- CommPortSender.java :
  1. package john.comm;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.OutputStream;  
  5.   
  6. public class CommPortSender {  
  7.     static OutputStream out;    
  8.       
  9.     public static void setWriterStream(OutputStream out) {    
  10.         CommPortSender.out = out;    
  11.     }    
  12.         
  13.     public static void send(byte[] bytes) {    
  14.         try {    
  15.             System.out.println("SENDING: " + new String(bytes, 0, bytes.length));    
  16.                 
  17.             // sending through serial port is simply writing into OutputStream    
  18.             out.write(bytes);    
  19.             out.flush();    
  20.         } catch (IOException e) {    
  21.             e.printStackTrace();    
  22.         }    
  23.     }      
  24. }  

接著我們將專案編譯並導出 JavaSerial.jar. 然後我們將使用 COM3 作為測試的 COM Port, 接著我們在 Console 鍵入以下命令 :
 
 
接著你便可以透過這個簡單範例開發出屬於你自己的 COM Port 程式. 如果原先你是使用 Java Communication API, 也可以很容易的移植到 RXTX 的 library 上. 

補充說明 : 
* RS232 in Java for Windows 
* Java串行端口通訊​​技術慨論 
* JAVA RS232實作

18 則留言:

  1. 能否寄source code 給我參考,謝謝!我的email:garyyang1616@gmail.com

    回覆刪除
  2. 爲什麽我在eclipse下顯示gnu包不存在?

    回覆刪除
    回覆
    1. 錯誤找到了,一是我的系統是64位,而下載的dll文件是32位。第二按博主的方法沒法把RXRTcomm導入到類庫中....

      刪除
  3. 請問讀取部分是否也需要寫到main中?

    回覆刪除
    回覆
    1. 讀取的部分是指 "CommPortReceiver"?
      如果是的會應該是不用, 因為它是一支線程, 在 "RS232Example" 執行時呼叫方法 connect(String portName) 便會將之呼叫起來, 便在背景等待接收訊息...不知道有回答到你的問題嗎?

      刪除
    2. 謝謝您的回覆! 不好意思~我一開始也是認為在物件建立時建構子會被執行, 但是我中斷下在CommPortReceiver上卻無法debug到? 可以請教這是怎麼回事嗎? 再次感謝您!

      刪除
    3. 我將整個 Eclipse Project 放到
      https://www.space.ntu.edu.tw/navigate/s/063E678770B1439584A5E32973B70E83QQY

      我剛剛有試著跑並加 debug message, 是可以在 CommPortReceiver 上看到, 所以不是很清楚你遇到的狀況, 但建議你可以參考我上傳專案裡 john.comm.Main 代碼 先跑 'list' 看看能不能列出所有 available 的 port, 再用 'test ' 跑跑看能否正常的 Send/Receive...另外要注意的是我專案用的 RXTX 是 64bit 版本, 根據你 local 的環境可能要換成對應的版本如 32bits.

      刪除
    4. 版大你好!!先感謝你熱心分享~~

      小弟是java新手 但是目前有要用到serial port的東西 ~~想拿版大的來測試裝置是否有回應

      若要直接使用版大的程式該怎麼使用呢??我用jcreator開main檔run跑出
      錯誤: 找不到或無法載入主要類別 john.test.CommPortReceiver

      我這邊是否有何環境沒設定好呢 或是 得用CMD才能打開 ?感謝大大!!

      刪除
    5. 看起來像是沒有載入對應的 jar 檔. 你可以在 https://www.space.ntu.edu.tw/navigate/s/063E678770B1439584A5E32973B70E83QQY
      下載整個專案 -> 使用 Eclipse import 進去 (Eclipse 可在 http://www.eclipse.org/downloads/ 下載)
      或是將 source code 移到你的 IDE JCreator 上. 再按上面說明跑跑看.

      刪除
  4. 作者已經移除這則留言。

    回覆刪除
  5. 我import後 結果圖 http://ppt.cc/6JaG

    這樣是正確的嗎@@" 而且這樣的話要怎麼和裝置溝通~

    一樣像你將專案編譯導出 JavaSerial.jar 去CMD使用嗎 ?! 3Q

    回覆刪除
    回覆
    1. 你可以直接使用 JavaSerial.jar 並透過 Command line 下命令測試; 或是改代碼 john.comm.Main 並修改 main 函數:
      args = new String[]{"list"};
      ...
      成你要給的參數...

      刪除
  6. 版主你好,我匯入的時候專案會有驚嘆號,請問是哪裡出錯呢

    回覆刪除
    回覆
    1. 可能是缺少 library 或是 library path 錯誤, 你在專案點擊右鍵->Java Build Path->Libraries 看看有沒有 library 路徑設錯.

      刪除
  7. 版主您好, 請問為什麼我會一直偵錯到 import gnu無法解析??

    回覆刪除
  8. 小弟常常看您的Java文章,受益良多,小弟把RXTX 2.2的檔留下(32/64),以讓各位利用…

    https://drive.google.com/file/d/0B2z0BtOkkf7gOFk4bmFDanlsbEE/view?usp=sharing

    回覆刪除
  9. RXTX例子:
    http://www.FunP.Net/620849

    回覆刪除

網誌存檔