你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 网络与通信
4.5 Java串行通信类
 

一、引言

Java程序具有一次编译、到处运行的跨平台特性,随着Java在嵌入式系统中的广泛应用,研究Java的串行通信程序具有日益重要的意义。文献[1]实现了基于Java事件驱动的串行通信,在该文献及Sun公司中国技术社区的蒋清野的基础之上,设计了通用的串行通信类,跟文献[2]相比,这些类更简洁、健壮,而且可以方便地处理任何字节,并可以将一批数据作为一个整体来提交,方便用户的下一步处理。这里的通用串口类由两个Java类组成,OperateCOM类用来初始化串口,并启动数据接收进程;ReadCOM类用来读取串口数据,并将一批数据作为整体提交。SerialExample类利用OperateCOMReadCOM类进行串行通信的测试,并利用文献[3]中的工具进行各种格式的显示。

二、OperateCOM

OperateCOMReadCOM类的包名均为SerialPortOperateCOM类主要完成的工作是:取得串口ID、打开串口、获取输入输出流、设置串口参数、启动串口数据读取进程。然后,就是常用的读取串口数据、从串口发送数据,以及关闭串口。

.public OperateCOM (int PortID, int nLen )

这是OperateCOM类的构造方法,PortID表示需要操作的串口号,“1”表示“COM1”,“2”表示“COM2”……以此类推;nLen表示串口的输入缓冲区大小,最小值为1。构造方法的源代码如下:

    public OperateCOM(int PortID, int nLen) {

        PortName = "COM"+PortID;

        nMaxLength = nLen;

        if (nLen < 1) nMaxLength = 1;

    }   

.public int GetPortID()    

该方法通过串口名字,如COM1,取得串口的ID,如果正确则返回Serial_Success(常数1);如果错误,则抛出没有此串口的异常NoSuchPortException,并返回Serial_Error(常数-1),其源代码如下:

    public int GetPortID(){

        try {

            portID = CommPortIdentifier.getPortIdentifier(PortName);

        } catch (NoSuchPortException e) {

            return Serial_Error;

        }

        return Serial_Success;

    }

.public int Open(String AppName, int nTime)

该方法通过获取的串口portID打开串口,输入参数AppName是程序的名称,nTime表示延迟的毫秒数。如果该串口正在被使用,则会抛出PortInUseException异常;如果打开串口成功,则根据serialPort继续获取串口的输入输出流,其源代码如下:

    public int Open(String AppName, int nTime){

        try {

            serialPort = (SerialPort)portID.open(AppName, nTime);

        } catch (PortInUseException e) {

            return Serial_Error;

        }

        try {

            in = serialPort.getInputStream();

            out = serialPort.getOutputStream();

        } catch (IOException e) {

            return Serial_Error;

        }

        return Serial_Success;

    }

.public int SetParams(int baudrate, int dataBits, int stopBits, int parity)

该方法用来设置串口参数。输入参数分别对应波特率、数据位、停止位和校验方式。在Java Communications APIJavadoc中,有相应的常数代号,例如,DATABITS_8表示整数8STOPBITS_1表示整数1等。源代码如下:

    public int SetParams(int baudrate, int dataBits, int stopBits, int parity){

        try {

            serialPort.setSerialPortParams(baudrate, dataBits, stopBits, parity);

        } catch (UnsupportedCommOperationException e) {

            return Serial_Error;

        }

        return Serial_Success;

    }

.public void StartCom(int nDelay)

该方法启动数据接收进程,nDelay是延迟的毫秒数,表示凡是时间间隔在nDelay毫秒之内的数据,都作为一个整体来处理。从文献[1]中的测试效果可知,串行通信的数据确实是不连续的,需要进行累加处理。ReadCOMThread类的子类,本方法根据给定的参数,生成一个多线程的实例,并启动多线程。OperateCOM类直接控制ReadCOM类的实例的生成、多线程的销毁与数据的读取等。StartCom的源代码如下:

    public void StartCom(int nDelay){

        readThread = new ReadCOM(in, nDelay, nMaxLength);

        readThread.start();

    }

.public byte[] ReadPort()

读取串口数据,并且根据要求的时间片将数据作为整体处理,是串行通信中的难点。ReadCOM类很好地实现了这个功能,其方法GetComBuffer()可以取得以字节数组形式的完整的数据包。ReadPort()的源代码如下:

    public byte[] ReadPort(){

        return readThread.GetComBuffer();

    }

.public void WritePort(byte bData[], int off, int len)

该方法用来从串口发送数据,其参数包括需要发送的字节数组、数组的偏移量与发送的长度,其源代码如下。

    public void WritePort(byte bData[], int off, int len){

        try {

            out.write(bData, off, len);

        } catch (IOException e) {

            System.out.println("IOException:"+e);

        }

    }

.public void ClosePort()

