亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

使用Java進行串口通信

系統 3057 0

引言
?

由于java的平臺無關特性使得串口編程很困難。因為串口需要一個與特定平臺實現的標準的API,而這對于java來說很困難。

不幸的是,Sun在java的串口通信上沒有太多關注。Sun已經定義了一個叫做 JavaComm 的串口通信API,但它的實現卻不是java SE(標準版)的一部分。Sun只為少數java平臺提供了一個參考實現。特別是在2005年后Sun悄悄地退出了對Windows平臺的 JavaComm 支持。那些被漏掉的平臺可以使用第三方實現。除了Sun實施過的幾次小的維護外, JavaComm 幾乎沒有被維護過,但那也只是Sun為了應付購買他們Sun Ray瘦客戶端和在此平臺采用JavaComm而放棄Windows支持的用戶所施加的壓力。

?

在這種情況下,而且在Sun起初沒有為Linux提供 JavaComm 實現的現實下(從2006開始才有的),導致了免費軟件 RxTx 庫的開發。 RxTx 支持許多平臺而不僅僅是Linux。它可以與JavaComm結合起來使用(RxTx提供了特定硬件驅動),也可以獨立使用。當作為 JavaComm 的驅動時,通過JCL( JavaComm for Linux )將 JavaComm RxTx 橋接起來。JCL是RxTx發布的一部分。

Sun對 JavaComm 的忽視和 JavaComm 的特殊的編程模型為 JavaComm 贏得了不實用的壞名聲。而 RxTx -如果不作為 JavaComm 的驅動-提供了豐富的接口,但卻不是標準化的。 RxTx 比已存在的 JavaComm 實現支持更多的平臺。最近,RxTx經過修改已經提供了和 JavaComm 相同的接口,只是包名與Sun的包名不同。

?

所以,應用程序到底應該使用哪個庫呢?如果需要較大的移植性, JavaComm 是較好的選擇。如果某一特定平臺沒有 JavaComm 實現但有RxTx實現的話,RxTx可以作為JavaComm的驅動使用。所以,通過使用 JavaComm ,一個應用可以通過Sun提供的參考實現或與JCL結合的Rxtx實現來支持所有平臺。這樣應用程序可以工作在一個接口上而不需要改動,即標準的JavaComm接口。

本文討論了 JavaComm ? 和 RxTx 。 它主要演示了概念而不是準備運行的代碼。那些只想拷貝代碼的人可以參考與這些包在一起的示例代碼。那些想知道他們在干什么的人在這篇文件章中可能會找到一些有用的信息。

jSSC (Java 簡單串口接口)也會被提及。

?

準備

  • 了解 串口通信和編程 的基礎.

  • 準備好你想與之通信的設備的文檔(例如調制解調器)

  • 搭建好所有硬件和測試環境

  • 使用一個終端程序來手動地與設備通信。這用來確保測試環境搭建已正確搭建,并保證你已經理解了與該設備交互的命令和響應。

  • 下載你想使用的與你的操作系統相符的API實現

  • 閱讀

    • JavaComm 和/或 RxTx安裝說明(并按照它說的去做)

    • API文檔

    • 示例代碼片段

安裝

常見問題

JavaComm ?和? RxTX ?安裝時有一些與眾不同的地方。強烈建議按照安裝說明一點點的安裝。如果安裝說明要求一個jar文件或一個共享庫必須在某一特定的文件夾下,那這就意味著需要嚴肅對待。如果說明要求一個特定的文件或設備需要擁有一個特定的所有權或訪問權,這也意味著需要嚴肅處理。很多安裝問題都只是因為沒有按照安裝說明要求的去做而引起的。

?

特別要注意的是一些版本的 JavaComm 會帶有兩個安裝說明。一個用于java 1.2及以后的版本,一個用于java 1.1版本。使用錯誤的安裝說明會導致不能工作的安裝結果。另一方面,TxTx的一些版本/構件/包會包含不完全的說明。在這種情況下需要獲得相關的RxTx發布的源碼,它包含了完整的安裝說明。

另外要注意Windows的Jdk安裝程序會包含三個java虛擬機,因此會有三個擴展文件夾。

  • 一個作為JDK的組成部分。

  • 一個作為與運行JDK工具的JDK一起的私有JRE的一部分。

  • 一個作為與運行應用程序的JDK一起的公共JRE的一部分。

更有甚者甚至會有第4個jre,它存在于\Windows的目錄結構中。? JavaComm 應該作為擴展被安裝到JDK和所有公共JRE中。

?

Webstart

? ?JavaComm

關于 JavaComm 和RxTx的一個常見問題是它們不支持通過 Java WebStart 進行安裝: JavaComm 的臭名昭著是因為需要將一個稱為 javax.comm.properties 的文件放到JDK lib目錄下,而這是不能通過Java WebStart完成的。很令人沮喪的是,對于該文件的需要是JavaComm中一些不必要的設計/決定所導致的惡果,而JavaComm的設計者們可以很容易地避免這種事情。Sun固執地拒絕修正這個錯誤,他們強調這個機制是必不可少的。他們是在睜著眼說瞎話,特別是當提及JavaComm時,因為Java在很長一段時間內擁有一個專門用于此類意圖的服務提供者架構。

這個屬性文件中的內容只有一行,即提供本地驅動的java類名稱。

      driver=com.sun.comm.Win32Driver
    

?

以下是一個可以通過Web Start部署JavaComm而無視那個傷腦筋的屬性文件的技巧。但它有嚴重的缺陷,并且在部署較新的JavaComm時可能會失?。绻鸖un會做一個新版本的話。

首先,關閉安全管理器(security manager)。Sun的一些蠢貨程序員覺得一遍又一遍地檢查可怕的 javax.comm.properties 文件的存在是很酷的事情,特別是當它最初已經被加載完成之后。這只是單純地檢查文件是否存在而不為其他原因。

      System.setSecurityManager(null);
    

?

然后,當初始化JavaComm API時,手動初始化驅動。

      String?driverName?=?"com.sun.comm.Win32Driver";?//?or?get?as?a?JNLP?property
CommDriver?commDriver?=?(CommDriver)Class.forName(driverName).newInstance();
commDriver.initialize();
    

?

RxTx

RxTx 在某些平臺上需要改變串口設備的所有權和訪問權。這也是無法通過WebStart完成的事。

?

在程序啟動時你應該要求用戶作為超級用戶來執行必要的設置。特別的,RxTx有一個模式匹配算法來驗證“合法”的串口設備名。當某人想使用不標準的設備,例如USB轉串口轉換器(USB-to-serial converter)時,這常會把事情弄砸。這個機制可以被系統屬性屏蔽掉。詳情參照RxTx的安裝說明。

JavaComm API

引言

Java官方串口通信API是JavaComm API。這個API不是Java 2標準版的組成部分,因而此API的實現需要單獨下載。不幸的是,JavaComm沒有獲得Sun足夠的重視,實際的維護時間也不是很長。Sun只是偶爾修復一些不重要的bug,卻沒有做過一些早已過期的重要檢修。

?

本節闡述JavaComm API的基本操作。所提供的源碼保持簡化以展示重點,在實際應用中使用需要完善。

這章的源碼并不是唯一可用的示例代碼。很多例子中都包含JavaComm下載。這些例子幾乎包括比其API文檔更多的關于如何使用它的信息。不幸的是,Sun公司沒有任何真正的教程或一些說明文檔。因此,要理解這個API的機制,學習這些示例代碼是值得的,也仍需要學習這個API文檔。但最好的方法是,學習這些例子并運用它們。由于缺少易用的應用以及理解這些API的編程模型有困難,API通常備受抨擊。相比其名氣和功能,這個API更好,但僅此而已。

?

該API采用回調機制通知程序員有新數據到來。這也是學習這一機制的好主意,而不是依賴詢問端口。不像Java中的其他回調接口(如:在圖形界面),這個接口只允許一個監聽器監聽事件。如果多個監聽器請求監聽幾個事件,主監聽器必須通過分派信息給其他二級監聽器的方式來實現。

下載與安裝

下載

Sun公司的 JavaComm 網頁指向 下載地址 。在這個地址下,Sun當前(2007年)提供了支持Solaris/SPARC、Solaris/x86已經Linux x86的JavaComm 3.0版本。下載需要注冊一個Sun公司的賬戶。下載頁提供了注冊頁的鏈接。注冊的目的并不清楚。在為注冊時,用戶可下載JDK和JREs,但對于這幾乎微不足道的JavaComm,Sun公司在軟件分銷和出口方面卻援引法律條文和政府限制。

?

官方已不再提供JavaComm的Windows版本,并且Sun已經違背了他們自己的產品死亡策略-不能在 Java產品集 中下載。但仍可以 從這 下載2.0的Windows版本(javacom 2.0).
?

安裝

