【精选】JAVA串口通信开发_java串口开发_明月照花台的博客-CSDN博客 - purejavacomm

JAVA串口通信开发

前言

最近几个月一直在接触串口,与硬件打交道,还是学到了不少之前没听过的东西,特此记录一下,其中不免有语焉不详或一知半解的地方,欢迎各位指教。

    • *

提示:以下是本篇文章正文内容,下面案例可供参考

一、项目背景

首先说串口是什么,百度上说串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口 (Serial Interface)是指数据一位一位地顺序传送。实际上就是传输数据用的物理接口,一般可以按照接线方式分为RS-232和RS-485,对于程序开发来说,这两者并没有什么不同。
之后说一下实际使用的项目背景,首先会有一台计算机,计算机上有一排物理串口,串口上接的是232的控制器,控制器连接实际的机械设备。而我们的目前是使用程序向232控制器发生指令来操控机械设备实现不同动作,程序最终会以HTTP接口的方式对外暴露。

二、实际开发

1.引入库

对于JAVA的串口通信开发,一般能查到的都是使用RXTXcomm.jar,同时需要rxtxParallel.dll和rxtxSerial.dll两个dll文件。最开始我也是使用了这种方式,但是后连在实际测试中发现了一个非常致命的问题,因为我的程序最后是一组HTTP接口,所以避免不了会同时对多个串口操作,而一旦发生同时或短时间内操作多个串口时,程序会崩溃,类似这样。
在这里插入图片描述
这个问题我实在没有搞清楚产生的原因,我怀疑可能使用的RXTXcomm并不支持同时操作,另外可能与JDK版本有关,建议1.8.0_144。
因为产生了这个问题目前又无法解决,所以最终我决定换一个驱动,不再采用RXTXcomm,而是选用了purejavacomm。purejavacomm使用的是JNA,并不需要额外引用DLL,使用方式与RXTXcomm相同,还是比较便捷的,我个人觉得绝对是JAVA串口开发最好的驱动了,可以直接用pom引用。

<dependency>
    <groupId>com.github.purejavacomm</groupId>
    <artifactId>purejavacomm</artifactId>
    <version>1.0.1.RELEASE</version>
</dependency>

2.串口通信工具类

废话不多说,代码如下:

package com.water.api.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import purejavacomm.CommPort;
import purejavacomm.CommPortIdentifier;
import purejavacomm.NoSuchPortException;
import purejavacomm.PortInUseException;
import purejavacomm.SerialPort;
import purejavacomm.SerialPortEventListener;
import purejavacomm.UnsupportedCommOperationException;

@Component
public class SerialTool {
    private static Logger logger = LoggerFactory.getLogger(SerialTool.class);

    public static final ArrayList<String> findPorts() {
        // 获得当前所有可用串口
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        ArrayList<String> portNameList = new ArrayList<String>();
        // 将可用串口名添加到List并返回该List
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }

    /**
     * 打开串口
     * 
     * @param portName
     *            端口名称
     * @param baudrate
     *            波特率
     * @return 串口对象
     * @throws Exception
     * @throws SerialPortParameterFailure
     *             设置串口参数失败
     * @throws NotASerialPort
     *             端口指向设备不是串口类型
     * @throws NoSuchPort
     *             没有该端口对应的串口设备
     * @throws PortInUse
     *             端口已被占用
     */
    public static SerialPort openPort(String portName, Integer baudrate, Integer dataBits, Integer stopBits,
            Integer parity) throws Exception {

        try {

            // 通过端口名识别端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);

            // 打开端口,并给端口名字和一个timeout(打开操作的超时时间)
            CommPort commPort = portIdentifier.open(portName, 2000);

            // 判断是不是串口
            if (commPort instanceof SerialPort) {
                SerialPort serialPort = (SerialPort) commPort;
                try {
                    // 设置一下串口的波特率等参数
                    serialPort.setSerialPortParams(baudrate, dataBits, stopBits, parity);
                    logger.info("串口" + portName + "打开成功");
                } catch (UnsupportedCommOperationException e) {
                    logger.error("设置串口" + portName + "参数失败:" + e.getMessage());
                    throw e;
                }

                return serialPort;

            } else {
                logger.error("不是串口" + portName);
                // 不是串口
                throw new Exception();
            }
        } catch (NoSuchPortException e1) {
            logger.error("无此串口" + portName);
            throw e1;
        } catch (PortInUseException e2) {
            logger.error("串口使用中" + portName);
            throw e2;
        } catch (Exception e) {
            throw e;
        }
    }

    public static byte[] HexString2Bytes(String src) {
        if (null == src || 0 == src.length()) {
            return null;
        }
        byte[] ret = new byte[src.length() / 2];
        byte[] tmp = src.getBytes();
        for (int i = 0; i < (tmp.length / 2); i++) {
            ret[i] = uniteBytes(tmp[i * 2], tmp[i * 2 + 1]);
        }
        return ret;
    }

    // byte类型数据,转成十六进制形式;
    public static byte uniteBytes(byte src0, byte src1) {
        byte _b0 = Byte.decode("0x" + new String(new byte[] { src0 })).byteValue();
        _b0 = (byte) (_b0 << 4);
        byte _b1 = Byte.decode("0x" + new String(new byte[] { src1 })).byteValue();
        byte ret = (byte) (_b0 ^ _b1);
        return ret;
    }

    /**
     * 关闭串口
     * 
     * @throws IOException
     */
    public static synchronized void closePort(SerialPort serialPort) throws IOException {
        if (serialPort != null) {
            serialPort.close();
            logger.info("串口" + serialPort.getName() + "已关闭");
        }
    }

    /**
     * 往串口发送数据
     * 
     * @param order
     *            待发送数据
     * @throws SendDataToSerialPortFailure
     *             向串口发送数据失败
     * @throws SerialPortOutputStreamCloseFailure
     *             关闭串口对象的输出流出错
     */
    public static void sendToPort(byte[] order, SerialPort serialPort) throws IOException {

        OutputStream out = null;

        try {

            out = serialPort.getOutputStream();
            out.write(order);
            out.flush();
            logger.info("发送数据成功" + serialPort.getName());
        } catch (IOException e) {
            logger.error("发送数据失败" + serialPort.getName());
            throw e;
        } finally {
            try {
                if (out != null) {
                    out.close();
                    out = null;
                }
            } catch (IOException e) {
                logger.error("关闭串口对象的输出流出错");
                throw e;
            }
        }

    }

    /**
     * 从串口读取数据
     * 
     * @param serialPort
     *            当前已建立连接的SerialPort对象
     * @return 读取到的数据
     * @throws ReadDataFromSerialPortFailure
     *             从串口读取数据时出错
     * @throws SerialPortInputStreamCloseFailure
     *             关闭串口对象输入流出错
     */
    public static byte[] readFromPort(SerialPort serialPort) throws Exception {

        InputStream in = null;
        byte[] bytes = null;

        try {
            if (serialPort != null) {
                in = serialPort.getInputStream();
            } else {
                return null;
            }
            int bufflenth = in.available(); // 获取buffer里的数据长度
            while (bufflenth != 0) {
                bytes = new byte[bufflenth]; // 初始化byte数组为buffer中数据的长度
                in.read(bytes);
                bufflenth = in.available();
            }
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                throw e;
            }

        }

        return bytes;

    }

    /**
     * 添加监听器
     * 
     * @param port
     *            串口对象
     * @param listener
     *            串口监听器
     * @throws TooManyListeners
     *             监听类对象过多
     */
    public static void addListener(SerialPortEventListener listener, SerialPort serialPort) throws TooManyListenersException {

        try {

            // 给串口添加监听器
            serialPort.addEventListener(listener);
            // 设置当有数据到达时唤醒监听接收线程
            serialPort.notifyOnDataAvailable(true);
            // 设置当通信中断时唤醒中断线程
            serialPort.notifyOnBreakInterrupt(true);

        } catch (TooManyListenersException e) {
            throw e;
        }
    }

}

这部分其实没什么好说的,网上一搜一堆,唯一需要注意的是,需要考虑在程序里每一个串口的生命周期。对于一个串口,系统全局应当只有一个实例,频繁的开关串口并不是一个好的选择。
上面的代码只是说明了如何向串口发送数据,从串口中读数据需要添加监听,当有数据返回时会把数据推送到监听里。当然这种方式使用起来非常不爽,因为发送和接收是异步的,换句话说,发送指令是一个线程,而接收数据又是一个线程,实际使用中,很多时候需要得到返回值,然后来判断接下来发送的指令,这样代码写起来非常复杂,作为一个使用者肯定希望在同一位置完成收发,所以最后我封装了一个操作类来完成收发。

public class SerialResquest {
    private static Logger logger = LoggerFactory.getLogger(SerialResquest.class);
    

    public static void resquest(String portName, Integer baudrate, Integer dataBits, Integer stopBits,
            Integer parity,byte[] data) throws Exception {
        SerialPort serialPort;
        if (!GlobalCache.smap.containsKey(portName)) {
            GlobalCache.bmap.put(portName, false);
            serialPort = SerialTool.openPort(portName, baudrate, dataBits, stopBits, parity);
            GlobalCache.smap.put(portName, serialPort);
            SerialTool.addListener(new SerialPortEventListener() {

                @Override
                public void serialEvent(SerialPortEvent event) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e1) {
                        logger.error("SerialResquest 监听异常!"+e1);
                    }
                    switch (event.getEventType()) {
                    case SerialPortEvent.DATA_AVAILABLE:
                        byte[] readBuffer = null;
                        int availableBytes = 0;
                        try {
                            availableBytes = serialPort.getInputStream().available();
                            if (availableBytes > 0) {
                                try {
                                    readBuffer = SerialTool.readFromPort(serialPort);
                                    GlobalCache.bmap.put(portName, true);
                                    GlobalCache.dmap.put(portName, readBuffer);
                                } catch (Exception e) {
                                    logger.error("读取推送信息异常!"+e);
                                }
                            }
                        } catch (IOException e) {
                            logger.error("读取流信息异常!"+e);
                        }
                    }
                }
                
            }, serialPort);
        }else {
            serialPort = GlobalCache.smap.get(portName);
        }
        SerialTool.sendToPort(data, serialPort);
    }
    
    public static byte[] response(String portName) throws InterruptedException {
        /*if (!GlobalCache.dmap.containsKey(portName)) {
            return null;
        }*/
        Thread.sleep(100);
        int i =0;
        while (!GlobalCache.bmap.get(portName)) {
            Thread.sleep(100);
            if (i++>30) {
                return new byte[0];
            }
        }
        GlobalCache.bmap.put(portName, false);
        return GlobalCache.dmap.get(portName);
    }
    
    public static void close(String portName) throws IOException {
        SerialTool.closePort(GlobalCache.smap.get(portName));
        GlobalCache.smap.remove(portName);
    }
    
}

对于上面的代码,可以调用resquest方法来完成发送指令,如果系统没有当前串口实例,会生成一个并添加监听,如果有,则直接使用实例发送指令,通过response方法来接收返回值。需要注意的是,在监听中做了一次Thread.sleep(50),这是因为实际使用时发现返回值是断断续续的,例如发送一条指令A,理论上应该立即返回一条结果如AABBCCDD,但是实际会多次返回不同的部分,如先返回AA,然后返回BBC,每次返回的不完整,可能的原因是程序调用的是CPU资源,串口返回值是走串口连接线,速度上有差异,sleep后可以得到完整的返回值。如果实际串口连接线比较长,可以适当增大sleep时间。

3.数据解析

上面说过,与程序通过串口通信的实际上是控制器,当然有些设备也可以直接连接串口通信。与这些硬件通信的时候,避免不了数据交互,有些设备可能返回的数据比较友好,可以直观的看到数据值,但大部分返回的都需要解析。
数据的解析方式需要依据设备厂商提供的文档,但是原理大同小异,一般来说,返回的数据格式为short或float居多。而我们从程序读到的都是字节数组,我们需要做的就是把字节转成short或float。对于short,我们知道它占两字节,也就是16bit,那么我们只需要知道字节组中哪两位代表了一个short就可以解析出这个值,方法如下:

public static short toShort(byte b1, byte b2) {
        return (short) (b1 << 8 | b2 & 0xFF);
    }

对于float,占四个字节,也就是32bit,同样知道字节组中哪四位代表了一个float就可以解析出这个值,方法如下:

private float bytes2Float(byte[] bytes) {
        String BinaryStr = bytes2BinaryStr(bytes);
        // 符号位S
        Long s = Long.parseLong(BinaryStr.substring(0, 1));
        // 指数位E
        Long e = Long.parseLong(BinaryStr.substring(1, 9), 2);
        // 位数M
        String M = BinaryStr.substring(9);
        float m = 0, a, b;
        for (int i = 0; i < M.length(); i++) {
            a = Integer.valueOf(M.charAt(i) + "");
            b = (float) Math.pow(2, i + 1);
            m = m + (a / b);
        }
        Float f = (float) ((Math.pow(-1, s)) * (1 + m) * (Math.pow(2, (e - 127))));
        return f;
    }

    private static String bytes2BinaryStr(byte[] bytes) {
        StringBuffer binaryStr = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String str = Integer.toBinaryString((bytes[i] & 0xFF) + 0x100).substring(1);
            binaryStr.append(str);
        }
        return binaryStr.toString();
    }
    • *

总结

说了半天可能有些东西还是没说明白,或者我自己也没有理解。如果您有类似的困惑,欢迎与我联系,我们可以一起探讨。


原网址: 访问
创建于: 2023-11-22 10:07:55
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论