OperateCOM类的Open方法打开串口,ClosePort则用于关闭串口。由于读取串口数据的多线程是一个死循环,关闭串口前,需要首先关闭多线程。在多线程中,有一个布尔变量,可以通过公共方法DestroyReadThread调用,用来设置为true,从而使多线程退出。纯粹给多线程实例readThread赋值null,并不能迫使多线程退出,在NetBeans 5.0 的调试环境下可以发现这一点。另外,ThreadDestroy方法已经抛弃不用了。关闭多线程后,再关闭串口即可。ClosePort方法的源代码如下:

    public void ClosePort(){

        readThread.DestroyReadThread();

        readThread = null;

        serialPort.close();

    }  

三、ReadCOM

ReadCOM类派生于Thread类,其主要方法为构造方法及run方法,其他方法被run方法所调用。

.public ReadCOM(InputStream Port, int steps, int nLen)

该方法是ReadCOM类的构造方法,需要传入输入流Port;需要等待的节拍数(即毫秒数)steps,在此时间片内的数据将被当作一个整体来处理;nLen则是输入缓冲区的大小,根据该数据调用ByteBuffer类的allocate方法分配缓冲区。构造方法的源代码如下:

    public ReadCOM(InputStream Port, int steps, int nLen) {

        ComPort = Port;

        TimeToWait = steps;

        nPackageLen = nLen;

        ComBuffer = ByteBuffer.allocate(nLen);

    }   

.public void run()

该方法是一个多线程方法,是ReadCOM类中的核心方法,本类中的其他方法几乎都是为该方法服务的。run方法是一个while循环,不停地检测串口的输入流有无数据。这个循环由3组并列的条件语句组成:

l         bDestroytrue,则退出循环,该变量的设置通过公共方法DestroyReadThread完成;

l         如果输入流中有数据,即ComPort.available()>0,则读取当前字节,lStart中记录当前字节到达的时间。第一个字节到达后,通过方法SetAvailable(false)将当前数据包设置为不可用,因为数据还没有接收组装完成;

l         如果当前字节到达的时间lStart大于0,则计算当前时间与lStart之间的时间间隔,如果大于规定的数值TimeToWait(在构造方法中设置),则认为数据接收结束,通过方法SetAvailable(true)将当前数据包设置为可用。run方法的源代码如下:

    public void run(){

        byte bIn;   //存放读取的当前字节

        try {

            while(true){

                if(bDestroy == true) return;

                if (ComPort.available()>0){

                    bIn = (byte)ComPort.read();

                    if (lStart == 0) SetAvailable(false); //清空缓冲区

                    PutByte(bIn);  //保存数据

                    sTime = Calendar.getInstance();

                    lStart = sTime.getTimeInMillis(); //当前读取数据的时间

                }

               

                if (lStart>0){

                    sTime = Calendar.getInstance();

                    lInterval = sTime.getTimeInMillis() - lStart;

                    if (lInterval >= TimeToWait) {

                        SetAvailable(true);

                    }

                }

            }

        } catch (IOException e) {

            System.out.println("IOException Error"+e);

        }

    }

.private synchronized void PutByte(byte bIn)

该方法是私有方法,被run方法调用,将当前从串口输入流ComPort中读取的字节存入串口输入缓冲区ComBuffer中,如果缓冲区的最后一个字节的位置已经大于最大值nPackageLen(在构造方法中设置),则提示缓冲区溢出,同时,将缓冲区指针复位。ComBuffer.put((byte)bIn)语句将字节bIn存入缓冲区,这将使得缓冲区的指针(位置)后移一个字节,源代码如下:

    private synchronized void PutByte(byte bIn){

        if (ComBuffer.position() >= nPackageLen){

            System.out.println("ComBuffer overflow!");

            ComBuffer.rewind();  //指针复位

        }

        ComBuffer.put((byte)bIn); 

    }

.private void SetAvailable(boolean bGet)

该方法是私有方法,被run方法和GetComBuffer方法调用。如果设置为false,则串口缓冲区ComBuffer中的内容被清除,指针(位置)复位,在首次收到数据包的头部数据时,执行该动作;如果在run方法中等待的时间片大于TimeToWait,则表示一个数据包接收结束,设置为true,同时,令lStart0,表示如果收到新的数据,则认为是下一个数据包中的内容。SetAvailable方法的源代码如下:

    private void SetAvailable(boolean bGet){

        bAvailable = bGet;

        if(bGet == false) {

            ComBuffer.clear();

            ComBuffer.rewind();

        }

        if(bGet == true) lStart = 0;

    }

.public void DestroyReadThread()

该公有方法用来将bDestroy的值设置为true,从而让run方法从while循环中退出,达到杀死进程的目的。其源代码如下:

    public void DestroyReadThread(){

        bDestroy = true;

    }

.public byte[] GetComBuffer()