按照與下載一起的安裝說明進行安裝。一些版本的JavaComm 2.0會包含兩個安裝說明。這兩個說明間最明顯的區別是錯誤的那個是用于古老的Java1.1環境的,而適用于Java 1.2(jdk1.2.html)的那個才是正確的。

Windows用戶可能不會意識到他們在不同的地方(一般是3到4個)安裝了同一個VM的副本。一些IDE和Java應用程序可能也會帶有他們自己的私有JRE/JDK。所以JavaComm需要重復安裝到這些VM(JDK和JRE)中,這樣才能夠開發和執行串口應用程序。

?

IDE 都有代表性的IDE的方式來得知一個新的庫(類和文檔)。通常一個庫想JavaComm不僅需要被IDE識別,而且每個使用該庫的項目也應當識別。閱讀IDE的文檔,應該注意老的JavaComm 2.0 版本以及JavaDoc API文檔使用的是Java 1.0 的Java Doc 布局。一些現代的IDE已經不再認識這些結構并不能將JavaComm2.0的文檔集成到他們的幫助系統中了。在這種情況下需要一個外部的瀏覽器來閱讀文檔(推薦活動)

一旦軟件安裝完成,它便會推薦測試樣例和JavaDoc 目錄。構建并運行樣例應用來確認安裝是否正確時很有道理的。樣例程序通常需要一些小的調整以便運行在特別的平臺上(像改寫硬編碼的com端口標識符)。在運行一個樣例程序時最好有一些串行硬件,想cabling,零調制解調器,接線盒,一個真正的貓,PABX以及其他可用的設備。

Serial_Programming:RS-232 Connections ?和 Serial_Programming:Modems and AT Commands ?提供了一些怎樣搭建串行應用開發環境的信息。

?

找到預期的串口

當用JavaComm串行編程時首先要做的三件事

  1. 枚舉JavaComm能訪問的所有串口(端口標識)

  2. 從能訪問的端口標識中選擇預期的端口標識

  3. 通過端口標識取得端口

枚舉和選擇期望的端口標識在同一個循環中完成:

?

      import javax.comm.*;
import java.util.*;
...
//
// Platform specific port name, here= a Unix name
//
// NOTE: On at least one Unix JavaComm implementation JavaComm 
//       enumerates the ports as "COM1" ... "COMx", too, and not
//       by their Unix device names "/dev/tty...". 
//       Yet another good reason to not hard-code the wanted
//       port, but instead make it user configurable.
//
String wantedPortName = "/dev/ttya";
 //
// Get an enumeration of all ports known to JavaComm
//
Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
//
// Check each port identifier if 
//   (a) it indicates a serial (not a parallel) port, and
//   (b) matches the desired name.
//
CommPortIdentifier portId = null;  // will be set if port found
while (portIdentifiers.hasMoreElements())
{
    CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
    if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
       pid.getName().equals(wantedPortName)) 
    {
        portId = pid;
        break;
    }
}
if(portId == null)
{
    System.err.println("Could not find serial port " + wantedPortName);
    System.exit(1);
}
//
// Use port identifier for acquiring the port
//
    

...

?

注意: ?

JavaComm會從與其綁定的特定平臺相關的驅動中獲得一個默認的可訪問串口標識列表。這個列表實際上不能通過JavaComm進行配置。方法CommPortIdentifier.addPortName()是有誤導性的,因為驅動類是與平臺相關的,而且它們的實現不是公共API的組成部分。依賴于驅動,這個端口列表可能會在驅動中進行配置/擴展。所以,如果JavaComm沒有找到某一特定端口,對驅動進行一些改動有時會有所幫助。

某端口標識符一旦被找到,就可以用它取得期望的端口:

?

      //
// Use port identifier for acquiring the port
//
SerialPort port = null;
try {
    port = (SerialPort) portId.open(
        "name", // Name of the application asking for the port 
        10000   // Wait max. 10 sec. to acquire port
    );
} catch(PortInUseException e) {
    System.err.println("Port already in use: " + e);
    System.exit(1);
}
//
// Now we are granted exclusive access to the particular serial
// port. We can configure it and obtain input and output streams.
//
    

?...

?

初始化串口

串口的初始化是很直觀的??梢灾饌€地設置通信參數(波特率,數據位,停止位,奇偶校驗),也可以使用方便的setSerialPortParams(...)方法一下把他們搞定。

作為初始化的一部分,通信的輸入輸出流可以在如下的示例中配置。

?

      import java.io.*;
...
//
// Set all the params.  
// This may need to go in a try/catch block which throws UnsupportedCommOperationException
//
port.setSerialPortParams(
    115200,
    SerialPort.DATABITS_8,
    SerialPort.STOPBITS_1,
    SerialPort.PARITY_NONE);
