赞
踩
概述:用Java的方式和我们的硬件(传感器)通过串口(COM3)实现数据交互等功能,基于UART协议实现
Java串口通讯是指使用Java编程语言与串行端口(串口)进行数据通讯的过程,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式(注意,是一位一位的传输,区别于并口通讯,传输慢)。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。虽然说慢,不如并,但不代表就要抛弃,某些项目还是很有用的,例如公司最近营养探索馆的一个血压探测仪,就是串口通讯,需要开发串口。
优点:
缺点:
并口通讯是通过计算机的并行接口(并口)进行数据传输的方式。并口通讯可以同时传输多个比特(通常是8位或更多),因此传输速度相对较快。并口通讯通常用于连接一些需要高速数据传输的外部设备,如打印机、扫描仪等。并口通讯所需的线缆较为复杂,包括大量的数据线和控制线,连接和布线相对复杂。
优点:
缺点:
综合来看,串口通讯适用性广泛、连接简单,但传输速度较慢;而并口通讯传输速度快,能够连接多个设备,但线缆复杂且不易远程传输。因此在实际应用中,选择串口通讯还是并口通讯取决于具体的应用场景和需求。
1.通过comm.jar实现太老了,对jdk版本有要求(jdk 1.8 32bit),也要配置dll文件
2.通过RXTXcomm.jar实现(更新慢),需要配置dll文件,打包可能会出错
3.通过jserialcomm实现(推荐)跨平台,兼容性好
1.下载comm.jar相关资源,将javax.comm.properties,win32com.dll配置文件放到jdk的bin和lib目录
2.准备好一个串口工具,和模拟串口的工具,便于测试
关于依赖引用问题:
当maven拉取不到相关依赖时,有两种解决方案
1.作为资源文件夹内的资源引用
我们可以选择将该依赖的jar包下载下来,通过创建lib文件夹的方式将依赖添加到项目中
<dependency>
<groupId>gun.io</groupId>
<artifactId>rxtx</artifactId>
<version>1.0.0</version>
<!--system,类似provided,需要显式提供依赖的jar以后,Maven就不会在Repository中查找它-->
<scope>system</scope>
<systemPath>${project.basedir}/lib/RXTXcomm.jar</systemPath>
</dependency>
2.将jar包手动安装到maven仓库
1.用Configure Virtual Serial Port Driver虚拟串口工具模拟2个串口号
2.导入相应依赖 comm.jar包并添加到项目中,jdk版本为1.8 32bit
3.通过comm.jar包提供的方法和串口建立通讯
4.通过CommPortIdentifier.getPortIdentifiers()方法获取可用串口
5.选择合适端口,通过该commPort.open()方法打开该端口
6.设置常用参数,如波特率,停止位,校验位等 serialPort.setSerialPortParams()
7.端口打开后,可用通过serialPort.addEventListener()方法添加监听器监听我们之前打开的端口
8.通过程序向端口发送数据,核心是基于流的形式,通过outputStream.write()发送数据
package com.xxxx.ckcomm.utils; import javax.comm.*; import java.io.*; import java.util.*; public class DSerialPort implements Runnable, SerialPortEventListener { private String appName = "串口通讯测试"; private int timeout = 2000;// open 端口时的等待时间,延迟时间(毫秒数) private int threadTime = 0; private CommPortIdentifier commPort; private SerialPort serialPort; private InputStream inputStream; private OutputStream outputStream; //当前接收COM口的数据 public static String receiptDataString = ""; //当前已取的数组下标 public static int nowDataIndex = 0; //当前接收COM口的数据自动切割成StringList public static ArrayList<String> receiptDataList = new ArrayList<String>(); static { String driverName = "com.sun.comm.Win32Driver"; CommDriver driver; try { driver = (CommDriver) Class.forName(driverName).newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } driver.initialize(); } /** * @方法名称 :listPort * @功能描述 :列出所有可用的串口 * @返回值类型 :List<String> */ @SuppressWarnings("rawtypes") public List<String> listPort() { //获得当前所有可用串口 Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); ArrayList<String> portNameList = new ArrayList<>(); //将可用串口名添加到List并返回该List while (portList.hasMoreElements()) { String portName = portList.nextElement().getName(); portNameList.add(portName); } return portNameList; } /** * @param portName * @方法名称 :selectPort * @功能描述 :选择一个端口,比如:COM1 * @返回值类型 :void */ @SuppressWarnings("rawtypes") public void selectPort(String portName) { this.commPort = null; CommPortIdentifier cpid; Enumeration portList = CommPortIdentifier.getPortIdentifiers(); while (portList.hasMoreElements()) { System.out.println("串口接口调用成功"); cpid = (CommPortIdentifier) portList.nextElement(); if (cpid.getPortType() == CommPortIdentifier.PORT_SERIAL && cpid.getName().equals(portName)) { this.commPort = cpid; break; } } openPort(); } /** * @方法名称 :openPort * @功能描述 :打开SerialPort * @返回值类型 :void */ private void openPort() { if (commPort == null) log("无法找到串口!"); else { log("端口选择成功,当前端口:" + commPort.getName() + ",现在实例化 SerialPort:"); try { serialPort = (SerialPort) commPort.open(appName, timeout);//打开端口 serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); //设置参数 log("实例 SerialPort 成功!"); } catch (PortInUseException e) { throw new RuntimeException(String.format("端口'%1$s'正在使用中!", commPort.getName())); } catch (UnsupportedCommOperationException e) { e.printStackTrace(); } } } /** * @方法名称 :checkPort * @功能描述 :检查端口是否正确连接 * @返回值类型 :void */ public void checkPort() { if (commPort == null) throw new RuntimeException("没有选择端口,请使用 " + "selectPort(String portName) 方法选择端口"); if (serialPort == null) { throw new RuntimeException("SerialPort 对象无效!"); } } /** * @方法名称 :write * @功能描述 :向端口发送数据,请在调用此方法前 先选择端口,并确定SerialPort正常打开! * @返回值类型 :void */ public void write(String message) { checkPort(); try { outputStream = new BufferedOutputStream(serialPort.getOutputStream()); } catch (IOException e) { throw new RuntimeException("获取端口的OutputStream出错:" + e.getMessage()); } try { outputStream.write(message.getBytes()); log("信息发送成功!"); } catch (IOException e) { throw new RuntimeException("向端口发送信息时出错:" + e.getMessage()); } finally { try { outputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public void write(byte[] message) { checkPort(); try { outputStream = new BufferedOutputStream(serialPort.getOutputStream()); } catch (IOException e) { throw new RuntimeException("获取端口的OutputStream出错:" + e.getMessage()); } try { outputStream.write(message); log("信息发送成功!"); } catch (IOException e) { throw new RuntimeException("向端口发送信息时出错:" + e.getMessage()); } finally { try { outputStream.close(); } catch (Exception e) { } } } /** * @param time 监听程序的存活时间,单位为秒,0 则是一直监听 * @方法名称 :startRead * @功能描述 :开始监听从端口中接收的数据 * @返回值类型 :void */ public void startRead(int time) { checkPort(); try { inputStream = new BufferedInputStream(serialPort.getInputStream()); } catch (IOException e) { throw new RuntimeException("获取端口的InputStream出错:" + e.getMessage()); } try { serialPort.addEventListener(this);//向SerialPort对象中添加串口事件监听器 } catch (TooManyListenersException e) { throw new RuntimeException(e.getMessage()); } serialPort.notifyOnDataAvailable(true);//设置串口有数据的事件true有效,false无效 log(String.format("开始监听来自'%1$s'的数据--------------", commPort.getName())); if (time > 0) { this.threadTime = time * 1000; Thread t = new Thread(this); t.start(); log(String.format("监听程序将在%1$d秒后关闭。。。。", threadTime)); } } /** * @方法名称 :close * @功能描述 :关闭 SerialPort * @返回值类型 :void */ public void close() { serialPort.close(); serialPort = null; commPort = null; } public void log(String msg) { System.out.println(appName + " --> " + msg); } /** * 数据接收的监听处理函数 */ @Override public void serialEvent(SerialPortEvent arg0) { switch (arg0.getEventType()) { case SerialPortEvent.BI:/* Break interrupt,通讯中断 */ case SerialPortEvent.OE:/* Overrun error,溢位错误 */ case SerialPortEvent.FE:/* Framing error,传帧错误 */ case SerialPortEvent.PE:/* Parity error,校验错误 */ case SerialPortEvent.CD:/* Carrier detect,载波检测 */ case SerialPortEvent.CTS:/* Clear to send,清除发送 */ case SerialPortEvent.DSR:/* Data set ready,数据设备就绪 */ case SerialPortEvent.RI:/* Ring indicator,响铃指示 */ case SerialPortEvent.OUTPUT_BUFFER_EMPTY:/* * Output buffer is * empty,输出缓冲区清空 */ break; case SerialPortEvent.DATA_AVAILABLE:/* * Data available at the serial * port,端口有可用数据。读到缓冲数组,输出到终端 */ byte[] readBuffer = new byte[1024]; StringBuilder readStr = new StringBuilder(); String s2 = ""; try { while (inputStream.available() > 0) { inputStream.read(readBuffer); readStr.append(new String(readBuffer).trim()); } s2 = new String(readBuffer).trim(); //接收的精华再这里 //1。readStr为当次读入的,一般设备是1位1位读,模拟的时候就很多位,但是不重要 //2。receiptDataString是用来缓存输入字符串的 //3。receiptDataString.length()==XX这里可以设定你要接受的长度,然后接收指定数据 //4。超长或者不符合长度,你可以看情况抛弃数据或者清空,或者累加 //5。接受成功的数据,放入receiptDataList供获取调用 //6。nowDataIndex是当前数组的下标,可以参考PortController中对数据获取的方法 log("接收端口COM->返回数据(长度为" + readStr.length() + "):数据" + s2); receiptDataString += readStr; log("receiptDataString->长度" + receiptDataString.length() + "),数据" + receiptDataString); if (receiptDataString.length() == 58) { receiptDataList.add(receiptDataString); receiptDataString = ""; log("校验通过,数据接收成功"); } else if (receiptDataString.length() > 100) { receiptDataString = ""; } } catch (IOException e) { throw new RuntimeException(e); } } } @Override public void run() { try { timerTest(threadTime); serialPort.close(); } catch (Exception e) { e.printStackTrace(); } } //监听串口指定时间,以秒为单位 public void timerTest(int minutes){ Timer timer = new Timer(); timer.schedule(new TimerTask() { int secondsLeft = minutes ; public void run() { if (secondsLeft > 0) { secondsLeft--; } else { log(String.format("端口'%1$s'监听关闭了!", commPort.getName())); timer.cancel(); // 结束计时器 } } }, 0, 1000); // 每隔一秒执行一次 } public static void main(String[] args) { DSerialPort sp = new DSerialPort(); sp.listPort(); sp.selectPort("COM3"); sp.write("2"); sp.startRead(120); sp.close(); } }
1.下载RXTXcomm的资源包
2.将rxtxParallel.dll和rxtxSerial.dll两个文件放到jdk下jre的bin目录
1.导入RXTXcomm.jar的依赖
2.建立连接,通过CommPortIdentifier.getPortIdentifier(portName)方法可用获取到对应端口的对象
3.打开端口,portIdentifier.open(portName, timeout);/portName:端口名 timeout:超时时间
4.判断该端口是否为串口,如果是设置串口的参数,如波特率,停止位,数据位,检验位
serialPort.setSerialPortParams(baudRate, SerialPort.*DATABITS_8*, SerialPort.*STOPBITS_1*,SerialPort.*PARITY_NONE*);
5.添加监听器,监听我们打开的端口,用于获取传感器向我们发送的数据
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);//设置串口数据时间有效(可监听)
6.关闭串口,serialPort.close();
SeriaTool.class
package com.xxxx.rxtxcomm.utils; import com.alibaba.fastjson.JSON; import com.xxxx.rxtxcomm.entity.SmCommMsg; import com.xxxx.rxtxcomm.exception.BusinessException; import com.xxxx.rxtxcomm.mqtt.MqttUtil; import com.xxxx.rxtxcomm.response.ResultCode; import com.xxxx.rxtxcomm.service.CommService; import gnu.io.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.*; /** * @author Long */ public class SerialTool implements SerialPortEventListener { private static SerialPort serialPort; private static InputStream input; private static OutputStream output; private static volatile SerialTool serialTool; /** * 获取提供服务的SerialTool对象 * * @return serialTool */ public static SerialTool getInstance() { if (serialTool == null) { synchronized (SerialTool.class) { //使用同步锁进行双重确认,防止生成两个实例 if (serialTool == null) { serialTool = new SerialTool(); } } } return serialTool; } //私有化SerialTool类的构造方法,不允许其他类生成SerialTool对象 private SerialTool() { } /** * 查找所有可用串口 * * @return 可用端口名称列表 */ public List<String> findPort() { //获取系统中所有通讯端口 Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers(); List<String> portNameList = new ArrayList<>(); //将可用串口名添加到List并返回该List while (portList.hasMoreElements()) { CommPortIdentifier commPortIdentifier = portList.nextElement(); //判断是否是串行端口 if (commPortIdentifier.getPortType() == CommPortIdentifier.PORT_SERIAL) { String portName = commPortIdentifier.getName(); portNameList.add(portName); } } return portNameList; } /** * 打开串口 * * @param portName 端口名称 * @param baudRate 波特率 */ public void openPort(String portName, int baudRate) { try { //通过端口名识别端口 CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); //打开端口,并给端口名字和一个timeout(打开操作的超时时间) CommPort commPort = portIdentifier.open(portName, 2000); //判断是不是串口 if (commPort instanceof SerialPort) { serialPort = (SerialPort) commPort; try { //设置一下串口的波特率等参数 serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); input = serialPort.getInputStream(); output = serialPort.getOutputStream(); serialPort.addEventListener(this); //设置当有数据到达时唤醒监听接收线程 serialPort.notifyOnDataAvailable(true); //设置当通信中断时唤醒中断线程 serialPort.notifyOnBreakInterrupt(true); } catch (UnsupportedCommOperationException e) { throw new BusinessException(ResultCode.PARAM_NOT_COMPLETE.getCode(), ResultCode.PARAM_NOT_VALID.getMessage()); } catch (IOException e) { throw new RuntimeException(e); } catch (TooManyListenersException e) { throw new BusinessException(ResultCode.COMM_PORT_LISTENERS_MANY.getCode(), ResultCode.COMM_PORT_LISTENERS_MANY.getMessage()); } System.out.println("Open " + portName + " successfully !"); System.out.println("-- 开始监听 " + portName + " 端口的数据 --"); } else { throw new BusinessException(ResultCode.COMM_PORT_NOT_PORT.getCode(), ResultCode.COMM_PORT_NOT_PORT.getMessage()); } } catch (Exception e1) { try { throw new Exception(); } catch (Exception e) { throw new RuntimeException(e); } } } /** * 关闭串口 */ public void closePort() { if (serialPort != null) { serialPort.close(); } if (input != null) { try { input.close(); } catch (IOException e) { throw new RuntimeException(e); } } if (output != null) { try { output.close(); } catch (IOException e) { throw new RuntimeException(e); } } } /** * 往串口发送数据 * * @param order 待发送数据 */ public void sendToPort(String message) { try { output = serialPort.getOutputStream(); output.write(message); output.flush(); System.out.println("成功发送数据,发送的数据为:" + message); System.out.println("========================================================================"); System.out.println(); } catch (IOException e) { try { throw new Exception(); } catch (Exception ex) { throw new RuntimeException(ex); } } } @Override public synchronized void serialEvent(SerialPortEvent oEvent) { try { //让该线程延迟一会,保证输出的顺序性,先发数据,在接收数据 Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } switch (oEvent.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: // 有数据到达 // 调用读取数据的方法 readData(); break; default: break; } } public void readData() { try { String strMsg = ""; int len = input.available(); byte[] buffer = new byte[len]; input.read(buffer, 0, len); String data = DataChangeUtil.byteArrayToHexString(buffer); // 处理接收到的数据 System.out.println("接收到的数据为==> " + data); System.out.println("========================================================================"); System.out.println(); } catch (Exception e) { e.printStackTrace(); } } }
package com.xxxx.rxtxcomm.controller; import com.xxxx.rxtxcomm.response.R; import com.xxxx.rxtxcomm.service.CommService; import com.xxxx.rxtxcomm.utils.SerialTool; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Api(value = "Java串口通讯测试") public class CommController { public static SerialTool serialTool = SerialTool.getInstance(); @ApiOperation(value = "初始化串口") @PostMapping("/init") public R init(String portName, int baudRate) { serialTool.openPort(portName, baudRate); return R.ok(); } @ApiOperation(value = "向传感器发送消息") @PostMapping("/sendMsg") public R sendMsg(String message) { serialTool.sendToPort(message); return R.ok(); } @ApiOperation(value = "释放资源,关闭串口") @PostMapping("/closePort") public R closePort() { serialTool.closePort(); return R.ok(); } }
1.引入依赖
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>[2.0.0,3.0.0)</version>
</dependency>
2.和模块建立连接
SerialPort serialPort=SerialPort.getCommPort(portName) //portName:端口名 例如:COM3
3.通过SerialPort的实例化对象添加一个监听器
serialPort.addDataListener(new DataListener());(DataListenner为自定义类)
4.开始向模块发送指令,如读取传感器监测气体类型
serialPort.writeBytes(data, data.length);
5.如果传感器有回传数据,此时我们的监听器会监听并记录
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) return;
SerialPort comPort = event.getSerialPort();
byte[] newData = new byte[comPort.bytesAvailable()];
comPort.readBytes(newData, newData.length);
readData(newData);
}
6.释放资源,关闭端口
serialPort.closePort();
package com.xxxx.jserialcomm.utils; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; import lombok.extern.slf4j.Slf4j; @Slf4j public class SerialCommUtil { private SerialPort serialPort; private static SerialCommUtil serialCommUtil; public static SerialCommUtil getSerialCommUtil() { if (serialCommUtil == null) { synchronized (SerialCommUtil.class) { if (serialCommUtil == null) { serialCommUtil = new SerialCommUtil(); } } } return serialCommUtil; } /** * 打开串口 * * @param portName 串口名称(例如 "COM3" 或 "/dev/ttyUSB0") * @param baudRate 波特率 * @return 是否成功打开 */ public boolean openPort(String portName, int baudRate) { serialPort = SerialPort.getCommPort(portName); serialPort.setBaudRate(baudRate); serialPort.setNumDataBits(8); serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT); serialPort.setParity(SerialPort.NO_PARITY); boolean flag = serialPort.openPort(); serialPort.addDataListener(new DataListener()); return flag; } /** * 关闭串口 */ public void closePort() { if (serialPort != null && serialPort.isOpen()) { serialPort.closePort(); } } /** * 发送数据 * * @param data 要发送的数据 * @return 是否成功发送 */ public boolean sendData(byte[] data) { if (serialPort == null || !serialPort.isOpen()) { return false; } int bytesSent = serialPort.writeBytes(data, data.length); return bytesSent == data.length; } // 数据监听器 private static class DataListener implements SerialPortDataListener { @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) return; SerialPort comPort = event.getSerialPort(); byte[] newData = new byte[comPort.bytesAvailable()]; comPort.readBytes(newData, newData.length); readData(newData); } } public static void readData(byte[] newData) { try { String data = DataChangeUtil.byteArrayToHexString(newData); // 处理接收到的数据 System.out.println("接收到的数据为: " + data); System.out.println("========================================================================"); System.out.println(); } catch (Exception e) { log.error("Bad Things",e); } } /** * 接收数据 * * @param bufferSize 缓冲区大小cl * @return 接收到的数据 */ public byte[] receiveData(int bufferSize) { if (serialPort == null || !serialPort.isOpen()) { return null; } byte[] buffer = new byte[bufferSize]; int bytesRead = serialPort.readBytes(buffer, bufferSize); if (bytesRead > 0) { byte[] actualData = new byte[bytesRead]; System.arraycopy(buffer, 0, actualData, 0, bytesRead); return actualData; } else { return null; } } }
Controller
package com.xxxx.jserialcomm.controller; import com.xxxx.jserialcomm.response.R; import com.xxxx.jserialcomm.service.CommService; import com.xxxx.jserialcomm.utils.DataChangeUtil; import com.xxxx.jserialcomm.utils.SerialCommUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Api(value = "Java串口通讯测试") public class SemeaController { @Autowired CommService commService; public static SerialCommUtil serialCommUtil = SerialCommUtil.getSerialCommUtil(); @ApiOperation(value = "初始化串口") @PostMapping("/init") public R init(String portName, int baudRate) { boolean flag = serialCommUtil.openPort(portName, baudRate); if(flag){ return R.ok().data("msg","初始化成功"); }else{ return R.error().data("msg","初始化失败"); } } @ApiOperation(value = "向传感器发送消息") @PostMapping("/sendMsg") public R sendMsg( String message) { boolean flag = serialCommUtil.sendData(message.getBytes()); if(flag){ return R.ok().data("msg","消息发送成功:"+ message); }else{ return R.error().data("msg","消息发送失败"); } } @ApiOperation(value = "释放资源,关闭串口") @PostMapping("/closePort") public R closePort() { boolean b = serialCommUtil.closePort(); if(b){ return R.ok(); }else { return R.error(); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。