JAVA設(shè)備對接與串口通信詳解
一、背景
近期,我的項目需要對接各種設(shè)備,其中有幾臺設(shè)備采用了串口通信方式。由于之前對串口通信方式接觸不多,深感自己在這方面知識的欠缺,于是我決定對串口通信進(jìn)行深入的研究和開發(fā)。
我仔細(xì)學(xué)習(xí)了串口通信的原理、協(xié)議以及相關(guān)的編程技術(shù),并通過實(shí)踐不斷加深對串口通信的理解。
在這個過程中,我遇到了不少挑戰(zhàn),但通過不斷的學(xué)習(xí)和實(shí)踐,最終成功實(shí)現(xiàn)了與設(shè)備的串口通信。現(xiàn)在,我將自己的學(xué)習(xí)和開發(fā)經(jīng)驗(yàn)記錄下來,形成這篇博文,希望能夠?yàn)橛行枰耐瑢W(xué)提供一些幫助和指導(dǎo)。
二、串口通信技術(shù)介紹
串口是串行接口(serial port)的簡稱,也稱為串行通信接口或COM接口。
串口通信是指采用串行通信協(xié)議(serial communication)在一條信號線上將數(shù)據(jù)一個比特一個比特地逐位進(jìn)行傳輸?shù)耐ㄐ拍J健?/p>
串口通信是一種利用串行通信協(xié)議,在計算機(jī)與外部設(shè)備之間進(jìn)行異步通信的技術(shù)。串行通信按照時間順序,按位依次發(fā)送通信字節(jié),相較于并行通信,它僅需較少的數(shù)據(jù)線,通常兩根線即可實(shí)現(xiàn)雙向通信。在串行通信中,并行數(shù)據(jù)被轉(zhuǎn)換為串行數(shù)據(jù)后,通過傳輸線路依次傳輸。異步通信則是發(fā)送端和接收端通過起始位、停止位來同步數(shù)據(jù)塊的方式。發(fā)送端在發(fā)送數(shù)據(jù)字節(jié)前,會先發(fā)送一個起始位,然后依次發(fā)送數(shù)據(jù)字節(jié)的每個位,最后發(fā)送一個或多個停止位。接收端通過檢測起始位的狀態(tài)轉(zhuǎn)變來同步接收數(shù)據(jù),一旦檢測到起始位,便會根據(jù)事先約定的規(guī)則接收之后的數(shù)據(jù)位和停止位。
三、代碼示例
3.1 創(chuàng)建串口監(jiān)聽器
// 串口監(jiān)聽器
@Slf4j
public class MyLister implements SerialPortEventListener {
//private static final Logger log = LoggerFactory.getLogger(MyLister.class);
@Override
public void serialEvent(SerialPortEvent event) {
switch (event.getEventType()) {
// 串口存在有效數(shù)據(jù)
case SerialPortEvent.DATA_AVAILABLE:
byte[] bytes = SerialPortUtil.getSerialPortUtil().readFromPort(PortInit.serialPort);
log.info("===========start===========");
String str = new String(bytes, StandardCharsets.UTF_8);
log.info("{}【讀到的字符】:-----{}", LocalDateTime.now(), str);
log.info("{}【字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制字符串】:-----{}", LocalDateTime.now(), stringToHex(str));
log.info("===========end===========");
break;
// 2.輸出緩沖區(qū)已清空
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
log.error("輸出緩沖區(qū)已清空");
break;
// 3.清除待發(fā)送數(shù)據(jù)
case SerialPortEvent.CTS:
log.error("清除待發(fā)送數(shù)據(jù)");
break;
// 4.待發(fā)送數(shù)據(jù)準(zhǔn)備好了
case SerialPortEvent.DSR:
log.error("待發(fā)送數(shù)據(jù)準(zhǔn)備好了");
break;
// 10.通訊中斷
case SerialPortEvent.BI:
log.error("與串口設(shè)備通訊中斷");
break;
default:
break;
}
}
public static String stringToHex(String input) {
byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* ASCII碼轉(zhuǎn)字節(jié)數(shù)組
*/
public static byte[] asciiToIntArray(List<Integer> asciiValues) {
byte[] byteArray = new byte[asciiValues.size()];
for (int i = 0; i < asciiValues.size(); i++) {
int asciiValue = asciiValues.get(i);
byteArray[i] = (byte) asciiValue;
}
return byteArray;
}
/**
* 字符串轉(zhuǎn)ASCII碼
*/
public static List<Integer> stringToAscii(String input) {
List<Integer> asciiValues = new ArrayList<>();
for (char c : input.toCharArray()) {
asciiValues.add((int) c);
}
return asciiValues;
}
/**
* 16進(jìn)制字符串轉(zhuǎn)字節(jié)數(shù)組
*
* @param hex 16進(jìn)制字符串
* @return 字節(jié)數(shù)組
*/
public static byte[] hex2byte(String hex) {
if (!isHexString(hex)) {
return null;
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = "" + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}
/**
* 校驗(yàn)是否是16進(jìn)制字符串
*
* @param hex
* @return
*/
public static boolean isHexString(String hex) {
if (hex == null || hex.length() % 2 != 0) {
return false;
}
for (int i = 0; i < hex.length(); i++) {
char c = hex.charAt(i);
if (!isHexChar(c)) {
return false;
}
}
return true;
}
/**
* 校驗(yàn)是否是16進(jìn)制字符
*
* @param c
* @return
*/
private static boolean isHexChar(char c) {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
}
3.2 串口初始化
/**
* 串口初始化
*/
@Component
@Slf4j
public class PortInit implements ApplicationRunner {
public static SerialPort serialPort = null;
@Override
public void run(ApplicationArguments args) {
String portName = "COM1";
//查看所有串口
SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();
ArrayList<String> port = serialPortUtil.findPort();
log.info("發(fā)現(xiàn)全部串口:{}", port);
log.info("打開指定portName:{}", portName);
//打開該對應(yīng)portName名字的串口
PortInit.serialPort = serialPortUtil.openPort(
portName,
9600,
SerialPort.DATABITS_8,
SerialPort.PARITY_NONE,
SerialPort.PARITY_ODD);
//給對應(yīng)的serialPort添加監(jiān)聽器
serialPortUtil.addListener(PortInit.serialPort, new MyLister());
}
}
3.3 啟動類
@SpringBootApplication
public class SerialPortProjectApplication {
public static void main(String[] args) {
SpringApplication.run(SerialPortProjectApplication.class, args);
}
@PreDestroy
public void destroy() {
//關(guān)閉應(yīng)用前 關(guān)閉端口
SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();
serialPortUtil.removeListener(PortInit.serialPort);
serialPortUtil.closePort(PortInit.serialPort);
}
}
3.4 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xsd</groupId>
<artifactId>SerialPortProject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SerialPortProject</name>
<description>SerialPortProject</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!-- <version>3.5.2</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!-- PropertyUtils工具類 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
<!-- jwt依賴 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 串口內(nèi)容讀取 -->
<dependency>
<groupId>org.bidib.jbidib.org.qbang.rxtx</groupId>
<artifactId>rxtxcomm</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.5 發(fā)送的Job
@Component
@EnableScheduling
public class SendJob {
/**
* 定時發(fā)送數(shù)據(jù)
* initialDelay :系統(tǒng)啟動后,3秒后開始運(yùn)行此方法
* fixedDelay:表示此方法運(yùn)行結(jié)束后,過1秒再次運(yùn)行此方法
*/
@Scheduled(initialDelay = 1000 * 3, fixedDelay = 1000)
public void plcAnalytic() {
String s = "4040000900手動滑稽阿薩德和靜安寺060004000000100172323";
SerialPortUtil.getSerialPortUtil().sendToPort(PortInit.serialPort, MyLister.asciiToIntArray(MyLister.stringToAscii(s)));
}
}
3.6 模擬窗口發(fā)布的軟件

四、總結(jié)
串口通信因其簡單性、靈活性和通用性,在眾多應(yīng)用場景中仍然保持著極高的實(shí)用價值。
深入理解串口的工作原理,掌握串口通信接口的選擇與編程技巧,對于更好地應(yīng)用串口技術(shù)、設(shè)計出更為可靠的通信系統(tǒng)具有重要意義。
這不僅能夠幫助我們充分發(fā)揮串口通信的優(yōu)勢,還能在不同應(yīng)用場景中靈活應(yīng)對各種通信需求,確保數(shù)據(jù)傳輸?shù)姆€(wěn)定性和準(zhǔn)確性。因此,深入學(xué)習(xí)和掌握串口通信的相關(guān)知識,是提升通信系統(tǒng)設(shè)計能力、實(shí)現(xiàn)高效數(shù)據(jù)傳輸?shù)年P(guān)鍵所在。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 如何使用Dataway配置數(shù)據(jù)查詢接口
這篇文章主要介紹了SpringBoot 如何使用Dataway配置數(shù)據(jù)查詢接口,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
java lambda 表達(dá)式中的雙冒號的用法說明 ::
這篇文章主要介紹了java lambda 表達(dá)式中的雙冒號的用法說明 ::具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Hibernate三種狀態(tài)和Session常用的方法
本文主要介紹了Hibernate三種狀態(tài)和Session常用的方法,具有很好的參考價值,下面跟著小編一起來看下吧2017-03-03
Netty網(wǎng)絡(luò)編程零基礎(chǔ)入門
Netty是一個異步的、基于事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用框架,用于快速開發(fā)可維護(hù)、高性能的網(wǎng)絡(luò)服務(wù)器和客戶端,如果你還不了解它的使用,就趕快繼續(xù)往下看吧2022-08-08
SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解
這篇文章主要為大家介紹了SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
已解決:No ''Access-Control-Allow-Origin''跨域問題
這篇文章主要介紹了已解決:No 'Access-Control-Allow-Origin' 跨域,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

