国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

香港云服务器
服務(wù)器之家 - 編程語言 - JAVA教程 - 詳細(xì)解讀Java的串口編程

詳細(xì)解讀Java的串口編程

2020-01-02 14:18goldensun JAVA教程

這篇文章主要介紹了詳細(xì)解讀Java的串口編程,而文中講解的示例主要針對(duì)于JavaComm和RxTx這兩個(gè)庫,需要的朋友可以參考下

常見問題

JavaComm 和 RxTX 安裝時(shí)有一些與眾不同的地方。強(qiáng)烈建議按照安裝說明一點(diǎn)點(diǎn)的安裝。如果安裝說明要求一個(gè)jar文件或一個(gè)共享庫必須在某一特定的文件夾下,那這就意味著需要嚴(yán)肅對(duì)待。如果說明要求一個(gè)特定的文件或設(shè)備需要擁有一個(gè)特定的所有權(quán)或訪問權(quán),這也意味著需要嚴(yán)肅處理。很多安裝問題都只是因?yàn)闆]有按照安裝說明要求的去做而引起的。


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

另外要注意Windows的Jdk安裝程序會(huì)包含三個(gè)java虛擬機(jī),因此會(huì)有三個(gè)擴(kuò)展文件夾。

  •     一個(gè)作為JDK的組成部分。
  •     一個(gè)作為與運(yùn)行JDK工具的JDK一起的私有JRE的一部分。
  •     一個(gè)作為與運(yùn)行應(yīng)用程序的JDK一起的公共JRE的一部分。

更有甚者甚至?xí)械?個(gè)jre,它存在于\Windows的目錄結(jié)構(gòu)中。 JavaComm應(yīng)該作為擴(kuò)展被安裝到JDK和所有公共JRE中。

Webstart

   JavaComm

關(guān)于JavaComm和RxTx的一個(gè)常見問題是它們不支持通過Java WebStart進(jìn)行安裝:JavaComm的臭名昭著是因?yàn)樾枰獙⒁粋€(gè)稱為javax.comm.properties的文件放到JDK lib目錄下,而這是不能通過Java WebStart完成的。很令人沮喪的是,對(duì)于該文件的需要是JavaComm中一些不必要的設(shè)計(jì)/決定所導(dǎo)致的惡果,而JavaComm的設(shè)計(jì)者們可以很容易地避免這種事情。Sun固執(zhí)地拒絕修正這個(gè)錯(cuò)誤,他們強(qiáng)調(diào)這個(gè)機(jī)制是必不可少的。他們是在睜著眼說瞎話,特別是當(dāng)提及JavaComm時(shí),因?yàn)镴ava在很長一段時(shí)間內(nèi)擁有一個(gè)專門用于此類意圖的服務(wù)提供者架構(gòu)。

這個(gè)屬性文件中的內(nèi)容只有一行,即提供本地驅(qū)動(dòng)的java類名稱。

?
1
driver=com.sun.comm.Win32Driver


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

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

?
1
System.setSecurityManager(null);

然后,當(dāng)初始化JavaComm API時(shí),手動(dòng)初始化驅(qū)動(dòng)。

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

RxTx

RxTx在某些平臺(tái)上需要改變串口設(shè)備的所有權(quán)和訪問權(quán)。這也是無法通過WebStart完成的事。


在程序啟動(dòng)時(shí)你應(yīng)該要求用戶作為超級(jí)用戶來執(zhí)行必要的設(shè)置。特別的,RxTx有一個(gè)模式匹配算法來驗(yàn)證“合法”的串口設(shè)備名。當(dāng)某人想使用不標(biāo)準(zhǔn)的設(shè)備,例如USB轉(zhuǎn)串口轉(zhuǎn)換器(USB-to-serial converter)時(shí),這常會(huì)把事情弄砸。這個(gè)機(jī)制可以被系統(tǒng)屬性屏蔽掉。詳情參照RxTx的安裝說明。
JavaComm API
引言

Java官方串口通信API是JavaComm API。這個(gè)API不是Java 2標(biāo)準(zhǔn)版的組成部分,因而此API的實(shí)現(xiàn)需要單獨(dú)下載。不幸的是,JavaComm沒有獲得Sun足夠的重視,實(shí)際的維護(hù)時(shí)間也不是很長。Sun只是偶爾修復(fù)一些不重要的bug,卻沒有做過一些早已過期的重要檢修。


本節(jié)闡述JavaComm API的基本操作。所提供的源碼保持簡化以展示重點(diǎn),在實(shí)際應(yīng)用中使用需要完善。

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


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

下載與安裝

下載

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


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

安裝

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

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


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

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

