ホームページ > Java > &#&チュートリアル > Java シリアル ポート プログラミングの詳細な解釈

Java シリアル ポート プログラミングの詳細な解釈

高洛峰
リリース: 2016-12-19 16:52:36
オリジナル
1638 人が閲覧しました

FAQ

JavaComm と RxTX のインストールにはいくつかの違いがあります。インストール手順に段階的に従うことを強くお勧めします。インストール手順で、jar ファイルまたは共有ライブラリを特定のフォルダーに配置する必要がある場合は、それを真剣に受け止める必要があることを意味します。指示で特定のファイルまたはデバイスに特定の所有権またはアクセス権が必要な場合、これはそれを真剣に受け止める必要があることも意味します。インストールの問題の多くは、単にインストール手順に従わないことによって発生します。


JavaComm の一部のバージョンには 2 つのインストール手順が付属していることに注意することが重要です。 1 つは Java 1.2 以降のバージョン用、もう 1 つは Java 1.1 バージョン用です。間違ったインストール手順を使用すると、インストールが機能しなくなる可能性があります。一方、TxTx の一部のバージョン/ビルド/パッケージには不完全な手順が含まれています。この場合、完全なインストール手順が含まれている、関連する RxTx ディストリビューションのソース コードを入手する必要があります。

また、Windows Jdk インストーラーには 3 つの Java 仮想マシンが含まれるため、拡張フォルダーが 3 つあることにも注意してください。

1 つは JDK に不可欠な部分です。

JDK ツールを実行する JDK とともにプライベート JRE の一部。

アプリケーションを実行する JDK とともに公開された JRE の一部。

さらに、Windows ディレクトリ構造には 4 つ目の jre さえ存在します。 JavaComm は、JDK およびすべてのパブリック JRE に拡張機能としてインストールする必要があります。

Webstart

JavaComm

JavaComm と RxTx に関するよくある質問は、Java WebStart によるインストールをサポートしていないということです。JavaComm は、javax.comm.properties というファイルを JDK lib ディレクトリに配置する必要があることで有名です。 Java WebStart を通じて実行できます。このファイルが必要なのは、JavaComm の設計者であれば簡単に回避できたであろう JavaComm における不必要な設計/決定の結果であることはイライラします。 Sun はこのエラーを修正することを頑なに拒否し、このメカニズムは不可欠であると主張しました。特に JavaComm に関しては、彼らは目を見開いてナンセンスなことを話しています。Java には長い間、そのような目的のための専用のサービス プロバイダー アーキテクチャがあったからです。

このプロパティ ファイルの内容は 1 行のみで、ローカル ドライバーの Java クラス名を提供します。

driver=com.sun.comm.Win32Driver
ログイン後にコピー


これは、Web Start 経由で JavaComm をデプロイし、その厄介なプロパティ ファイルを無視するためのトリックです。しかし、これには重大な欠陥があり、Sun が新しいバージョンを作成した場合、新しい JavaComm をデプロイするときに失敗する可能性があります。

まず、セキュリティマネージャーを閉じます。 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-シリアルコンバータなどの非標準デバイスを使用したい場合、これにより問題が発生することがよくあります。このメカニズムはシステム プロパティによって無効にできます。詳細については、RxTx のインストール手順を参照してください。
JavaComm API
はじめに

Javaの公式シリアル通信APIはJavaComm APIです。この API は Java 2 Standard Edition の一部ではないため、この API の実装は個別にダウンロードする必要があります。残念ながら、JavaComm は Sun から十分な注目を集めておらず、実際のメンテナンス時間はそれほど長くありませんでした。 Sun は、重要でないバグを時々修正するだけですが、長い間待ち望まれていたいくつかの重要なオーバーホールは行いません。


このセクションでは、JavaComm API の基本的な操作について説明します。提供されるソース コードは重要なポイントを示すために簡略化されており、実際のアプリケーションで使用するには改善する必要があります。

