Java串口通信Word格式文档下载.docx
《Java串口通信Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《Java串口通信Word格式文档下载.docx(41页珍藏版)》请在冰豆网上搜索。
4结束语
嵌入式系统或传感器网络的很多应用和测试都需要通过PC机与嵌入式设备或传感器节点进行通信。
其中,最常用的接口就是RS-232串口和并口(鉴于USB接口的复杂性以及不需要很大的数据传输量,USB接口用在这里还是显得过于奢侈,况且目前除了SUN有一个支持USB的包之外,我还没有看到其他直接支持USB的Java类库)。
SUN的CommAPI分别提供了对常用的RS232串行端口和IEEE1284并行端口通讯的支持。
RS-232-C(又称EIARS-232-C,以下简称RS232)是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。
RS232是一个全双工的通讯协议,它可以同时进行数据接收和发送的工作。
目前,常见的Java串口包有SUN在1998年发布的串口通信API:
comm2.0.jar(Windows下)、comm3.0.jar(Linux/Solaris);
IBM的串口通信API以及一个开源的实现。
鉴于在Windows下SUN的API比较常用以及IBM的实现和SUN的在API层面都是一样的,那个开源的实现又不像两家大厂的产品那样让人放心,这里就只介绍SUN的串口通信API在Windows平台下的使用。
到SUN的网站下载javacomm20-win32.zip,包含的东西如下所示:
按照其使用说明(Readme.html)的说法,要想使用串口包进行串口通信,除了设置好环境变量之外,还要将win32com.dll复制到<
JDK>
\bin目录下;
将comm.jar复制到<
\lib;
把m.properties也同样拷贝到<
\lib目录下。
然而在真正运行使用串口包的时候,仅作这些是不够的。
因为通常当运行“javaMyApp”的时候,是由JRE下的虚拟机启动MyApp的。
而我们只复制上述文件到JDK相应目录下,所以应用程序将会提示找不到串口。
解决这个问题的方法很简单,我们只须将上面提到的文件放到JRE相应的目录下就可以了。
(这个不完全正确肖刚)
安装
这里的所谓安装就是把三个重要的文件放到指定的目录下。
将下载的文件解压缩后,在\javacomm20-win32\commapi目录下有必需的三个文件comm.jar,m.properties和win32comm.dll。
将文件comm.jar拷贝到%JAVA_HOME%\jre\lib\ext;
文件m.properties拷贝到%JAVA_HOME%\jre\lib;
文件win32comm.dll拷贝到%JAVA_HOME%\bin。
注意%JAVA_HOME%是jdk的路径,而非jre。
值得注意的是,在网络应用程序中使用串口API的时候,还会遇到其他更复杂问题。
有兴趣的话,你可以查看CSDN社区中“关于网页上Applet用javacomm20读取客户端串口的问题”的帖子。
这是用于描述一个被底层系统支持的端口的抽象类。
它包含一些高层的IO控制方法,这些方法对于所有不同的通讯端口来说是通用的。
SerialPort和ParallelPort都是它的子类,前者用于控制串行端口而后者用于控这并口,二者对于各自底层的物理端口都有不同的控制方法。
这里我们只关心SerialPort。
这个类主要用于对串口进行管理和设置,是对串口进行访问控制的核心类。
主要包括以下方法
l确定是否有可用的通信端口
l为IO操作打开通信端口
l决定端口的所有权
l处理端口所有权的争用
l管理端口所有权变化引发的事件(Event)
这个类用于描述一个RS-232串行通信端口的底层接口,它定义了串口通信所需的最小功能集。
通过它,用户可以直接对串口进行读、写及设置工作。
大段的文字怎么也不如一个小例子来的清晰,下面我们就一起看一下串口包自带的例子---SerialDemo中的一小段代码来加深对串口API核心类的使用方法的认识。
voidlistPortChoices(){
CommPortIdentifierportId;
Enumerationen=CommPortIdentifier.getPortIdentifiers();
//iteratethroughtheports.
while(en.hasMoreElements()){
portId=(CommPortIdentifier)en.nextElement();
if(portId.getPortType()==CommPortIdentifier.PORT_SERIAL){
System.out.println(portId.getName());
}
portChoice.select(parameters.getPortName());
以上代码可以列举出当前系统所有可用的串口名称,我的机器上输出的结果是COM1和COM3。
(我的电脑输出结果是COM1,LPT1,LPT2后二个是并口)
串口一般有如下参数可以在该串口打开以前配置进行配置:
包括波特率,输入/输出流控制,数据位数,停止位和齐偶校验。
SerialPortsPort;
try{
sPort.setSerialPortParams(BaudRate,Databits,Stopbits,Parity);
//设置输入/输出控制流
sPort.setFlowControlMode(FlowControlIn|FlowControlOut);
}catch(UnsupportedCommOperationExceptione){}
对串口读写之前需要先打开一个串口:
CommPortIdentifierportId=CommPortIdentifier.getPortIdentifier(PortName);
SerialPortsPort=(SerialPort)portId.open("
串口所有者名称"
超时等待时间);
}catch(PortInUseExceptione){//如果端口被占用就抛出这个异常
thrownewSerialConnectionException(e.getMessage());
//用于对串口写数据
OutputStreamos=newBufferedOutputStream(sPort.getOutputStream());
os.write(intdata);
//用于从串口读数据
InputStreamis=newBufferedInputStream(sPort.getInputStream());
intreceivedData=is.read();
读出来的是int型,你可以把它转换成需要的其他类型。
这里要注意的是,由于Java语言没有无符号类型,即所有的类型都是带符号的,在由byte到int的时候应该尤其注意。
因为如果byte的最高位是1,则转成int类型时将用1来占位。
这样,原本是10000000的byte类型的数变成int型就成了1111111110000000,这是很严重的问题,应该注意避免。
终于唠叨完我最讨厌的基础知识了,下面开始我们本次的重点--串口应用的研究。
由于向串口写数据很简单,所以这里我们只关注于从串口读数据的情况。
通常,串口通信应用程序有两种模式,一种是实现SerialPortEventListener接口,监听各种串口事件并作相应处理;
另一种就是建立一个独立的接收线程专门负责数据的接收。
由于这两种方法在某些情况下存在很严重的问题(至于什么问题这里先卖个关子J),所以我的实现是采用第三种方法来解决这个问题。
现在我们来看看事件监听模型是如何运作的
:
l首先需要在你的端口控制类(例如SManager)加上“implementsSerialPortEventListener”
l在初始化时加入如下代码:
SerialPortsPort.addEventListener(SManager);
}catch(TooManyListenersExceptione){
sPort.close();
thrownewSerialConnectionException("
toomanylistenersadded"
);
sPort.notifyOnDataAvailable(true);
l覆写publicvoidserialEvent(SerialPortEvente)方法,在其中对如下事件进行判断:
BI-通讯中断.
CD-载波检测.
CTS-清除发送.
DATA_AVAILABLE-有数据到达.
DSR-数据设备准备好.
FE-帧错误.
OE-溢位错误.
OUTPUT_BUFFER_EMPTY-输出缓冲区已清空.
PE-奇偶校验错.
RI- 振铃指示.
一般最常用的就是DATA_AVAILABLE--串口有数据到达事件。
也就是说当串口有数据到达时,你可以在serialEvent中接收并处理所收到的数据。
然而在我的实践中,遇到了一个十分严重的问题。
首先描述一下我的实验:
我的应用程序需要接收传感器节点从串口发回的查询数据,并将结果以图标的形式显示出来。
串口设定的波特率是115200,川口每隔128毫秒返回一组数据(大约是30字节左右),周期(即持续时间)为31秒。
实测的时候在一个周期内应该返回4900多个字节,而用事件监听模型我最多只能收到不到1500字节,不知道这些字节都跑哪里去了,也不清楚到底丢失的是那部分数据。
值得注意的是,这是我将serialEvent()中所有处理代码都注掉,只剩下打印代码所得的结果。
数据丢失的如此严重是我所不能忍受的,于是我决定采用其他方法。
这个模型顾名思义,就是将接收数据的操作写成一个线程的形式:
publicvoidstartReadingDataThread(){
ThreadreadDataProcess=newThread(newRunnable(){
publicvoidrun(){
while(newData!
=-1){
try{
newData=is.read();
System.out.println(newData);
//其他的处理过程
……….
}catch(IOExceptionex){
System.err.println(ex);
return;
readDataProcess.start();
}
在我的应用程序中,我将收到的数据打包放到一个缓存中,然后启动另一个线程从缓存中获取并处理数据。
两个线程以生产者—消费者模式协同工作,数据的流向如下图所示:
这样,我就圆满解决了丢数据问题。
然而,没高兴多久我就又发现了一个同样严重的问题:
虽然这回不再丢数据了,可是原本一个周期(31秒)之后,传感器节电已经停止传送数据了,但我的串口线程依然在努力的执行读串口操作,在控制台也可以看见收到的数据仍在不断的打印。
原来,由于传感器节点发送的数据过快,而我的接收线程处理不过来,所以InputStream就先把已到达却还没处理的字节缓存起来,于是就导致了明明传感器节点已经不再发数据了,而控制台却还能看见数据不断打印这一奇怪的现象。
唯一值得庆幸的是最后收到数据确实是4900左右字节,没出现丢失现象。
然而当处理完最后一个数据的时候已经快1分半钟了,这个时间远远大于节点运行周期。
这一延迟对于一个实时的显示系统来说简直是灾难!
后来我想,是不是由于两个线程之间的同步和通信导致了数据接收缓慢呢?
于是我在接收线程的代码中去掉了所有处理代码,仅保留打印收到数据的语句,结果依然如故。
看来并不是线程间的通信阻碍了数据的接收速度,而是用线程模型导致了对于发送端数据发送速率过快的情况下的数据接收延迟。
这里申明一点,就是对于数据发送速率不是如此快的情况下前面者两种模型应该还是好用的,只是特殊情况还是应该特殊处理。
痛苦了许久(Boss天天催我L)之后,偶然的机会,我听说TinyOS中(又是开源的)有一部分是和我的应用程序类似的串口通信部分,于是我下载了它的1.x版的Java代码部分,参考了它的处理方法。
解决问题的方法说穿了其实很简单,就是从根源入手。
根源不就是接收线程导致的吗,那好,我就干脆取消接收线程和作为中介的共享缓存,而直接在处理线程中调用串口读数据的方法来解决问题(什么,为什么不把处理线程也一并取消?
----都取消应用程序界面不就锁死了吗?
所以必须保留)于是程序变成了这样:
publicbyte[]getPack(){
while(true){
//PacketLength为数据包长度
byte[]msgPack=newbyte[PacketLength];
for(inti=0;
i<
PacketLength;
i++){
if((newData=is.read())!
=-1){
msgPack[i]=(byte)newData;
System.out.println(msgPack[i]);
returnmsgPack;
在处理线程中调用这个方法返回所需要的数据序列并处理之,这样不但没有丢失数据的现象行出现,也没有数据接收延迟了。
这里唯一需要注意的就是当串口停止发送数据或没有数据的时候is.read()一直都返回-1,如果一旦在开始接收数据的时候发现-1就不要理它,继续接收,直到收到真正的数据为止。
本文介绍了串口通信的基本知识,以及常用的几种模式。
通过实践,提出了一些问题,并在最后加以解决。
值得注意的是对于第一种方法,我曾将传感器发送的时间由128毫秒增加到512毫秒,仍然有很严重的数据丢失现象发生,所以如果你的应用程序需要很精密的结果,传输数据的速率又很快的话,就最好不要用第一种方法。
对于第二种方法,由于是线程导致的问题,所以对于不同的机器应该会有不同的表现,对于那些处理多线程比较好的机器来说,应该会好一些。
但是我的机器是Inter奔四3.0双核CPU+512DDR内存,这样都延迟这么厉害,还得多强的CPU才行啊?
所以对于数据量比较大的传输来说,还是用第三种方法吧。
不过这个世界问题是很多的,而且未知的问题比已知的问题多的多,说不定还有什么其他问题存在,欢迎你通过下面的联系方式和我一起研究。
关于java使用javacomm20-win32实践总结
由于这几天要通过java调用通过串口或并口连接的硬件资源,所以我就要用到和底层的硬件进行通讯。
通过RS-232的通讯协议,了解电脑和外设是怎样进行通讯的。
在应用中我们也可以通过JNI来实现(详情请见
我们经过串口和外设通讯,下面我就以串口为例进行解说。
1)我们要准备相应的设备。
电脑,外设,通过数据线把他们连接起来。
2)检验外设到底是用的那个COM口和电脑通讯的.
也就是说,他们有没有真确的连接上。
我们可以通过下载串口通讯口测试软件,我用的是"
SuperCommTool.exe"
的绿色软件,进行测试的。
这软件很适应,如果选中的某个COM已经被使用了,它会给你一个相应的提示(端口以被占用)。
如果你不知道到底是使用的那个端口,那么你可以通过superCommTool软件一个一个的试,如果正常的话,那么你可以看到有数据显示在数据接收窗口。
也许,有些主板的串口坏了,那么你就要买一个转接卡,通过PCI插口转接。
3)察看外设使用说明书知道外设的相关参数.
比如,波特率,数据位,停止位,校验位,等等。
只有正确参数,才能显示正确的数据。
当然,你可以在通讯测试软件上调试这些参数的。
比如:
波特率=2400,数据位=8,停止位=2,校验位=1。
4)准备开发环境。
最基本的JDK了,你可以使用自己钟爱的IDE,帮助你开发。
IDE可能自带了JDK,那么你要把相应的javaComm20-win32放到运行时使用的JDK中。
下载JAVAcomm20-win32。
5)了解javaComm20-win32。
你必须把win32com.dll复制到java.home/bin下;
把m.properties复制到java.home/lib下;
把comm.jar添加到你classPath下。
前面两个都是非常重要的。
下面说明用到的几个类:
m.CommPortIdentifier
通讯端口管理器,CommPortIdentifier是控制访问到通讯端口的中心类。
它包括的方法有:
a.通过驱动决定通讯端口是可用的。
b.打开通讯端口为了I/O操作。
c.决定端口的拥有者。
d.解析端口拥有者的争夺。
e.管理事件显示在端口拥有者的中的状态改变。
一个应用程序首先使用CommPortIdentifier中的方法,通过相关的驱动去获取那些通讯端口是可用的并且选择一个端口便于开始。
然后它使用方法在其它类中想CommPort,ParallelPort和SerialPort通过这个端口进行通讯。
m.SerialPort
一个RS-232串口通讯端口。
SerialPort描述底层的接口到一个串口通讯端口
变得有效的通过底层的系统。
SerialPort定义最小的必需的功能便于串口通讯端口。
m.SerialPortEventListener串行端口事件传播。
m.CommDriver
6)代码的编写。
a.获取SerialPortsPort对象的两种方法。
1)2)
java代码
1.System.loadLibrary("
win32com"
2.m.CommDriverdriver=null;
3.StringdriverName="
m.Win32Driver"
;
4.SerialPortsPort=(SerialPort)driver.getCommPort("
COM4"
ommPortIdentifier.PORT_SERIAL);
1.CommPortIdentifierportId=CommPortIdentifier.getPortIdentifier("
2.SerialPortsPort=(SerialPort)portId.open("
shipment"
1000);
以上两种方法都可以。
不过一般都会采用第二种。
方法说明我们获取了对串行端口(COM4),可以和它进行通讯了。
b.设置串行端口通讯参数。
1.sPort.setSerialPortParams(2400,SerialPort.DATABITS_8,SerialPort.STOPBITS_2,SerialPort.PARITY_NONE);
c.获取输入(出)流。
1.InputStreamis=sPort.getInputStream();
//从外设获取数据
2.OutputStreamos=sPort.getOutputStream();
//发送命令到外设
d.通过监听器就可以得到数据了。
1.//SetnotifyOnDataAvailabletotruetoalloweventdriveninput.
2.sPort.notifyOnDataAvailable(true);
3.
4.//SetnotifyOnBreakInterruptoalloweventdrivenbreakhandling.
5.sPort.notifyOnBreakInterrupt(true);
6.
7.//Setreceivetimeouttoallowbreakingoutofpollingloopduringinputhandling.
8.sPort.enableReceiveTimeout(30);
9.StringBufferlinkWgt=newStringBuffer();
//存放获取的数据
10.sPort.addEventListener(
11.newSerialPortEventL