该方法是公有方法,用来返回完整的数据包。输入缓冲区ComBuffer的指针(位置)即为数据包的长度。如果数据接收完成,则bAvailabletrue,就先取得数据的长度,然后,将ComBuffer缓冲区的指针(位置)复位,这样,就可以从位置0处开始读取给定的字节。数据读取完毕,就通过SetAvailable(false)方法销毁数据,以免数据被重复读取,如此模仿Visual Basic 6.0 中的MSComm控件的动作。如果没有数据或者数据没有准备好,就返回null

    public byte[] GetComBuffer(){

        int bLen;

        byte[] bReceive;

        if (bAvailable == true){   //数据接收结束

            bLen = ComBuffer.position();

            bReceive = new byte[bLen];

            ComBuffer.rewind();

            ComBuffer.get(bReceive, 0, bLen);

            SetAvailable(false);  //销毁数据

            return bReceive;

        }

        else return null; //没有数据或数据没有准备好

    }    

四、串口类的发布

NetBeans 5.0环境下完成代码编写后,可以单击项目名称,然后,选择生成项目,即可在项目的build\classes\SerialPort目录下,看到OperateCOM.classReadCOM.class。在C盘建立一个目录,如C:\JarPackage,将包含类文件的SerialPort文件夹复制到该文件夹。在DOS环境下进入JarPackage目录,输入如下命令(下划线所示):

C:\JarPackage> jar cvf SerialPort.jar *

即可得到将以上两个类打包后的jar文件。该命令的“c”表示创建新的文档,“v”表示生成详细信息到标准输出上,“f”表示制定存档文件名。

可以将该串口包复制到jre目录下的lib中,并在CLASSPATH系统环境变量中包含该包的绝对路径,即可通过“import SerialPort.*;”来使用串口类,并在DOS环境下利用javac命令对java源代码进行编译。如果在NetBeans 5.0环境下使用串口包,则需要添加库,并指出库的绝对路径。

五、串口类的测试

测试源代码除了需要引用上文生成的SerialPort包外,还要使用文献[3]中的ComputerMonitor包,用来灵活地处理数据,并以需要的形式进行显示。测试程序首先初始化串口,然后,在while循环中读取数据,以各种形式显示,如果收到的数据包的第一个字节为0x21(即字符”!”)则退出程序。测试类SerialExample的源代码如下:

import SerialPort.*;

import ComputerMonitor.*;

    /** Creates a new instance of SerialExample */

    public static void main(String[] args) {

        OperateCOM SB = new OperateCOM(1, 1024);

        // open COM1, the max length of package is 1024 bytes

        if (SB.GetPortID() == -1) {

            System.out.println("No such port!");

            System.exit(1);

        }

        if (SB.Open("SerialExample", 100) == -1) {

            System.out.println("Port in use now!");

            System.exit(1);

        }

        if (SB.SetParams(9600, 8, 1, 0) == -1){

            System.out.println("Unsupported operation!");

            System.exit(1);

        }

        SB.StartCom(150); //延迟150毫秒

        while(true){

            byte[] bData = SB.ReadPort();

            if (bData != null){

                System.out.println("Receive and send back: ");

                System.out.println("UTF-8:     " +

                                ByteProcess.BytesToEnString(bData));

                System.out.println("UTF-16BE:  " +

                                ByteProcess.UnicodeToString(bData));

                System.out.println("Bytes:     " +

                                ByteProcess.InsertSpaceToHexChars(

                                ByteProcess.BytesToHexChars(bData)));

                SB.WritePort(bData,0,bData.length);

                if(bData[0] == 0x21){  //The first char is "!", so stop now!

                    SB.ClosePort();

                    return;

                }

            }       

        }

    }


Java例程在NetBeans 5.0 环境下的调试方式运行,利用串口测试工具TestPort分别发送字节序列 FF 00 66 25 21 4F 4B,结果如下图所示。在串行通信中,字节最高位为1,可能由于字符集或系统环境原因,导致最高位为0;而字节00通常作为高级语言中字符串的结束标志,这意味着字节00后面的数据将被截去。从下图中可以看出,所有数据均被完整接收,并可以实现原样发送返回。在UTF-8的表示中,0xFF0对应的字符不可见,0x66对应字符“f”,0x25对应字符“%”;在UTF-16BE的表示中,两个字节表示一个汉字,0xFF00是不可见汉字(或没有此汉字),0x6625表示汉字“春”。由于第二个数据包的第一个字节是0x21,程序退出,与设计效果完全一样。

 

 


六、结语

本文利用Java Communications API函数,设计了OperateCOMReadCOM类,用来便捷地进行串口数据的收发,并给出了应用实例和测试结果。该程序具有通用性,可以用于相关的嵌入式系统设备的二次开发中。完整的源代码可以从本刊网站下载。

 

  推荐精品文章

·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 
·2023年7月目录
·2023年6月目录 
·2023年5月目录
·2023年4月目录 
·2023年3月目录 
·2023年2月目录 
·2023年1月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089