串口通信是在工程应用中很常见。在上位机与下位机通讯过程中常通过有线的串口进行通信,在低速传输模式下串口通信得到广泛使用。在说个之前先来简单解释一下上位机与下位机的概念。

上位机与下位机

通常上位机指的是PC,下位机指的是单片机或者带微处理器的系统。下位机一般是将模拟信号经过AD采集将模拟量转换为数字量,下位机再经过数字信号处理以后将数字信号通过串口发送到上位机,相反上位机可以给下位机发送一些指令或者信息。常见的通信串口包括RS232、RS485、RS422等。这些串口只是在电平特性有所不同,在上位机与下位机进行数据通信时可以不考虑电平特性,而且现在在硬件上有各种转接接口,使用起来也很方便。

当然在通常做简单的串口UART实验时我们可以使用各种各样的串口助手小软件,但是这些串口小工具有时候并不能很好满足需求,那就尝试着自己写一套属于自己的串口助手?接下来说说如何使用java实现上位机与下位机之间的RS485串口通信。

step 1: 下载支持java串口通信的jar包,这里给出下载地址:

http://files.cnblogs.com/files/Dreamer-1/mfz-rxtx-2.2-20081207-win-x86.zip(32bit 下载地址)

http://files.cnblogs.com/files/Dreamer-1/mfz-rxtx-2.2-20081207-win-x64.zip (64位下载地址)

对以上的版本解释一下,因为本人在这里踩了一个坑,32位或者64位是与ecplise/myecplise一致,要是版本弄错了会报错。

step 2:下载了那个jar包解压后会出现一下内容:

这个文件夹里面需要注意两点:jar包RXTXcomm需要导入到java工程里面去。另外就是需要将rxtxParallel.dll与rxtxSerial.dll复制在安转JDK的bin文件下和jre的bin文件夹下面,这样才能保证能够正常使用这个jar包。以下是将两个dll文件复制的位置:

C:\Program Files (x86)\Java\jdk1.8.0_25\bin\
C:\Program Files (x86)\Java\jdk1.8.0_25\jre\bin\

怎么讲jar包导入java工程里面就是比较简单的操作,可以参考:http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html

step 3:RXTXComm Api如何使用

接下来就是使用该导入jar包进行编码实现串口通信的功能了。在编码之前先来理一理串口通信的主要环节,本人总结主要分为以下几点:

1)计算机首先需要进行硬件check,查找是否有可用的COM端口,并对该对端口进行简要判断,包括这些端口是否是串口,是否正在使用。以下是部分主要代码:

     /*类方法 不可改变 不接受继承* 扫描获取可用的串口* 将可用串口添加至list并保存至list*/public static final ArrayList<String> uartPortUseAblefind(){//获取当前所有可用串口 //由CommPortIdentifier类提供方法Enumeration<CommPortIdentifier> portList=CommPortIdentifier.getPortIdentifiers();ArrayList<String> portNameList=new ArrayList();//添加并返回ArrayListwhile(portList.hasMoreElements()){String portName=portList.nextElement().getName();portNameList.add(portName); }return portNameList;}

以下是测试类的测试实例:

ArrayList<String> arraylist=UARTParameterSetup.uartPortUseAblefind();int useAbleLen=arraylist.size();if(useAbleLen==0){System.out.println("没有找到可用的串口端口,请check设备!");}else{   System.out.println("已查询到该计算机上有以下端口可以使用:");for(int index=0;index<arraylist.size();index++){System.out.println("该COM端口名称:"+arraylist.get(index));//测试串口配置的相关方法} }   

2)通过计算机对串口的自检后,可以对串口参数进行简单的配置。常见的配置可以从常见的串口助手中得到启发。以下是一个串口助手的人机交换界面:

以下是对串口设置主要代码:

     /** 串口常见设置* 1)打开串口* 2)设置波特率 根据单板机的需求可以设置为57600 ...* 3)判断端口设备是否为串口设备* 4)端口是否占用* 5)对以上条件进行check以后返回一个串口设置对象new UARTParameterSetup()* 6)return:返回一个SerialPort一个实例对象,若判定该com口是串口则进行参数配置*   若不是则返回SerialPort对象为null*/public static final SerialPort portParameterOpen(String portName,int baudrate){SerialPort serialPort=null;try {  //通过端口名识别串口CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);//打开端口并设置端口名字 serialPort和超时时间 2000msCommPort commPort=portIdentifier.open(portName,1000);//进一步判断comm端口是否是串口 instanceofif(commPort instanceof SerialPort){System.out.println("该COM端口是串口!");//进一步强制类型转换serialPort=(SerialPort)commPort;//设置baudrate 此处需要注意:波特率只能允许是int型 对于57600足够//8位数据位//1位停止位//无奇偶校验serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8,SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);//串口配制完成 logSystem.out.println("串口参数设置已完成,波特率为"+baudrate+",数据位8bits,停止位1位,无奇偶校验");}//不是串口else{System.out.println("该com端口不是串口,请检查设备!");//将com端口设置为null 默认是null不需要操作}} catch (NoSuchPortException e) {e.printStackTrace();} catch (PortInUseException e) {e.printStackTrace();} catch (UnsupportedCommOperationException e){e.printStackTrace();}return serialPort;      }

以上代码就是返回一个对象,同时也返回了对象属性,因为对象在java里面是属于传值引用。对以上需要说明的是:在实验时需要连接串口才能让计算机检测到才能让程序工作,这里使用的是RS485转接线:

3)通过以上两个步骤后基本对串口的设置也完成了,对于串口类型的确认例如:RS232/RS485/RS422等,可以作为进一步确认的条件。RS485可以在gnu.io中找到。

接下来就是上位机与下位机之间的双向通信的功能实现了。该部分主要是利用java的输入输出流来实现。以下是主要代码:

/** 串口数据发送以及数据传输作为一个类* 该类做主要实现对数据包的传输至下单板机*/
class DataTransimit
{/** 上位机往单板机通过串口发送数据* 串口对象 seriesPort * 数据帧:dataPackage* 发送的标志:数据未发送成功抛出一个异常*/public static void uartSendDatatoSerialPort(SerialPort serialPort,byte[] dataPackage){OutputStream out=null;try{out=serialPort.getOutputStream();out.write(dataPackage);out.flush();} catch (IOException e) {e.printStackTrace();}finally{//关闭输出流if(out!=null){try {out.close();out=null;System.out.println("数据已发送完毕!");} catch (IOException e) {e.printStackTrace();}   }}           }/** 上位机接收数据* 串口对象seriesPort* 接收数据buffer* 返回一个byte数组*/public  static  byte[] uartReceiveDatafromSingleChipMachine(SerialPort serialPort){byte[] receiveDataPackage=null;InputStream in=null;try {in=serialPort.getInputStream();//获取data buffer数据长度int bufferLength=in.available();while(bufferLength!=0){receiveDataPackage=new byte[bufferLength];in.read(receiveDataPackage);bufferLength=in.available();}} catch (IOException e) {e.printStackTrace();}return receiveDataPackage;}   

通过以上关于Uart两个基本类实现对底层Uart的功能封装,其中一个类主要负责Uart串口自检和基本设置,另外一个类主要has数据传输的两个方法。接下来以一个实例说一说通过RS485串口通信将系统当前时间发送至单板机系统。

step 4:实现实时系统时间的数据包传输至下位机

这一步可以分为以下两个步骤:首先实现获取系统时间,将时间进行封装成帧;另外就是通过RS485串口将时间数据包发送至单板机系统进行解析。

1) 系统时间的获取

根据java面对对象设计思想,这里将有关系统时间的方法归为一类。

以下是获取当前系统时间代码:

public static String getCurrentDateTime(){//单例模式Calendar calendar=Calendar.getInstance();int year = calendar.get(Calendar.YEAR);//获取年份  int month=calendar.get(Calendar.MONTH);//获取月份   int day=calendar.get(Calendar.DATE);//获取日期  int minute=calendar.get(Calendar.MINUTE);//分   int hour=calendar.get(Calendar.HOUR);//小时   int second=calendar.get(Calendar.SECOND);//秒  String curerentDateTime = year + " " + (month + 1 )+ " " + day + " "+ (hour+12) + " " + minute + " " + second + " ";timeCheckSum=year+(month+1)+day+(hour+12)+minute+second;return curerentDateTime;  }

java 提供了calender类,该类提供了一些与时间有关方法。至于Calendar.getInstance()使用单例模式获取一个Calendar实例对象,单例模式就是一个类在任何时候只允许有一个实例化对象。获取系统时间除了使用Calendar还可以使用Date类,通过创建对象也可以实现系统当前时间的获取。timeCheckSum作为时间数据的校验和发送至单板机作为自定义协议的一部分。

由于发送的数据包通常是以字节(byte)为单位进行发送和传输的,因此需要将int型的时间转换为byte使用byte[]进行存储,作为一个数据包发送。

    /** 将以上时间字符串进行隔开用byte[]保存*/public static byte[] dateTimeBytesGet(String currenDateTime){//对当前时间参数进行格式判断//对格式进行判断int rawDataSize=6;byte[] dateTimeBytes=new byte[rawDataSize+1];String[] currentDateTimeSplit=currenDateTime.split(" ");if(currentDateTimeSplit.length==rawDataSize){//时间数据格式正确//eg 2016 12 23 22 18 26//使用byte[]进行存储时需要 -128~+127//对于年份使用两个byte存储for(int dataIndex=0;dataIndex<rawDataSize;dataIndex++){int dateTemp=Integer.parseInt(currentDateTimeSplit[dataIndex]);if(dataIndex==0){byte H8bits=(byte)((dateTemp)>>8);byte L8bits=(byte)((dateTemp)&0xff);dateTimeBytes[dataIndex]= H8bits;dateTimeBytes[dataIndex+1]= L8bits;}dateTimeBytes[dataIndex+1]=(byte)dateTemp;}}else{System.out.println("当前时间获取出现异常数据");System.exit(-1);dateTimeBytes=null;}return dateTimeBytes;}

以上数据可以使用7个byte对时间数据进行存储,因为年份需要使用两个字节来存储,格式为高字节在前,低字节在后,之后依次存放。

将时间数据存放在byte[]数组以后接下来就是添加自己的协议部分了。该部分具有较大的随意性,因为该协议可以根据不同的风格有不同的形式。为了简单起见,只需要在时间数据byte[]之前添加head、CMD、时间数据长度length这三个字节进行补充,时间数据byte[]后面依次添加校验和的高低字节以及tail指令即可。以上基本实现了一个简单的时间数据package。以下是本模块的代码:

    /** 将数组封装成帧* 每一个数据帧由以下几个部分组成* 1)数据包头部 head 0X2F* 2)数据包命令 CMD  0X5A* 3)数据个数     length of data 7* 4)校验和         H8/L8 byte of  check sum(高字节在前 低字节在后)* 5)数据结尾标志 tail OX30* 6)可采用线程进行获取当前时间*/public static byte[] makeCurrentDateTimefromStringtoFramePackage(byte[] dateTimeBytes){//在时间byte[]前后添加一些package校验信息int dataLength=13;byte[] terimalTimePackage=new byte[dataLength];//装填信息//时间数据包之前的信息terimalTimePackage[0]=0x2F;terimalTimePackage[1]=0X5A;terimalTimePackage[2]=7;//计算校验和//转化为无符号进行校验for(int dataIndex=0;dataIndex<dateTimeBytes.length;dataIndex++){terimalTimePackage[dataIndex+3]=dateTimeBytes[dataIndex];}//将校验和分为高低字节byte sumH8bits=(byte)((timeCheckSum)>>8);byte sumL8bits=(byte)((timeCheckSum)&0xff);terimalTimePackage[10]=sumH8bits;//高字节在前terimalTimePackage[11]=sumL8bits;//低字节在后//数据包结尾terimalTimePackage[12]=0X30;return terimalTimePackage;}

下面给出了将时间数据byte数组进行解析的debug代码,一方面是确定上位机本部分模块的程序可靠性,另外也可以直接移植到下位机对数据包的解析之中。在下位机解析过程中需要注意一点:因为在java中8大基本类型都是带符号,年份时间和时间校验和拆分为高低字节时,低字节是二进制无符号的,但是计算机却是按照有符号数(补码方式)进行读取,例如在2016年转换为二进制数为:11111100000,那么高字节为00000111,低字节为11100000。计算机读取为:高字节为7,低字节为-32。其实由两个byte真实还原的过程应为:7<<8+(低字节二进制数字)=7*256+224=2016,因此在debug解析时间数据包时需要将有符号数字转换为无符号数字。

     /** 对时间格式进行解析并还原原来的时间格式* 对数据进行还原* 仅限于debug使用*/public static String dateTimeBytesfromTostring(byte[] currentDateTime){String string="";if(currentDateTime.length==7){string=((currentDateTime[0]<<8)+bytetoUnsigendInt(currentDateTime[1]))+" "+currentDateTime[2]+" "+currentDateTime[3]+" "+currentDateTime[4]+" "+currentDateTime[5]+" "+currentDateTime[6];}return string;}/** 将byte转化为字符串* 将有符号byte转化为无符号数字* debug使用*/public  static int bytetoUnsigendInt(byte aByte){String s=String.valueOf(aByte);//System.out.println(s);int bytetoUnsigendInt=0;for(int i=0;i<s.length();i++){if(s.charAt(i)!='0'){bytetoUnsigendInt+=1<<(7-i);}}return bytetoUnsigendInt;}

2)将最后的时间数据包通过RS485串口发送至下位机

结合前面的串口程序就可以使用串口发送程序了。在程序debug的前期可以在程序的关键位置输出日志就是打印log的方法可以提高程序调试的效率。以下是主类的测试代码:

//取出第一个COM端口进行测试SerialPort serialPort=UARTParameterSetup.portParameterOpen(arraylist.get(0), 57600);//退出程序 后续不需要监测 因为transimit一直需要保证连接状态//System.exit(0);DataTransimit.uartSendDatatoSerialPort(serialPort, dataFrame);String currentDateTime=SystemDateTimeGet.getCurrentDateTime();System.out.println(currentDateTime);byte[] bytes=SystemDateTimeGet.dateTimeBytesGet(currentDateTime);//System.out.println(Arrays.toString(bytes));String str=SystemDateTimeGet.dateTimeBytesfromTostring(bytes);System.out.println(str);//System.out.println(SystemDateTimeGet.bytetoUnsigendInt((byte) -32));byte[] terimalTimeByte=SystemDateTimeGet.makeCurrentDateTimefromStringtoFramePackage(bytes);System.out.println(Arrays.toString(terimalTimeByte));DataTransimit.uartSendDatatoSerialPort(serialPort, terimalTimeByte);

以下是测试结果:

当没有串口设备接入计算机时控制台打印一条信息:

没有找到可用的串口端口,请check设备!

当RS485设备接入计算机时,控制台打印消息如下:

通过以上几个步骤基本实现了上位机与下位机串口通信的功能,接下来还可以对程序进行改进:

1)添加界面,可以类比串口助手界面根据自身需要设计独具风格的人机交互界面。

2) 在程序中添加线程,在以上程序中对于系统时间的获取可以通过线程的方式进行获取,这样上位机就可以一直往下位机发送数据包,而不是仅仅发一次。

3)对于上位机数据接收,除了以上最基本的接收功能外,还可以使用JDBC与mysql等数据进行存储,并绘画数据曲线实现特性分析。

java实现上位机与下位机串口通信相关推荐

  1. QT5实现串口收发数据(上位机与下位机通信)

    最近帮老师做一个应用程序,通过上位机与下位机进行串口通信,最后实现实时绘图,通过几天努力,成功实现蓝牙串口通信. 参考博客1 注意:代码中一些与串口无关代码,可以忽略掉 一.QT5串口基础知识 1. ...

  2. 上下位机通讯协议_上位机与下位机的区别通讯

    上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接 ...

  3. 上位机和下位机的概念,理解如何实现PC从PLC中读取数据?

    市面上的PLC有上百种, 西门子的, 三菱的, 欧姆龙的等等. 上位机和下位机的理解: 上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/ ...

  4. 打开单片机世界的大门——上位机控制下位机实例详解

    上位机控制下位机实例详解 一.基本概念 上位机与下位机 串口 数据表达 二.下位机程序 三.上位机程序 四.总结 一.基本概念 在开始讲解前,先来看几个基本概念,如果是有基础的大佬,请直接跳到下一节. ...

  5. 超详细Klipper 上位机与下位机配置

    (适用多数Mega2560芯片打印机主板,本文使用香橙派ZERO2作为上位机) 上位机:ZERO2 下位机:打印机主板 下载镜像系统 首先,去Armbian官网下载Buster系统镜像:Armbian ...

  6. 计算机基础-工控机、上位机、下位机、stm32、单片机

    工控机 定义:(Industrial Personal Computer,IPC)即工业控制计算机,主要用于工业生产上. 性能:采用全钢机箱,抗震性能好,抗电磁干扰,抗冲击. 结构:包括CPU.io外 ...

  7. 什么是上位机、下位机

    上位机 上位机是指可以直接发出操控命令的计算机, 一般是PC/host computer/master computer/upper computer, 屏幕上显示各种信号变化(液压,水位,温度等). ...

  8. 关于Qt上位机与下位机stm32数据传输的解析问题(一)

    在制作上位机中,我们常常要把单片机上的数据,比如曲线图.电机速度.信号频率幅值等显示在上位机软件中,那么就需要下位机方将数据不断传给上位机以在Qt的QLCDNumber或者Qchart不断刷新,这是上 ...

  9. 上位机与下位机都是个啥?

    上位机与下位机都是个啥? 基础概念 上位机是指人可以直接发出操控命令的计算机,一般是PC,屏幕上显示各种信号变化(液压,水位,温度等). 下位机是直接控制设备获取设备状况的计算机,一般是PLC/单片机 ...

最新文章

  1. 大厂 CEO 一年薪酬拿多少?
  2. vue+django2.0.2-rest-framework 生鲜项目(八)
  3. python xgboost参数_如何对XGBoost模型进行参数调优
  4. 保姆级的HTML零基础教程少见吧?这是第一节(1)
  5. java弱口令生成1001无标题,教你批量生成自动发卡平台需要的卡密数据
  6. Python中[index for index, value in enumerate(a) if value > 3]
  7. MAC使用find命令的正确办法
  8. cmd命令查看本机网外地址
  9. MATLAB求解夏普利值
  10. nodejs+vue+elementui企业考勤管理系统
  11. 16本版式设计书籍推荐(附PDF链接)设计从业人员必备
  12. 十大热门经典历史小说,大有希望获得第四届橙瓜网络文学奖
  13. 《金狐系统维护盘》五周年纪念版【简洁易用,强大实用】
  14. 徐波 博士 计算机,中文问答系统中问题分类技术研究
  15. 学术与应用的碰撞,精准医疗和生物医药大数据的盛宴
  16. Stability Analysis Of Voltage-Feedback Op Amps,Including Compensation Technique
  17. win10任务栏的音量图标变透明且无法点击
  18. 如何在万米高空畅享5G?
  19. maven依赖c3p0_Maven项目中c3p0连接数据库及实例
  20. 浅谈跟美女上床和次贷危机

热门文章

  1. 微信小程序提示:https://www.xxxxx.com 不在以下 request 合法域名列表中,请参考文档....
  2. PLY的LALR语法详细解释
  3. 第七十四章 Caché 函数大全 $WREVERSE 函数
  4. 第七十二章 Caché 函数大全 $WISWIDE 函数
  5. MySQL limit后面加变量
  6. 为什么myeclipse9.0解析类时会多一个类名呢!解决方案
  7. 数据库与关键字同名解决方法
  8. 关于Inter CPU的一些认识
  9. linux使用grep条件搜索大文件的行数等操作
  10. Loki 收集Nginx日志以 grafana 可视化展示