zoukankan      html  css  js  c++  java
  • Java实现RS485串口通信

    前言

      前段时间赶项目的过程中,遇到一个调用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只是抛砖引玉,有相关开发需求的童鞋可以看一看,参考一下大概的思路。

  • 相关阅读:
    Python中的数据库连接与查询——使用SQLAlchemy
    Python中的数据库连接与查询——使用PyMySQL
    Selenium爬虫
    Scrapy爬虫
    Weka的基本概念和操作介绍
    Leetcode练习(Python):第860题: 柠檬水找零: 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
    Leetcode练习(Python):第771题:宝石与石头: 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。
    Leetcode练习(Python):第747题:至少是其他数字两倍的最大数: 在一个给定的数组nums中,总是存在一个最大元素 。 查找数组中的最大元素是否至少是数组中每个其他数字的两倍。 如果是,则返回最大元素的索引,否则返回-1。
    Leetcode练习(python):第728题:自除数:自除数 是指可以被它包含的每一位数除尽的数。
    istio-http流量管理(2)
  • 原文地址:https://www.cnblogs.com/JohanChan/p/14267756.html
Copyright © 2011-2022 走看看