java

位置:IT落伍者 >> java >> 浏览文章

编程技巧:Java串口通信简介


发布日期:2023年07月12日
 
编程技巧:Java串口通信简介

嵌入式系统或传感器网络的很多应用和测试都需要通过PC机与嵌入式设备或传感器节点进行通信其中最常用的接口就是RS串口和并口(鑒于USB接口的复杂性以及不需要很大的数据传输量USB接口用在这里还是显得过于奢侈况且目前除了SUN有一个支持USB的包之外我还没有看到其他直接支持USB的Java类库)SUN的CommAPI分别提供了对常用的RS串行端口和IEEE并行端口通讯的支持RSC(又称EIA RSC以下简称RS)是在年由美国电子工业协会(EIA)联合贝尔系统调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准RS是一个全双工的通讯协议它可以同时进行数据接收和发送的工作

常见的Java串口包

目前常见的Java串口包有SUN在年发布的串口通信APIcommjar(Windows下)commjar(Linux/Solaris);IBM的串口通信API以及一个开源的实现鑒于在Windows下SUN的API比较常用以及IBM的实现和SUN的在API层面都是一样的那个开源的实现又不像两家大厂的产品那样让人放心这里就只介绍SUN的串口通信API在Windows平台下的使用

串口包的安装(Windows下)

到SUN的网站下载javacommwinzip包含的东西如下所示

按照其使用说明(l)的说法要想使用串口包进行串口通信除了设置好环境变量之外还要将wincomdll复制到\bin目录下;将commjar复制到\lib;把mproperties也同样拷贝到\lib目录下然而在真正运行使用串口包的时候仅作这些是不够的因为通常当运行java MyApp的时候是由JRE下的虚拟机启动MyApp的而我们只复制上述文件到JDK相应目录下所以应用程序将会提示找不到串口解决这个问题的方法很简单我们只须将上面提到的文件放到JRE相应的目录下就可以了

值得注意的是在网络应用程序中使用串口API的时候还会遇到其他更复杂问题有兴趣的话你可以查看CSDN社区中关于网页上Applet用javacomm读取客户端串口的问题的帖子

串口API概览

mCommPort

这是用于描述一个被底层系统支持的端口的抽象类它包含一些高层的IO控制方法这些方法对于所有不同的通讯端口来说是通用的SerialPort 和ParallelPort都是它的子类前者用于控制串行端口而后者用于控这并口二者对于各自底层的物理端口都有不同的控制方法这里我们只关心SerialPort

mCommPortIdentifier

这个类主要用于对串口进行管理和设置是对串口进行访问控制的核心类主要包括以下方法

l 确定是否有可用的通信端口

l 为IO操作打开通信端口

l 决定端口的所有权

l 处理端口所有权的争用

l 管理端口所有权变化引发的事件(Event)

mSerialPort

这个类用于描述一个RS串行通信端口的底层接口它定义了串口通信所需的最小功能集通过它用户可以直接对串口进行读写及设置工作

串口API实例

大段的文字怎么也不如一个小例子来的清晰下面我们就一起看一下串口包自带的例子SerialDemo中的一小段代码来加深对串口API核心类的使用方法的认识

列举出本机所有可用串口

voidlistPortChoices(){

CommPortIdentifierportId;

Enumerationen=CommPortIdentifiergetPortIdentifiers();

//iteratethroughtheports

while(enhasMoreElements()){

portId=(CommPortIdentifier)ennextElement();

if(portIdgetPortType()==CommPortIdentifierPORT_SERIAL){

Systemoutprintln(portIdgetName());

}

}

portChoiceselect(parametersgetPortName());

}

以上代码可以列举出当前系统所有可用的串口名称我的机器上输出的结果是COM和COM

串口参数的配置

串口一般有如下参数可以在该串口打开以前配置进行配置

包括波特率输入/输出流控制数据位数停止位和齐偶校验

SerialPortsPort;

try{

sPortsetSerialPortParams(BaudRateDatabitsStopbitsParity);

//设置输入/输出控制流

sPortsetFlowControlMode(FlowControlIn|FlowControlOut);

}catch(UnsupportedCommOperationExceptione){}

串口的读写

对串口读写之前需要先打开一个串口

CommPortIdentifierportId=CommPortIdentifiergetPortIdentifier(PortName);

try{

SerialPortsPort=(SerialPort)portIdopen(串口所有者名称超时等待时间);

}catch(PortInUseExceptione){//如果端口被占用就抛出这个异常

thrownewSerialConnectionException(egetMessage());

}

//用于对串口写数据

OutputStreamos=newBufferedOutputStream(sPortgetOutputStream());

oswrite(intdata);

//用于从串口读数据

InputStreamis=newBufferedInputStream(sPortgetInputStream());

intreceivedData=isread();

读出来的是int型你可以把它转换成需要的其他类型

这里要注意的是由于Java语言没有无符号类型即所有的类型都是带符号的在由byte到int的时候应该尤其注意因为如果byte的最高位是则转成int类型时将用来占位这样原本是的byte类型的数变成int型就成了这是很严重的问题应该注意避免

串口通信的通用模式及其问题

终于唠叨完我最讨厌的基础知识了下面开始我们本次的重点串口应用的研究由于向串口写数据很简单所以这里我们只关注于从串口读数据的情况通常串口通信应用程序有两种模式一种是实现SerialPortEventListener接口监听各种串口事件并作相应处理;另一种就是建立一个独立的接收线程专门负责数据的接收由于这两种方法在某些情况下存在很严重的问题(至于什么问题这里先卖个关子J)所以我的实现是采用第三种方法来解决这个问题

事件监听模型

现在我们来看看事件监听模型是如何运作的

l 首先需要在你的端口控制类(例如SManager)加上implements SerialPortEventListener

l 在初始化时加入如下代码

try{

SerialPortsPortaddEventListener(SManager);

}catch(TooManyListenersExceptione){

sPortclose();

thrownewSerialConnectionException(toomanylistenersadded);

}

sPortnotifyOnDataAvailable(true);

l 覆写public void serialEvent(SerialPortEvent e)方法在其中对如下事件进行判断

BI 通讯中断

CD 载波检测

CTS 清除发送

DATA_AVAILABLE 有数据到达

DSR 数据设备准备好

FE 帧错误

OE 溢位错误

OUTPUT_BUFFER_EMPTY 输出缓沖区已清空

PE 奇偶校验错

RI 振铃指示

一般最常用的就是DATA_AVAILABLE串口有数据到达事件也就是说当串口有数据到达时你可以在serialEvent中接收并处理所收到的数据然而在我的实践中遇到了一个十分严重的问题

首先描述一下我的实验我的应用程序需要接收传感器节点从串口发回的查询数据并将结果以图标的形式显示出来串口设定的波特率是川口每隔毫秒返回一组数据(大约是字节左右)周期(即持续时间)为实测的时候在一个周期内应该返回多个字节而用事件监听模型我最多只能收到不到字节不知道这些字节都跑哪里去了也不清楚到底丢失的是那部分数据值得注意的是这是我将serialEvent()中所有处理代码都注掉只剩下打印代码所得的结果数据丢失的如此严重是我所不能忍受的于是我决定采用其他方法

串口读数据的线程模型

这个模型顾名思义就是将接收数据的操作写成一个线程的形式:

publicvoidstartReadingDataThread(){

ThreadreadDataProcess=newThread(newRunnable(){

publicvoidrun(){

while(newData!=){

try{

newData=isread();

Systemoutprintln(newData);

//其他的处理过程

………

}catch(IOExceptionex){

Systemerrprintln(ex);

return;

}

}

readDataProcessstart();

}

在我的应用程序中我将收到的数据打包放到一个缓存中然后启动另一个线程从缓存中获取并处理数据两个线程以生产者—消费者模式协同工作数据的流向如下图所示

这样我就圆满解决了丢数据问题然而没高兴多久我就又发现了一个同样严重的问题虽然这回不再丢数据了可是原本一个周期(秒)之后传感器节电已经停止传送数据了但我的串口线程依然在努力的执行读串口操作在控制台也可以看见收到的数据仍在不断的打印原来由于传感器节点发送的数据过快而我的接收线程处理不过来所以InputStream就先把已到达却还没处理的字节缓存起来于是就导致了明明传感器节点已经不再发数据了而控制台却还能看见数据不断打印这一奇怪的现象唯一值得庆幸的是最后收到数据确实是左右字节没出现丢失现象然而当处理完最后一个数据的时候已经快分半钟了这个时间远远大于节点运行周期这一延迟对于一个实时的显示系统来说简直是灾难!

后来我想是不是由于两个线程之间的同步和通信导致了数据接收缓慢呢?于是我在接收线程的代码中去掉了所有处理代码仅保留打印收到数据的语句结果依然如故看来并不是线程间的通信阻碍了数据的接收速度而是用线程模型导致了对于发送端数据发送速率过快的情况下的数据接收延迟这里申明一点就是对于数据发送速率不是如此快的情况下前面者两种模型应该还是好用的只是特殊情况还是应该特殊处理

第三种方法

痛苦了许久(Boss天天催我L)之后偶然的机会我听说TinyOS中(又是开源的)有一部分是和我的应用程序类似的串口通信部分于是我下载了它的x版的Java代码部分参考了它的处理方法解决问题的方法说穿了其实很简单就是从根源入手根源不就是接收线程导致的吗那好我就干脆取消接收线程和作为中介的共享缓存而直接在处理线程中调用串口读数据的方法来解决问题(什么为什么不把处理线程也一并取消?都取消应用程序界面不就锁死了吗?所以必须保留)于是程序变成了这样

publicbyte[]getPack(){

while(true){

//PacketLength为数据包长度

byte[]msgPack=newbyte[PacketLength];

for(inti=;i<PacketLength;i++){

if((newData=isread())!=){

msgPack[i]=(byte)newData;

Systemoutprintln(msgPack[i]);

}

}

returnmsgPack;

}

}

在处理线程中调用这个方法返回所需要的数据序列并处理之这样不但没有丢失数据的现象行出现也没有数据接收延迟了这里唯一需要注意的就是当串口停止发送数据或没有数据的时候isread()一直都返回如果一旦在开始接收数据的时候发现就不要理它继续接收直到收到真正的数据为止

结束语

本文介绍了串口通信的基本知识以及常用的几种模式通过实践提出了一些问题并在最后加以解决值得注意的是对于第一种方法我曾将传感器发送的时间由毫秒增加到毫秒仍然有很严重的数据丢失现象发生所以如果你的应用程序需要很精密的结果传输数据的速率又很快的话就最好不要用第一种方法对于第二种方法由于是线程导致的问题所以对于不同的机器应该会有不同的表现对于那些处理多线程比较好的机器来说应该会好一些但是我的机器是Inter 奔四双核CPU+DDR内存这样都延迟这么厉害还得多强的CPU才行啊?所以对于数据量比较大的传输来说还是用第三种方法吧不过这个世界问题是很多的而且未知的问题比已知的问题多的多说不定还有什么其他问题存在欢迎你通过下面的联系方式和我一起研究

               

上一篇:初探Java 7异常处理功能增强

下一篇:可变MD5加密(Java实现)