Serial_Programming:RS-232 Connections 和Serial_Programming:Modems and AT Commands 提供了一些怎樣搭建串行應(yīng)用開發(fā)環(huán)境的信息。

找到預(yù)期的串口

當(dāng)用JavaComm串行編程時(shí)首先要做的三件事

  •     枚舉JavaComm能訪問的所有串口(端口標(biāo)識(shí))
  •     從能訪問的端口標(biāo)識(shí)中選擇預(yù)期的端口標(biāo)識(shí)
  •     通過端口標(biāo)識(shí)取得端口

枚舉和選擇期望的端口標(biāo)識(shí)在同一個(gè)循環(huán)中完成:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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會(huì)從與其綁定的特定平臺(tái)相關(guān)的驅(qū)動(dòng)中獲得一個(gè)默認(rèn)的可訪問串口標(biāo)識(shí)列表。這個(gè)列表實(shí)際上不能通過JavaComm進(jìn)行配置。方法CommPortIdentifier.addPortName()是有誤導(dǎo)性的,因?yàn)轵?qū)動(dòng)類是與平臺(tái)相關(guān)的,而且它們的實(shí)現(xiàn)不是公共API的組成部分。依賴于驅(qū)動(dòng),這個(gè)端口列表可能會(huì)在驅(qū)動(dòng)中進(jìn)行配置/擴(kuò)展。所以,如果JavaComm沒有找到某一特定端口,對(duì)驅(qū)動(dòng)進(jìn)行一些改動(dòng)有時(shí)會(huì)有所幫助。
 
某端口標(biāo)識(shí)符一旦被找到,就可以用它取得期望的端口:
 
//
// 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.
//
...


初始化串口

串口的初始化是很直觀的。可以逐個(gè)地設(shè)置通信參數(shù)(波特率,數(shù)據(jù)位,停止位,奇偶校驗(yàn)),也可以使用方便的setSerialPortParams(...)方法一下把他們搞定。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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();

簡單數(shù)據(jù)傳輸
簡單地寫入數(shù)據(jù)

 

將數(shù)據(jù)寫入到串口與基本的java IO一樣簡單。但在你使用AT Hayes 協(xié)議時(shí)仍有一些注意事項(xiàng):

  •     不要在輸出流(OutputStream)中使用prinln(或其他自動(dòng)附加"\n"的方法)。調(diào)制解調(diào)器的AT Hayes協(xié)議使用"\r\n"作為分隔符(而不考濾底層的操作系統(tǒng))。
  •     寫入輸出流之后,如果調(diào)制解調(diào)器設(shè)置了回顯命令行,輸入流的緩沖區(qū)會(huì)存有發(fā)送的指令的復(fù)述(有換行)和另一個(gè)換行("AT"指令的響應(yīng))。所以做為寫操作的一部分,要確保清理輸入流中的這種信息(實(shí)際上它可以用于查錯(cuò))。
  •     當(dāng)使用Reader/Writer(不是個(gè)好主意)時(shí),最少要設(shè)置字符編碼為US-ASCII而不是使用系統(tǒng)平臺(tái)的默認(rèn)編碼,否則程序可能不會(huì)運(yùn)行。
  •     因?yàn)槭褂谜{(diào)制解調(diào)器的主要操作是傳輸原始數(shù)據(jù),與調(diào)制解調(diào)器的通信應(yīng)該使用輸入/輸出流,而不是Reader/Writer.

Clipboard
 

To do:

    解釋如何在同一個(gè)流中混合二進(jìn)制與字符的輸入輸出


    修改示例程序使其使用流

?
1
2
3
4
5
6
// 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

簡單的數(shù)據(jù)讀取(輪詢)

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

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

簡單讀寫的問題

上一節(jié)中演示的簡單串口讀寫有很嚴(yán)重的缺陷。所有的操作都是通過阻塞I/O完成的。這意味著當(dāng)沒有可讀數(shù)據(jù)時(shí),或輸出緩沖區(qū)滿(設(shè)備不能接受更多數(shù)據(jù))時(shí):

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


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

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

讀操作也應(yīng)該在一個(gè)單獨(dú)的線程中進(jìn)行處理,但如果采用了JavaComm的事件通知機(jī)制這也不是必須的??偨Y(jié):

讀操作使用事件通知和/或單獨(dú)線程;

寫操作都要使用單獨(dú)線程,可選用事件通知機(jī)制。

接下來的部分會(huì)介紹一些其他細(xì)節(jié)。

事件驅(qū)動(dòng)串行通信
引言

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

原則上一個(gè)應(yīng)用程序可以注冊事件監(jiān)聽器到一個(gè)特定的串口以接收發(fā)生在這個(gè)端口上的重要事件的通知。讀寫數(shù)據(jù)的兩個(gè)最有意思的事件類型是

    javax.comm.SerialPortEvent.DATA_AVAILABLE和  javax.comm.SerialPortEvent.OUTPUT_BUFFER_EMPTY.

但這也帶來了兩個(gè)問題:

  1.     每個(gè)串口只能注冊一個(gè)事件監(jiān)聽器。這會(huì)強(qiáng)制程序員編寫"巨大"的監(jiān)聽器,它以接收到的事件類型來區(qū)分要進(jìn)行的操作。
  2.     OUTPUT_BUFFER_EMPTY是一個(gè)可選的事件類型。Sun在文檔中隱晦地提到JavaComm的實(shí)現(xiàn)不一定都會(huì)支持產(chǎn)生這個(gè)事件類型。

在進(jìn)行詳細(xì)討論前,下一節(jié)將會(huì)演示實(shí)現(xiàn)和注冊一個(gè)串口事件處理器的主要方式。要記住一個(gè)串口只能有一個(gè)事件處理器,而且它要處理所有可能的事件。


設(shè)置串行事件處理器

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
  }
}