//
// Open the input Reader and output stream. The choice of a
// Reader and Stream are arbitrary and need to be adapted to
// the actual application. Typically one would use Streams in
// both directions, since they allow for binary data transfer,
// not only character data transfer.
//
BufferedReader is = null;  // for demo purposes only. A stream would be more typical.
PrintStream    os = null;
try {
  is = new BufferedReader(new InputStreamReader(port.getInputStream()));
} catch (IOException e) {
  System.err.println("Can't open input stream: write-only");
  is = null;
}
//
// New Linux systems rely on Unicode, so it might be necessary to
// specify the encoding scheme to be used. Typically this should
// be US-ASCII (7 bit communication), or ISO Latin 1 (8 bit
// communication), as there is likely no modem out there accepting
// Unicode for its commands. An example to specify the encoding
// would look like:
//
//     os = new PrintStream(port.getOutputStream(), true, "ISO-8859-1");
//
os = new PrintStream(port.getOutputStream(), true);
  //
// Actual data communication would happen here
// performReadWriteCode();
//
//
// It is very important to close input and output streams as well
// as the port. Otherwise Java, driver and OS resources are not released.
//
if (is != null) is.close();
if (os != null) os.close();
if (port != null) port.close();
    

?

簡單數據傳輸

簡單地寫入數據
?

將數據寫入到串口與基本的java IO一樣簡單。但在你使用AT Hayes 協議時仍有一些注意事項:

  1. 不要在輸出流(OutputStream)中使用prinln(或其他自動附加"\n"的方法)。調制解調器的AT Hayes協議使用"\r\n"作為分隔符(而不考濾底層的操作系統)。

  2. 寫入輸出流之后,如果調制解調器設置了回顯命令行,輸入流的緩沖區會存有發送的指令的復述(有換行)和另一個換行("AT"指令的響應)。所以做為寫操作的一部分,要確保清理輸入流中的這種信息(實際上它可以用于查錯)。

  3. 當使用Reader/Writer(不是個好主意)時,最少要設置字符編碼為US-ASCII而不是使用系統平臺的默認編碼,否則程序可能不會運行。

  4. 因為使用調制解調器的主要操作是傳輸原始數據,與調制解調器的通信應該使用輸入/輸出流,而不是Reader/Writer.

Clipboard

To do:

  • 解釋如何在同一個流中混合二進制與字符的輸入輸出

    ?

  • 修改示例程序使其使用流

?

      // Write to the output 
os.print("AT");
os.print("\r\n"); // Append a carriage return with a line feed

is.readLine(); // First read will contain the echoed command you sent to it. In this case: "AT"
is.readLine(); // Second read will remove the extra line feed that AT generat
    

??

?

簡單的數據讀?。ㄝ喸儯?

如果你正確的使用了寫操作(如上所述),讀操作只需簡單的一條命令。

      //?Read?the?response
String?response?=?is.readLine();?//?if?you?sent?"AT"?then?response?==?"OK"
    

簡單讀寫的問題

上一節中演示的簡單串口讀寫有很嚴重的缺陷。所有的操作都是通過阻塞I/O完成的。這意味著當

  • 沒有可讀數據時,或

  • 輸出緩沖區滿(設備不能接受更多數據)時

讀寫方法(在前面示例中的是os.print()或is.readLine())不會返回, 導致應用程序被暫停。更準確地說,讀寫線程被阻塞了。如果那個線程是應用程序主線程的話,應用程序會停止直到阻塞條件被釋放(即有可讀數據到達或設備重新接受數據)。

除非應用程序是最原始的那種,否則程序被阻塞是絕不允許的。例如,最起碼也要能讓用戶取消通信操作。這需要使用非阻塞I/O或異步I/O。然而JavaComm是基于Java的標準阻塞I/O系統(InputStream,OutputStream)的,但可以采用稍后展示的一個變形技巧。

所謂的"變形技巧"是JavaComm通過事件通知機制為異步I/O提供的有限的支持。但在Java中要在阻塞I/O的基礎上實現非阻塞I/O的常用解決方案是使用線程。對于串口寫操作這個方案是切實可行的,強烈建議使用一個單獨的線程對串口進行寫操作-盡管已經使用了事件通知機制,這稍后會做出解釋。

讀操作也應該在一個單獨的線程中進行處理,但如果采用了JavaComm的事件通知機制這也不是必須的。總結:

操作 ???? 架構

