亲宝软件园·资讯

展开

Java实现RS485串口通信

胜金 人气:0

前言

  前段时间赶项目的过程中,遇到一个调用RS485串口通信的需求,赶完项目因为楼主处理私事,没来得及完成文章的更新,现在终于可以整理一下当时的demo,记录下来。

  首先说一下大概需求:这个项目是机器视觉方面的,AI算法通过摄像头视频流检测画面中的目标事件,比如:火焰、烟雾、人员离岗、吸烟、打手机、车辆超速等,检测到目标事件后上传检测结果到后台系统,

后台系统存储检测结果并推送结果到前端,这里用的是SpringBoot整合WebSocket实现前后端互推消息,感兴趣的同学可以看一看,大家多交流。然后就是今天的主题,系统在推送检测结果到前端的同时,需要触发

声光报警器,现有条件就是系统调用支持RS485串口的继电器控制电路,进而达到打开和关闭报警器的目的。

准备工作

  说了这么多可能没什么具体的概念,下面先列出需要的硬件设备及准备工作:

  硬件:

  USB串口转换器(现在很多主机和笔记本已经没有485串口的接口了,转换器淘宝可以买到);

  RS485继电器(12V,继电器模块有8个通道,模块的寄存器有对应8个通道的命令);

  声光报警器(12V);

  12V电源转换器;

  电线若干;

  驱动:

  USB串口转换驱动;

  看了这些硬件,感觉楼主是电工是吧?没错,楼主确实是自己摸索着连接的,下面上图:

   线路如何接不是本文的重点,用12V的硬件就是因为安全,楼主可以大胆尝试。。。

  接通硬件设备后,在系统中查看串口名称,如下图,可以看到通信端口名称是COM1,其实电脑上每个硬件接口都是有固定名称的,USB插在不同的USB接口上,系统读取到的通信端口名称就是对应接口的名称,这里

的端口名称要记下来,后面编码要用到。

   然后是搬砖前的最后一步准备工作:安装驱动。楼主的USB串口转换器是在淘宝上买的,商家提供驱动,在电脑上正常安装驱动即可。

开发实现

  首先需要引入rxtx的jar包,Java实现串口通信的依赖,如下:

        <dependency>
            <groupId>org.rxtx</groupId>
            <artifactId>rxtx</artifactId>
            <version>2.1.7</version>
        </dependency>    

  引入jar包后,就可以搬砖了,大概思路如下:

  1、获取到与串口通信的对象;

  2、打开对应串口的端口并建立连接;

  3、获取对应通道的命令并发送;

  4、接收返回的信息;

  5、关闭端口连接。

  代码如下:

package com.XXX.utils;

import com.databus.Log;
import gnu.io.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class RS485Demo extends Thread implements SerialPortEventListener {
    //单例模式提供连接串口的对象
    private static RS485Demo getInstance(){
        if (cRead == null){
            synchronized (RS485Demo.class) {
                if (cRead == null) {
                    cRead = new RS485Demo();
                    // 启动线程来处理收到的数据
                    cRead.start();
                }
            }
        }
        return cRead;
    }
//    封装十六进制的打开、关闭命令
    private static final List<byte[]> onOrderList = Arrays.asList(
            new byte[]{0x01, 0x05, 0x00, 0x00, (byte) 0xFF, 0x00, (byte) 0x8C, 0x3A},       new byte[]{0x01, 0x05, 0x00, 0x01, (byte) 0xFF, 0x00, (byte) 0xDD, (byte)0xFA},
            new byte[]{0x01, 0x05, 0x00, 0x02, (byte) 0xFF, 0x00, (byte) 0x2D, (byte)0xFA}, new byte[]{0x01, 0x05, 0x00, 0x03, (byte) 0xFF, 0x00, (byte) 0x7C, 0x3A},
            new byte[]{0x01, 0x05, 0x00, 0x04, (byte) 0xFF, 0x00, (byte) 0xCD,(byte) 0xFB}, new byte[]{0x01, 0x05, 0x00, 0x05, (byte) 0xFF, 0x00, (byte) 0x9C, 0x3B},
            new byte[]{0x01, 0x05, 0x00, 0x06, (byte) 0xFF, 0x00, (byte) 0x6C, 0x3B},       new byte[]{0x01, 0x05, 0x00, 0x07, (byte) 0xFF, 0x00,  0x3D, (byte)0xFB});
    private static final List<byte[]> offOrderList = Arrays.asList(
            new byte[]{0x01, 0x05, 0x00, 0x00,  0x00, 0x00, (byte) 0xCD, (byte)0xCA},new byte[]{0x01, 0x05, 0x00, 0x01,  0x00, 0x00, (byte) 0x9C, (byte)0x0A},
            new byte[]{0x01, 0x05, 0x00, 0x02,  0x00, 0x00, (byte) 0x6C, (byte)0x0A},new byte[]{0x01, 0x05, 0x00, 0x03,  0x00, 0x00, (byte) 0x3D, (byte)0xCA},
            new byte[]{0x01, 0x05, 0x00, 0x04,  0x00, 0x00, (byte) 0x8C, (byte)0x0B},new byte[]{0x01, 0x05, 0x00, 0x05,  0x00, 0x00, (byte) 0xDD, (byte)0xCB},
            new byte[]{0x01, 0x05, 0x00, 0x06,  0x00, 0x00, (byte) 0x2D, (byte)0xCB},new byte[]{0x01, 0x05, 0x00, 0x07,  0x00, 0x00, (byte) 0x7C, (byte)0x0B});

    // 监听器,这里独立开辟一个线程监听串口数据
// 串口通信管理类
    static CommPortIdentifier portId;
    static RS485Demo cRead = null;
    //USB在主机上的通信端口名称,如:COM1、COM2等
    static String COMNUM = "";

    static Enumeration<?> portList;
    InputStream inputStream; // 从串口来的输入流
    static OutputStream outputStream;// 向串口输出的流
    static SerialPort serialPort; // 串口的引用
    // 堵塞队列用来存放读到的数据
    private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<String>();

    /**
     * SerialPort EventListene 的方法,持续监听端口上是否有数据流
     */
    public void serialEvent(SerialPortEvent event) {

        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:// 当有可用数据时读取数据
                byte[] readBuffer = null;
                int availableBytes = 0;
                try {
                    availableBytes = inputStream.available();
                    while (availableBytes > 0) {
                        readBuffer = RS485Demo.readFromPort(serialPort);
                        String needData = printHexString(readBuffer);
                        System.out.println(new Date() + "真实收到的数据为:-----" + needData);
                        availableBytes = inputStream.available();
                        msgQueue.add(needData);
                    }
                } catch (IOException e) {
                }
            default:
                break;
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 当前已建立连接的SerialPort对象
     * @return 读取到的数据
     */
    public static byte[] readFromPort(SerialPort serialPort) {
        InputStream in = null;
        byte[] bytes = {};
        try {
            in = serialPort.getInputStream();
            // 缓冲区大小为一个字节
            byte[] readBuffer = new byte[1];
            int bytesNum = in.read(readBuffer);
            while (bytesNum > 0) {
                bytes = concat(bytes, readBuffer);
                bytesNum = in.read(readBuffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

    /**
     * 通过程序打开COM串口,设置监听器以及相关的参数
     * @return 返回1 表示端口打开成功,返回 0表示端口打开失败
     */
    public int startComPort() {
        // 通过串口通信管理类获得当前连接上的串口列表
        try {
            Log.info("开始获取串口。。。");
            portList = CommPortIdentifier.getPortIdentifiers();
            Log.info("获取串口。。。" + portList);
            Log.info("获取串口结果。。。" + portList.hasMoreElements());

            while (portList.hasMoreElements()) {
                // 获取相应串口对象
                Log.info(portList.nextElement());
                portId = (CommPortIdentifier) portList.nextElement();

                System.out.println("设备类型:--->" + portId.getPortType());
                System.out.println("设备名称:---->" + portId.getName());
                // 判断端口类型是否为串口
                if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                    // 判断如果COM4串口存在,就打开该串口
//                if (portId.getName().equals(portId.getName())) {
                    if (portId.getName().equals(COMNUM)) {
                        try {
                            // 打开串口名字为COM_4(名字任意),延迟为1000毫秒
                            serialPort = (SerialPort) portId.open(portId.getName(), 1000);

                        } catch (PortInUseException e) {
                            System.out.println("打开端口失败!");
                            e.printStackTrace();
                            return 0;
                        }
                        // 设置当前串口的输入输出流
                        try {
                            inputStream = serialPort.getInputStream();
                            outputStream = serialPort.getOutputStream();
                        } catch (IOException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        // 给当前串口添加一个监听器,serialEvent方法监听串口返回的数据
                        try {
                            serialPort.addEventListener(this);
                        } catch (TooManyListenersException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        // 设置监听器生效,即:当有数据时通知
                        serialPort.notifyOnDataAvailable(true);

                        // 设置串口的一些读写参数
                        try {
                            // 比特率、数据位、停止位、奇偶校验位
                            serialPort.setSerialPortParams(9600,
                                    SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                                    SerialPort.PARITY_NONE);
                        } catch (UnsupportedCommOperationException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        return 1;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            Log.info(e);
            return 0;
        }
        return 0;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            System.out.println("--------------任务处理线程运行了--------------");
            while (true) {
                // 如果堵塞队列中存在数据就将其输出
                try {
                    if (msgQueue.size() > 0) {
                        String vo = msgQueue.peek();
                        String vos[] = vo.split("  ", -1);
                        //根据返回数据可以做相应的业务逻辑操作
//                        getData(vos);
//                        sendOrder();
                        msgQueue.take();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 16转10计算
    public long getNum(String num1, String num2) {
        long value = Long.parseLong(num1, 16) * 256 + Long.parseLong(num2, 16);
        return value;
    }

    // 字节数组转字符串
    private String printHexString(byte[] b) {
        StringBuffer sbf = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sbf.append(hex.toUpperCase() + "  ");
        }
        return sbf.toString().trim();
    }

    /**
     * 合并数组
     *
     * @param firstArray  第一个数组
     * @param secondArray 第二个数组
     * @return 合并后的数组
     */
    public static byte[] concat(byte[] firstArray, byte[] secondArray) {
        if (firstArray == null || secondArray == null) {
            if (firstArray != null)
                return firstArray;
            if (secondArray != null)
                return secondArray;
            return null;
        }
        byte[] bytes = new byte[firstArray.length + secondArray.length];
        System.arraycopy(firstArray, 0, bytes, 0, firstArray.length);
        System.arraycopy(secondArray, 0, bytes, firstArray.length, secondArray.length);
        return bytes;
    }

    //num:偶数启动报警器,奇数关闭报警器
    //commandInfo:偶数打开,奇数关闭;channel:继电器通道;comNum:串口设备通信名称
    public static void startRS485(int commandInfo,int channel,String comNum) {
        try {
            if(cRead == null){
                cRead = getInstance();
            }
            if (!COMNUM.equals(comNum) && null != serialPort){
                serialPort.close();
                COMNUM = comNum;
            }
            int i = 1;
            if (serialPort == null){
                COMNUM = comNum;
                //打开串口通道并连接
                i = cRead.startComPort();
            }
            if (i == 1){
                Log.info("串口连接成功");
                try {
                    //根据提供的文档给出的发送命令,发送16进制数据给仪器
                    byte[] b;
                    if (commandInfo % 2 == 0) {
                        b = onOrderList.get(channel);
                    }else{
                        b = offOrderList.get(channel);
                    }
                    System.out.println("发送的数据:" + b);
                    System.out.println("发出字节数:" + b.length);
                    outputStream.write(b);
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (outputStream != null) {
                            outputStream.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //每次调用完以后关闭串口通道
                    if (null != cRead){
                        if (null != serialPort){
                            serialPort.close();
                            serialPort = null;
                        }
                        cRead.interrupt();
                        cRead = null;
                    }
                }
            }else{
                Log.info("串口连接失败");
                return;
            }
        }catch (Exception e){
            e.printStackTrace();
            Log.info("串口连接失败");

        }
    }

    public static void main(String[] args) {
        //打开通道1的电路,对应设备名称COM3
        startRS485(0,1,"COM3");
    }
}

  代码比较繁杂,需要有点耐心才能完全了解,大家可以从startRS485()函数作为切入点阅读代码。当然,这个demo只是抛砖引玉,有相关开发需求的童鞋可以看一看,参考一下大概的思路。

加载全部内容

相关教程
猜你喜欢
用户评论