この章のソース コードは、利用可能な唯一のサンプル コードではありません。多くの例には JavaComm ダウンロードが含まれます。例には、API ドキュメントよりも使用方法に関する詳細な情報がほとんど含まれています。残念ながら、Sun には実際のチュートリアルやドキュメントがありません。したがって、この API の仕組みを理解するには、サンプル コードを学習する価値がありますが、それでも API ドキュメントを学習する必要があります。しかし、最善の方法は、これらの例を研究して適用することです。 API は、使いやすいアプリケーションが存在しないこと、および API のプログラミング モデルを理解することが難しいため、しばしば批判されます。評判や機能と比較すると、この API は優れていますが、それ以上のものではありません。


この API はコールバック メカニズムを使用して、新しいデータが到着したことをプログラマーに通知します。 ask ポートに依存する代わりにメカニズムを学ぶこともお勧めします。 Java の他のコールバック インターフェイス (グラフィカル インターフェイスなど) とは異なり、このインターフェイスでは 1 つのリスナーのみがイベントをリッスンできます。複数のリスナーが複数のイベントのリッスンを要求した場合、プライマリ リスナーは他のセカンダリ リスナーに情報をディスパッチすることでこれを行う必要があります。

ダウンロードとインストール

ダウンロード

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串行编程时首先要做的三件事

枚举JavaComm能访问的所有串口(端口标识)

从能访问的端口标识中选择预期的端口标识

通过端口标识取得端口

枚举和选择期望的端口标识在同一个循环中完成:

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 协议时仍有一些注意事项:

不要在输出流(OutputStream)中使用prinln(或其他自动附加"\n"的方法)。调制解调器的AT Hayes协议使用"\r\n"作为分隔符(而不考滤底层的操作系统)。

写入输出流之后,如果调制解调器设置了回显命令行,输入流的缓冲区会存有发送的指令的复述(有换行)和另一个换行("AT"指令的响应)。所以做为写操作的一部分,要确保清理输入流中的这种信息(实际上它可以用于查错)。

当使用Reader/Writer(不是个好主意)时,最少要设置字符编码为US-ASCII而不是使用系统平台的默认编码,否则程序可能不会运行。

因为使用调制解调器的主要操作是传输原始数据,与调制解调器的通信应该使用输入/输出流,而不是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 generates as output
ログイン後にコピー

简单的数据读取(轮询)

如果你正确的使用了写操作(如上所述),读操作只需简单的一条命令。

// 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.

但这也带来了两个问题:

每个串口只能注册一个事件监听器。这会强制程序员编写"巨大"的监听器,它以接收到的事件类型来区分要进行的操作。

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 编程的话你需要

        Sun JavaComm 通用版。撰写本文时实际上就是Unix包(包含对各种类Unix系统的支持,像Linux或Solaris)即使在Windows上,这个Unix包也是需要用来提供java.comm的通用实现的。只用用Java实现那部分会被用到,然而Unix的本地库会被忽略的。

    RxTx 2.0, 为了能在JavaComm通用版本下有不同的提供者,不同于JavaComm包下的那个。然而,如果你只想用gnu.io替换包,那么你只需要将一个JavaComm应用转换成RxTx应用。

如果你是对Sun公司放弃使JavaComm支持Windows这一行为感到失望的众多成员中的一个,那么你应该将你的JavaComm应用转到RxTx上来。如你在上面所看到的,这里有两种方式来完成这件事,假设你已经安装了RxTx的某一版本,那么下面的选项可选其一:

    使用RxTx 2.0作为JavaComm接口的实现

    将应用移植到RxTx 2.1环境上

上記の最初の項目は以前に説明しましたが、2 番目の項目も非常に簡単です。 JavaComm アプリケーションを RxTx 2.1 に移植する必要がある場合は、元の JavaComm アプリケーションが適切に記述されていれば、アプリケーションのソース コード内の「java.comm」パッケージへのすべての参照を「gnu.io」パッケージに置き換えるだけです。いいえ、他のことを行う必要があります。

Unix プラットフォームでは、RxTx 2.1 は、ソース コード ツリー構造でグローバル置換を実行するツール「contrib/ChangePackage.sh」も提供します。このような置換は、リファクタリング機能 (統合開発) をサポートする IDE を使用して他のプラットフォームでも簡単に使用できます。環境)を選択して完了します。



Java シリアル ポート プログラミング関連記事の詳細な説明については、PHP 中国語 Web サイトに注目してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート