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只是抛砖引玉,有相关开发需求的童鞋可以看一看,参考一下大概的思路。