Heim > Java > javaLernprogramm > Detaillierte Interpretation der Programmierung der seriellen Java-Schnittstelle

Detaillierte Interpretation der Programmierung der seriellen Java-Schnittstelle

高洛峰
Freigeben: 2016-12-19 16:52:36
Original
1639 Leute haben es durchsucht

FAQ

Bei der Installation von JavaComm und RxTX gibt es einige Unterschiede. Es wird dringend empfohlen, die Installationsanweisungen Schritt für Schritt zu befolgen. Wenn die Installationsanweisungen erfordern, dass sich eine JAR-Datei oder eine gemeinsam genutzte Bibliothek in einem bestimmten Ordner befinden muss, muss dies ernst genommen werden. Wenn die Anweisungen erfordern, dass eine bestimmte Datei oder ein bestimmtes Gerät über bestimmte Eigentums- oder Zugriffsrechte verfügt, bedeutet dies auch, dass dies ernst genommen werden muss. Viele Installationsprobleme werden einfach dadurch verursacht, dass die Installationsanweisungen nicht befolgt werden.


Es ist wichtig zu beachten, dass einige Versionen von JavaComm zwei Installationsanweisungen enthalten. Eine für Java 1.2 und spätere Versionen und eine für Java 1.1-Versionen. Die Verwendung falscher Installationsanweisungen kann dazu führen, dass die Installation nicht funktioniert. Andererseits enthalten einige Versionen/Builds/Pakete von TxTx unvollständige Anweisungen. In diesem Fall ist es notwendig, den Quellcode der entsprechenden RxTx-Distribution zu beschaffen, der vollständige Installationsanweisungen enthält.

Beachten Sie außerdem, dass das Windows-JDK-Installationsprogramm drei virtuelle Java-Maschinen enthält, sodass es drei Erweiterungsordner gibt.

Einer ist ein integraler Bestandteil von JDK.

Eines, das zusammen mit dem JDK, das die JDK-Tools ausführt, Teil einer privaten JRE ist.

Eines, das zusammen mit dem JDK, das die Anwendung ausführt, Teil der öffentlichen JRE ist.

Darüber hinaus wird es sogar ein viertes JRE geben, das in der Windows-Verzeichnisstruktur vorhanden ist. JavaComm sollte als Erweiterung im JDK und allen öffentlichen JREs installiert werden.

Webstart

JavaComm

Eine häufige Frage zu JavaComm und RxTx ist, dass sie die Installation über Java WebStart nicht unterstützen: JavaComm ist dafür berüchtigt, dass es einen Namen namens javax erfordert. Die Datei „comm.properties“ wird im JDK-Lib-Verzeichnis abgelegt, was nicht über Java WebStart möglich ist. Es ist frustrierend, dass die Notwendigkeit dieser Datei das Ergebnis einiger unnötiger Design-/Entscheidungen in JavaComm ist, die von den JavaComm-Designern leicht hätten vermieden werden können. Sun weigerte sich hartnäckig, diesen Fehler zu korrigieren und bestand darauf, dass dieser Mechanismus unerlässlich sei. Sie reden mit offenen Augen Blödsinn, insbesondere wenn es um JavaComm geht, da es in Java schon seit langem eine dedizierte Service-Provider-Architektur für solche Absichten gibt.

Der Inhalt dieser Eigenschaftendatei besteht nur aus einer Zeile, die den Java-Klassennamen des lokalen Treibers bereitstellt.

driver=com.sun.comm.Win32Driver
Nach dem Login kopieren


Hier ist ein Trick, um JavaComm über Web Start ohne diese lästige Eigenschaftendatei bereitzustellen. Es weist jedoch schwerwiegende Mängel auf und kann bei der Bereitstellung von neuerem JavaComm scheitern – falls Sun jemals eine neue Version erstellt.

Schließen Sie zunächst den Sicherheitsmanager. Einige idiotische Programmierer bei Sun dachten, es wäre cool, immer wieder nach der Existenz der gefürchteten Datei javax.comm.properties zu suchen, insbesondere nachdem sie überhaupt geladen wurde. Dadurch wird lediglich überprüft, ob die Datei vorhanden ist und sonst nichts.

System.setSecurityManager(null);
Nach dem Login kopieren

Initialisieren Sie dann beim Initialisieren der JavaComm-API den Treiber manuell.

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

RxTx

RxTx erfordert auf einigen Plattformen eine Änderung der Besitz- und Zugriffsrechte des seriellen Geräts. Auch dies ist mit WebStart nicht möglich.


Beim Programmstart sollten Sie den Benutzer bitten, als Superuser zu fungieren, um die erforderlichen Einstellungen vorzunehmen. Insbesondere verfügt RxTx über einen Mustervergleichsalgorithmus zur Überprüfung „gültiger“ serieller Gerätenamen. Dies führt häufig zu Problemen, wenn jemand ein nicht standardmäßiges Gerät verwenden möchte, beispielsweise einen USB-zu-Seriell-Konverter. Dieser Mechanismus kann durch Systemeigenschaften deaktiviert werden. Einzelheiten finden Sie in den RxTx-Installationsanweisungen.
JavaComm API
Einführung