監(jiān)聽器一旦實(shí)現(xiàn),即可用來監(jiān)聽特定的串口事件。要做到如此,需要為串口添加一個(gè)監(jiān)聽器實(shí)例。此外,每個(gè)事件類型的接收需要進(jìn)行單獨(dú)申請(qǐng)。
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 */

數(shù)據(jù)寫入

使用單獨(dú)分離的進(jìn)程進(jìn)行數(shù)據(jù)寫入只有一個(gè)目的:避免整個(gè)應(yīng)用程序塊由于某一個(gè)串口未準(zhǔn)備好寫數(shù)據(jù)而鎖定。

一個(gè)簡單的,線程安全的環(huán)形緩沖區(qū)實(shí)現(xiàn)

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


乍一看PipedInputStream/PipedOutputStream對(duì)于這種通信是一個(gè)不錯(cuò)的主意。但如果管道流真的有用的話那Sun就不是Sun了。如果與之對(duì)應(yīng)的PipedOutputStream沒有及時(shí)清理的話,PipedInputStream會(huì)發(fā)生阻塞,進(jìn)而會(huì)阻塞應(yīng)用程序線程。就算使用獨(dú)立線程也避免不了。而java.nio.Pipe也有與此相同的問題。它的阻塞行為與平臺(tái)相關(guān)。而將JavaComm使用的傳統(tǒng)I/O改為NIO也不是很好。

在本文中采用了一個(gè)很簡單的同步的環(huán)形緩沖區(qū)來進(jìn)行線程間數(shù)據(jù)傳遞。在現(xiàn)實(shí)世界中的應(yīng)用程序很可能會(huì)使用更加復(fù)雜的緩沖區(qū)實(shí)現(xiàn)。例如在一個(gè)現(xiàn)實(shí)世界的實(shí)現(xiàn)需要以輸入輸出流的視角操作緩沖區(qū)。


如此一個(gè)環(huán)形緩沖器并沒有什么特別的,在線程處理方面,也沒有特別的屬性。它只是用來這里用來提供數(shù)據(jù)緩沖的一個(gè)簡單數(shù)據(jù)結(jié)構(gòu)。這里已經(jīng)實(shí)現(xiàn)了該緩沖器,以確保訪問該數(shù)據(jù)結(jié)構(gòu)是線程安全的。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
 * 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;
  }
}

通過使用該環(huán)形緩沖器,你現(xiàn)在可以以一種可控的方式從一個(gè)線程提交數(shù)據(jù)到另一個(gè)線程。當(dāng)然,其他線程安全、非阻塞式的方法同樣可以。這里的關(guān)鍵點(diǎn)在于當(dāng)緩沖區(qū)已滿或者緩沖區(qū)為空時(shí),數(shù)據(jù)的讀寫不會(huì)造成堵塞。


根據(jù)在 "建立一個(gè)串口事件處理器"小節(jié)演示的事件處理器的輪廓,你可以使用在"一個(gè)簡單的,線程安全的環(huán)形緩沖區(qū)實(shí)現(xiàn)"小節(jié)中介紹的共享環(huán)形緩沖區(qū)以支持OUTPUT_BUFFER_EMPTY事件。不是所有的JavaComm實(shí)現(xiàn)都支持這個(gè)事件,所以這段代碼可能永遠(yuǎn)也不會(huì)被調(diào)用。但如果可以,它是確保最佳數(shù)據(jù)吞吐量的一部分,因?yàn)樗梢允勾诓粫?huì)長時(shí)間處于空閑狀態(tài)。

事件監(jiān)聽器的輪廓需要提供一個(gè)outputBufferEmpty()方法,它的實(shí)現(xiàn)如下:

?
1
2
3
4
5
6
7
8
9
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) {
}

下面的示例假設(shè)數(shù)據(jù)的目的地是某個(gè)文件。當(dāng)數(shù)據(jù)到達(dá)時(shí)它會(huì)被從串口中取出并寫入目的文件。這只是個(gè)精簡化的視圖,因?yàn)閷?shí)際上你需要檢查數(shù)據(jù)的EOF標(biāo)識(shí)以將調(diào)制解調(diào)器(通常稱為“貓”)重置為命令模式。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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();
  }


調(diào)制解調(diào)器控制

JavaComm主要關(guān)心的是一個(gè)串口的處理和串口上數(shù)據(jù)的傳送。它不懂或者提供對(duì)高層協(xié)議的支持,比如Hayes調(diào)制解調(diào)指令通常用來控制客戶級(jí)的貓。這不是JavaComm的任務(wù),也就不是一個(gè)bug。

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


一些操作系統(tǒng),像Windows或某一Linux對(duì)于如何配置一個(gè)特別類型或牌子的貓的控制命令提供了一個(gè)或多或少標(biāo)準(zhǔn)的方式。例如,Windows貓的“驅(qū)動(dòng)”通常只是注冊入口,描述一個(gè)個(gè)別的貓(真正的驅(qū)動(dòng)是一個(gè)通用的串行調(diào)制解調(diào)驅(qū)動(dòng))。JavaComm沒法獲取這樣的操作系統(tǒng)的具體的數(shù)據(jù)。因此,要么必須提供一個(gè)單獨(dú)的java工具來允許用戶為使用個(gè)別的貓去配置一個(gè)應(yīng)用,要么就添加一些相應(yīng)平臺(tái)的(本地的)代碼。

RxTx
概述與版本

由于Sun沒有為Linux提供JavaComm的參考實(shí)現(xiàn),人們?yōu)閖ava和linux開發(fā)了RxTx。后來RxTx被移植到了其他平臺(tái)。最新版本的RxTx已知可運(yùn)行在100種以上平臺(tái),包括Linux, Windows, Mac OS, Solaris 和其他操作系統(tǒng)。

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


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

  •     RxTx 2.0
  •     這個(gè)版本的RxTx 主要用作JavaComm提供者。它應(yīng)該源自于RxRx 1.4,這是RxTx添加gui.io包之前的版本。
  •     RxTx 2.1
  •     這個(gè)版本的RxTx包含了一個(gè)完整的代替java.comm的gnu.io包。它應(yīng)該源自于RxTx 1.5,這是支持gnu.io的起始版本。


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

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

    RxTx 2.0, 為了能在JavaComm通用版本下有不同的提供者,不同于JavaComm包下的那個(gè)。然而,如果你只想用gnu.io替換包,那么你只需要將一個(gè)JavaComm應(yīng)用轉(zhuǎn)換成RxTx應(yīng)用。

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

  •     使用RxTx 2.0作為JavaComm接口的實(shí)現(xiàn)
  •     將應(yīng)用移植到RxTx 2.1環(huán)境上

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

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

延伸 · 閱讀

精彩推薦
663
主站蜘蛛池模板: 亚洲高清在线 | 日韩成人不卡 | 深夜网址| 久久久久久九九 | 欧美一区 | 亚洲五码在线 | 美女88av| 亚洲精品久久久久久下一站 | 日本电影中文字幕 | 亚洲在线电影 | 欧美一级看片a免费观看 | 欧美亚洲国产日韩 | 在线播放亚洲 | 曰韩av| 欧美日韩一区精品 | 青青草国产在线 | 久久中文字幕一区二区三区 | 日韩精品在线播放 | 欧洲在线一区 | 亚洲国产精品网站 | 日韩亚洲一区二区 | 一区二区精品在线 | 欧美精品成人一区二区三区四区 | 国产日韩视频 | 久久高清精品 | 久久中文字幕电影 | 在线成人免费视频 | 亚洲精品欧美在线 | 91成人小视频 | 国产精品久久久久久中文字 | 激情综合在线 | 在线视频亚洲 | 亚洲男人天堂网 | 亚洲一区久久 | 91视频精品| 精品96久久久久久中文字幕无 | 国产一区二区成人 | 久久综合另类激情人妖 | 婷婷综合久久 | 国产精品视频导航 | 91天天综合 |