讀操作? 使用事件通知和/或單獨線程

寫操作? 都要使用單獨線程,可選用事件通知機制

接下來的部分會介紹一些其他細節。

?

事件驅動串行通信

引言

JavaComm API提供了事件通知機制以克服阻塞I/O帶來的問題。但在這個典型的Sun方式中這個機制也有問題的。

原則上一個應用程序可以注冊事件監聽器到一個特定的串口以接收發生在這個端口上的重要事件的通知。讀寫數據的兩個最有意思的事件類型是?

  • javax.comm.SerialPortEvent.DATA_AVAILABLE和

  • javax.comm.SerialPortEvent.OUTPUT_BUFFER_EMPTY.

但這也帶來了兩個問題:

  1. 每個串口只能注冊一個事件監聽器。這會強制程序員編寫"巨大"的監聽器,它以接收到的事件類型來區分要進行的操作。

  2. OUTPUT_BUFFER_EMPTY是一個可選的事件類型。Sun在文檔中隱晦地提到JavaComm的實現不一定都會支持產生這個事件類型。

在進行詳細討論前,下一節將會演示實現和注冊一個串口事件處理器的主要方式。要記住一個串口只能有一個事件處理器,而且它要處理所有可能的事件。

設置串行事件處理器

?

      import javax.comm.*;

/**
 * Listener to handle all serial port events.
 *
 * NOTE: It is typical that the SerialPortEventListener is implemented
 *       in the main class that is supposed to communicate with the
 *       device. That way the listener has easy access to state information
 *       about the communication, e.g. when a particular communication
 *       protocol needs to be followed.
 *
 *       However, for demonstration purposes this example implements a
 *       separate class.
 */ 
class SerialListener implements SerialPortEventListener {

    /**
     * Handle serial events. Dispatches the event to event-specific
     * methods.
     * @param event The serial event
     */
    @Override
    public void serialEvent(SerialPortEvent event){

        //
        // Dispatch event to individual methods. This keeps this ugly
        // switch/case statement as short as possible.
        //
        switch(event.getEventType()) {
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                outputBufferEmpty(event);
                break;

            case SerialPortEvent.DATA_AVAILABLE:
                dataAvailable(event);
                break;

/* Other events, not implemented here ->
            case SerialPortEvent.BI:
                breakInterrupt(event);
                break;

            case SerialPortEvent.CD:
                carrierDetect(event);
                break;

            case SerialPortEvent.CTS:
                clearToSend(event);
                break;

            case SerialPortEvent.DSR:
                dataSetReady(event);
                break;

            case SerialPortEvent.FE:
                framingError(event);
                break;

            case SerialPortEvent.OE:
                overrunError(event);
                break;

            case SerialPortEvent.PE:
                parityError(event);
                break;
            case SerialPortEvent.RI:
                ringIndicator(event);
                break;
<- other events, not implemented here */

        }
    }

    /**
     * Handle output buffer empty events.
     * NOTE: The reception of this event is optional and not
     *       guaranteed by the API specification.
     * @param event The output buffer empty event
     */
    protected void outputBufferEmpty(SerialPortEvent event) {
        // Implement writing more data here
    }

    /**
     * Handle data available events.
     *
     * @param event The data available event
     */
    protected void dataAvailable(SerialPortEvent event) {
        // implement reading from the serial port here
    }
}
    

?監聽器一旦實現,即可用來監聽特定的串口事件。要做到如此,需要為串口添加一個監聽器實例。此外,每個事件類型的接收需要進行單獨申請。

?

?

      SerialPort port = ...;
...
//
// Configure port parameters here. Only after the port is configured it
// makes sense to enable events. The event handler might be called immediately
// after an event is enabled.
...

//
// Typically, if the current class implements the SerialEventListener interface
// one would call
//
//        port.addEventListener(this);
//
// but for our example a new instance of SerialListener is created:
//
port.addEventListener(new SerialListener());

//
// Enable the events we are interested in
//
port.notifyOnDataAvailable(true);
port.notifyOnOutputEmpty(true);

/* other events not used in this example ->
port.notifyOnBreakInterrupt(true);
port.notifyOnCarrierDetect(true);
port.notifyOnCTS(true);
port.notifyOnDSR(true);
port.notifyOnFramingError(true);
port.notifyOnOverrunError(true);
port.notifyOnParityError(true);
port.notifyOnRingIndicator(true);
<- other events not used in this example */
    

? 數據寫入

?

使用單獨分離的進程進行數據寫入只有一個目的:避免整個應用程序塊由于某一個串口未準備好寫數據而鎖定。

?

一個簡單的,線程安全的環形緩沖區實現

使用一個獨立于主程序線程的線程進行寫操作,表明需要某種方式將要寫入的數據從主應用線程(主線程)提交給寫線程。這可以采用一個共享的異步事件緩沖區,例如一個byte數組。另外,主程序還需要某種方式決定是否可以往數據緩沖區中寫數據或者數據緩沖區是否已經滿了。如果數據緩沖區已滿,表明串口還沒有準備好寫操作,并且要輸出的數據正在排隊。主程序需要在共享數據緩沖區中輪詢可用的新的空閑空間。然而,在主程序輪詢的間隙可以做些其他的事,例如更新用戶界面(GUI),提供一個可以退出發送數據的命令提示等等。

乍一看PipedInputStream/PipedOutputStream對于這種通信是一個不錯的主意。但如果管道流真的有用的話那Sun就不是Sun了。如果與之對應的PipedOutputStream沒有及時清理的話,PipedInputStream會發生阻塞,進而會阻塞應用程序線程。就算使用獨立線程也避免不了。而java.nio.Pipe也有與此相同的問題。它的阻塞行為與平臺相關。而將JavaComm使用的傳統I/O改為NIO也不是很好。

在本文中采用了一個很簡單的同步的環形緩沖區來進行線程間數據傳遞。在現實世界中的應用程序很可能會使用更加復雜的緩沖區實現。例如在一個現實世界的實現需要以輸入輸出流的視角操作緩沖區。

?

如此一個環形緩沖器并沒有什么特別的,在線程處理方面,也沒有特別的屬性。它只是用來這里用來提供數據緩沖的一個簡單數據結構。這里已經實現了該緩沖器,以確保訪問該數據結構是線程安全的。

?

      /**
 * Synchronized ring buffer. 
 * Suitable to hand over data from one thread to another.
 **/
public  class RingBuffer {
    /** internal buffer to hold the data **/
    protected byte buffer[];
    /** size of the buffer **/
    protected int size;
    /** current start of data area **/
    protected int start;
    /** current end of data area **/
    protected int end;

    /**
     * Construct a RingBuffer with a default buffer size of 1k.
     */
    public RingBuffer() {
         this(1024);
    }
    /**
     * Construct a RingBuffer with a certain buffer size.
     * @param size   Buffer size in bytes
     */
    public RingBuffer(int size) {
         this.size = size;
         buffer = new byte[size];
         clear();
    }
    /**
     * Clear the buffer contents. All data still in the buffer is lost.
     */
    public void clear() {
        // Just reset the pointers. The remaining data fragments, if any,
        // will be overwritten during normal operation.
        start = end = 0;
    }
    /**
     * Return used space in buffer. This is the size of the
     * data currently in the buffer.
     * <p>
     * Note: While the value is correct upon returning, it
     * is not necessarily valid when data is read from the 
     * buffer or written to the buffer. Another thread might
     * have filled the buffer or emptied it in the mean time.
     *
     * @return currently amount of data available in buffer
     */
    public int data() {
         return start <= end
                     ? end - start
                     : end - start + size;
    }
    /**
     * Return unused space in buffer. Note: While the value is
     * correct upon returning, it is not necessarily valid when
     * data is written to the buffer or read from the buffer.
     * Another thread might have filled the buffer or emptied
     * it in the mean time.
     *
     * @return currently available free space
     */
    public int free() {
         return start <= end
                     ? size + start - end
                     : start - end;
    }
    /**
     * Write as much data as possible to the buffer.
     * @param data   Data to be written
     * @return       Amount of data actually written
     */
    int write(byte data[]) {
        return write(data, 0, data.length);  
    }
    /**
     * Write as much data as possible to the buffer.
     * @param data   Array holding data to be written
     * @param off    Offset of data in array
     * @param n      Amount of data to write, starting from .
     * @return       Amount of data actually written
     */
    int write(byte data[], int off, int n) {
        if(n <= 0) return 0;
        int remain = n;
        // @todo check if off is valid: 0= <= off < data.length; throw exception if not
        int i = Math.min(remain, (end < start ? start : buffer.length) - end);
        if(i > 0) {
             System.arraycopy(data, off, buffer, end, i);
             off    += i;
             remain -= i;
             end    += i;
        }
        i = Math.min(remain, end >= start ? start : 0);
        if(i > 0 ) {
             System.arraycopy(data, off, buffer, 0, i);
             remain -= i;
             end = i;
        }
        return n - remain;
    }

    /**
     * Read as much data as possible from the buffer.
     * @param data   Where to store the data
     * @return       Amount of data read
     */
    int read(byte data[]) {
        return read(data, 0, data.length);  
    }
    /**
     * Read as much data as possible from the buffer.
     * @param data   Where to store the read data
     * @param off    Offset of data in array
     * @param n      Amount of data to read
     * @return       Amount of data actually read
     */
    int read(byte data[], int off, int n) {
        if(n <= 0) return 0;
        int remain = n;
        // @todo check if off is valid: 0= <= off < data.length; throw exception if not
        int i = Math.min(remain, (end < start ? buffer.length : end) - start);
        if(i > 0) {
             System.arraycopy(buffer, start, data, off, i);
             off    += i;
             remain -= i;
             start  += i;
             if(start >= buffer.length) start = 0;
        }
        i = Math.min(remain, end >= start ? 0 : end);
        if(i > 0 ) {
             System.arraycopy(buffer, 0, data, off, i);
             remain -= i;
             start = i;
        }
        return n - remain;
    }
}
    

?通過使用該環形緩沖器,你現在可以以一種可控的方式從一個線程提交數據到另一個線程。當然,其他線程安全、非阻塞式的方法同樣可以。這里的關鍵點在于當緩沖區已滿或者緩沖區為空時,數據的讀寫不會造成堵塞。

?

?

根據在? "建立一個串口事件處理器" 小節演示的事件處理器的輪廓,你可以使用在" 一個簡單的,線程安全的環形緩沖區實現 "小節中介紹的共享環形緩沖區以支持OUTPUT_BUFFER_EMPTY事件。不是所有的JavaComm實現都支持這個事件,所以這段代碼可能永遠也不會被調用。但如果可以,它是確保最佳數據吞吐量的一部分,因為它可以使串口不會長時間處于空閑狀態。

事件監聽器的輪廓需要提供一個outputBufferEmpty()方法,它的實現如下:

?

      RingBuffer dataBuffer = ... ;
   /**
    * Handle output buffer empty events.
    * NOTE: The reception is of this event is optional and not
    *       guaranteed by the API specification.
    * @param event The output buffer empty event
    */
   protected void outputBufferEmpty(SerialPortEvent event) {
   }
    

下面的示例假設數據的目的地是某個文件。當數據到達時它會被從串口中取出并寫入目的文件。這只是個精簡化的視圖,因為實際上你需要檢查數據的EOF標識以將調制解調器(通常稱為“貓”)重置為命令模式。

?

      import javax.comm.*;
...
InputStream is = port.getInputStream();
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("out.dat"));
/**
 * Listen to port events
 */ 
class FileListener implements SerialPortEventListener {

    /**
     * Handle serial event.
     */
    void serialEvent(SerialPortEvent e) {
        SerialPort port = (SerialPort) e.getSource();

        //
        // Discriminate handling according to event type
        //
        switch(e.getEventType()) {
        case SerialPortEvent.DATA_AVAILABLE:

            //
            // Move all currently available data to the file
            //
            try {
                 int c;
                 while((c = is.read()) != -1) {
                       out.write(c);
                 }
            } catch(IOException ex) {
                 ...
            }
            break;
        case ...:
            ...
            break;
        ...
        }
       if (is != null) is.close();
       if (port != null) port.close();
    }
    

?

調制解調器控制

?

JavaComm主要關心的是一個串口的處理和串口上數據的傳送。它不懂或者提供對高層協議的支持,比如Hayes調制解調指令通常用來控制客戶級的貓。這不是JavaComm的任務,也就不是一個bug。

如同其他特別的串行設備,如果希望由JavaComm控制一個貓,那么就得在JavaComm上寫必要的代碼。頁面" Hayes-compatible Modems and AT Commands "提供了處理Hayes貓的必要的基本信息。

?

一些操作系統,像Windows或某一Linux對于如何配置一個特別類型或牌子的貓的控制命令提供了一個或多或少標準的方式。例如,Windows貓的“驅動”通常只是注冊入口,描述一個個別的貓(真正的驅動是一個通用的串行調制解調驅動)。JavaComm沒法獲取這樣的操作系統的具體的數據。因此,要么必須提供一個單獨的java工具來允許用戶為使用個別的貓去配置一個應用,要么就添加一些相應平臺的(本地的)代碼。

?

RxTx

概述與版本

由于Sun沒有為Linux提供JavaComm的參考實現,人們為java和linux開發了RxTx。后來RxTx被移植到了其他平臺。最新版本的RxTx已知可運行在100種以上平臺,包括Linux, Windows, Mac OS, Solaris 和其他操作系統。

RxTx可以獨立于JavaComm API使用,也可以作為所謂的Java Comm API服務者。如果采用后者還需要一個稱為JCL的封裝包。JCL和RxTx通常與Linux/Java發行版打包在一起,或者JCL完全與代碼集成在一起。所以,在一個個地下載他們之前,看一看Linux發行版的CD是值得的。

?

由于Sun對JavaComm的有限的支持和不適當的文檔,放棄JavaComm API,轉而直接使用RxTx而不是通過JCL封裝包似乎成為了一種趨勢。然而RxTx的文檔是很稀少的。特別是RxTx開發者喜歡將他們的版本和包內容弄得一團糟(例如使用或未使用集成的JCL)。從1.5版本開始,RxTx包含了公共JavaComm類的替代類。由于法律原因,他們沒有在java.comm包中,而是在gui.io包下。然而現存的兩個版本的打包內容有很大差別。

  • RxTx 2.0

  • 這個版本的RxTx 主要用作JavaComm提供者。它應該源自于RxRx 1.4,這是RxTx添加gui.io包之前的版本。

  • RxTx 2.1

  • 這個版本的RxTx包含了一個完整的代替java.comm的gnu.io包。它應該源自于RxTx 1.5,這是支持gnu.io的起始版本。

因此,如果你想對原始的JavaComm API 編程的話你需要

  1. ??? Sun JavaComm 通用版。撰寫本文時實際上就是Unix包(包含對各種類Unix系統的支持,像Linux或Solaris)即使在Windows上,這個Unix包也是需要用來提供java.comm的通用實現的。只用用Java實現那部分會被用到,然而Unix的本地庫會被忽略的。

  2. RxTx 2.0, 為了能在JavaComm通用版本下有不同的提供者,不同于JavaComm包下的那個。然而,如果你只想用gnu.io替換包,那么你只需要

  • ??????? RxTx 2.1

?

將一個JavaComm應用轉換成RxTx應用

如果你是對Sun公司放棄使JavaComm支持Windows這一行為感到失望的眾多成員中的一個,那么你應該將你的JavaComm應用轉到RxTx上來。如你在上面所看到的,這里有兩種方式來完成這件事,假設你已經安裝了RxTx的某一版本,那么下面的選項可選其一:

  1. 使用RxTx 2.0作為JavaComm接口的實現

  2. 將應用移植到RxTx 2.1環境上

上面的第一項在前面已經解釋,第二項也相當簡單。對于需要將JavaComm應用移植到RxTx 2.1上來的人,只需要將應用源代碼中所有對“java.comm”包的引用換成“gnu.io”包,如果原始的JavaComm應用編寫恰當,這里就沒有其他的事情需要去做。

在Unix平臺上,RxTx 2.1甚至提供了工具“contrib/ChangePackage.sh”去在源代碼樹形結構中執行全局的替換,這樣的替換在其他的平臺很容易使用支持重構功能的IDE(集成開發環境)來完成。

另請參閱

?

使用Java進行串口通信


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产99在线视频 | 国产一级特黄高清免费大片 | 亚洲视频精品 | 亚洲国产精品久久卡一 | 国产老年人性视频 | 欧美一级毛片片aa视频 | 私拍在线| 99在线国产 | 亚洲一区中文字幕在线 | 久久亚洲影院 | 啪啪免费网站入口链接 | 久久中文网中文字幕 | 亚洲欧美国产一区二区三区 | 久久伊人精品综合观看99 | 伊在人亚洲香蕉精品播放 | 青青青爽在线视频观看大全 | 国产乱人视频在线播放不卡 | 亚洲欧美一级久久精品 | 国产伦乱 | 国产福利一区二区精品视频 | 成人欧美精品大91在线 | 国产成人麻豆精品video | 亚洲日本人成网站在线观看 | 狠狠影视| 日韩国产一区二区 | 久草在线久草在线 | 亚洲国产成人久久综合区 | 色视频在线看 | 亚洲精品mv在线观看 | 久久99精品久久久久久 | 五月天激情视频 | 成人免费播放视频777777 | 免费人成激情视频在线观看冫 | 成人a毛片一级 | 成人国产三级在线播放 | 亚洲美女在线播放 | 永久黄网站色视频免费观看 | 亚洲精品国产经典一区二区 | 欧美一级片在线免费观看 | 亚洲欧美日韩不卡一区二区三区 | 亚洲视频一二三 |