Javas offizielle serielle Kommunikations-API ist die JavaComm API. Diese API ist nicht Teil der Java 2 Standard Edition, daher muss die Implementierung dieser API separat heruntergeladen werden. Leider erhielt JavaComm von Sun nicht genügend Aufmerksamkeit und die tatsächliche Wartungszeit war nicht sehr lang. Sun behebt nur gelegentlich einige unwichtige Fehler, hat aber einige wichtige Überarbeitungen, die längst überfällig waren, nicht vorgenommen.


In diesem Abschnitt werden die grundlegenden Vorgänge der JavaComm-API erläutert. Der bereitgestellte Quellcode ist vereinfacht gehalten, um die wichtigsten Punkte darzustellen, und muss für den Einsatz in praktischen Anwendungen verbessert werden.

Der Quellcode für dieses Kapitel ist nicht der einzige verfügbare Beispielcode. Viele Beispiele umfassen JavaComm-Downloads. Die Beispiele enthalten fast mehr Informationen zur Verwendung als die API-Dokumentation. Leider verfügt Sun über keine echten Tutorials oder Dokumentationen. Um die Mechanismen dieser API zu verstehen, lohnt es sich daher, den Beispielcode zu studieren und dennoch die API-Dokumentation zu studieren. Aber der beste Weg ist, diese Beispiele zu studieren und sie anzuwenden. APIs werden oft wegen des Mangels an benutzerfreundlichen Anwendungen und der Schwierigkeit, das Programmiermodell dieser APIs zu verstehen, kritisiert. Im Vergleich zu ihrem Ruf und ihrer Funktionalität ist diese API besser, aber nicht mehr.


Diese API verwendet einen Rückrufmechanismus, um Programmierer darüber zu informieren, dass neue Daten eingetroffen sind. Es ist auch eine gute Idee, den Mechanismus zu erlernen, anstatt sich auf den Ask-Port zu verlassen. Im Gegensatz zu anderen Callback-Schnittstellen in Java (z. B. in grafischen Schnittstellen) erlaubt diese Schnittstelle nur einem Listener, auf Ereignisse zu warten. Wenn mehrere Listener das Abhören mehrerer Ereignisse anfordern, muss der primäre Listener dies tun, indem er Informationen an andere sekundäre Listener weiterleitet.

Herunterladen und installieren

Herunterladen

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.
//
...
Nach dem Login kopieren


初始化串口

串口的初始化是很直观的。可以逐个地设置通信参数(波特率,数据位,停止位,奇偶校验),也可以使用方便的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();
Nach dem Login kopieren

简单数据传输
简单地写入数据

将数据写入到串口与基本的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
Nach dem Login kopieren

简单的数据读取(轮询)

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

// Read the response
String response = is.readLine(); // if you sent "AT" then response == "OK"
Nach dem Login kopieren

简单读写的问题

上一节中演示的简单串口读写有很严重的缺陷。所有的操作都是通过阻塞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
  }
}
Nach dem Login kopieren

监听器一旦实现,即可用来监听特定的串口事件。要做到如此,需要为串口添加一个监听器实例。此外,每个事件类型的接收需要进行单独申请。

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 */
Nach dem Login kopieren

数据写入

使用单独分离的进程进行数据写入只有一个目的:避免整个应用程序块由于某一个串口未准备好写数据而锁定。

一个简单的,线程安全的环形缓冲区实现

使用一个独立于主程序线程的线程进行写操作,表明需要某种方式将要写入的数据从主应用线程(主线程)提交给写线程。这可以采用一个共享的异步事件缓冲区,例如一个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;
  }
}
Nach dem Login kopieren

通过使用该环形缓冲器,你现在可以以一种可控的方式从一个线程提交数据到另一个线程。当然,其他线程安全、非阻塞式的方法同样可以。这里的关键点在于当缓冲区已满或者缓冲区为空时,数据的读写不会造成堵塞。


根据在 "建立一个串口事件处理器"小节演示的事件处理器的轮廓,你可以使用在"一个简单的,线程安全的环形缓冲区实现"小节中介绍的共享环形缓冲区以支持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) {
}
Nach dem Login kopieren

下面的示例假设数据的目的地是某个文件。当数据到达时它会被从串口中取出并写入目的文件。这只是个精简化的视图,因为实际上你需要检查数据的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();
  }
Nach dem Login kopieren

   


调制解调器控制

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环境上

Der erste Punkt oben wurde bereits erklärt, und der zweite Punkt ist auch recht einfach. Für diejenigen, die JavaComm-Anwendungen auf RxTx 2.1 portieren müssen, ersetzen Sie einfach alle Verweise auf das Paket „java.comm“ im Quellcode der Anwendung durch das Paket „gnu.io“. Wenn die ursprüngliche JavaComm-Anwendung ordnungsgemäß geschrieben ist, gibt es keine Andere Dinge müssen erledigt werden.

Auf der Unix-Plattform bietet RxTx 2.1 sogar das Tool „contrib/ChangePackage.sh“, um eine globale Ersetzung in der Quellcode-Baumstruktur durchzuführen, die auf anderen Plattformen einfach zu verwenden ist und die funktionale IDE unterstützt (Integrierte Entwicklungsumgebung) abgeschlossen.



Ausführlichere Erläuterungen zu Artikeln zur Java-Seriell-Port-Programmierung finden Sie auf der chinesischen PHP-Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage