上位机下位机串口通信设计详解

串行接口是一种可以将接受来自cpu的并行数据字符转换为连续的串行数据流发送出去,同时可将接受的串行数据流转换为并行的数据字符供给cpu的器件。一般完成这种功能的电路,我们称为串行接口电路。
串口通信结构
串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。
串口是计算机上一种非常通用的设备通信协议。大多数计算机(不包括笔记本电脑)包含两个基于rs-232的串口。串口同时也是仪器仪表设备通用的通信协议;很多gpib兼容的设备也带有rs-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。
rs-232(ansi/eia-232标准)是ibm-pc及其兼容机上的串行连接标准。可用于许多用途,比如连接鼠标、打印机或者modem,同时也可以接工业仪器仪表。用于驱动和连线的改进,实际应用中rs-232的传输长度或者速度常常超过标准的值。rs-232只限于pc串口和设备间点对点的通信。rs-232串口通信最远距离是50英尺。
串口通信
串口通信是在工程应用中很常见。在上位机与下位机通讯过程中常通过有线的串口进行通信,在低速传输模式下串口通信得到广泛使用。在说个之前先来简单解释一下上位机与下位机的概念。
上位机与下位机设计
通常上位机指的是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)javajdk1.8.0_25in
c:program files (x86)javajdk1.8.0_25jrein12
怎么讲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();
//添加并返回arraylist
while(portlist.hasmoreelements())
{
string portname=portlist.nextelement().getname();
portnamelist.add(portname);
}
return portnamelist;
}123456789101112131415161718
以下是测试类的测试实例:
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));
//测试串口配置的相关方法
}
} 123456789101112131415
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和超时时间 2000ms
commport commport=portidentifier.open(portname,1000);
//进一步判断comm端口是否是串口 instanceof
if(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);
//串口配制完成 log
system.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;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
以上代码就是返回一个对象,同时也返回了对象属性,因为对象在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;
} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
通过以上关于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;
}1234567891011121314
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;
}123456789101112131415161718192021222324252627282930313233343536
以上数据可以使用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;
}1234567891011121314151617181920212223242526272829303132333435
下面给出了将时间数据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;
}12345678910111213141516171819202122232425262728293031323334353637
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);123456789101112131415
以下是测试结果:
当没有串口设备接入计算机时控制台打印一条信息:
没有找到可用的串口端口,请check设备!
12
当rs485设备接入计算机时,控制台打印消息如下:
通过以上几个步骤基本实现了上位机与下位机串口通信的功能,接下来还可以对程序进行改进:
1)添加界面,可以类比串口助手界面根据自身需要设计独具风格的人机交互界面。
2) 在程序中添加线程,在以上程序中对于系统时间的获取可以通过线程的方式进行获取,这样上位机就可以一直往下位机发送数据包,而不是仅仅发一次。
3)对于上位机数据接收,除了以上最基本的接收功能外,还可以使用jdbc与mysql等数据进行存储,并绘画数据曲线实现特性分析。


直流接触器连接头不牢固该如何去处理
最后一代iPhone将成指纹识别绝响?
车灯设计与仿真测试解决方案
LED汽车灯驱动芯片降压恒流IC内置mos管AP5193
TH6501型高精度可编程线性直流电源简介
上位机下位机串口通信设计详解
2024年中国在汽车传感器市场有望获得最大的市场份额
linuxvi无法打开并写入文件
发光二极管的工作电流如何计算
飞思卡尔防篡改特性MCU强劲智能电表解决方案
华润微电子:目前对2021年全年行情的看法?
ATF的启动过程介绍
芯片公司marvell灾难性裁员千人
比亚迪推出IGBT4.0引领车规级功率半导体发展
剖析平板电脑生存现状 产业聚首共谋发展大计
视频矩阵切换原理及技术详解
关于波音737MAX事故机型探讨自动防失速软件MCAS系统研究安全性
!!销售/收购!SFL-T罗德与施瓦茨SFL-T谭艳飞/李S
简述I2C总线协议
苹果笔记本运行变慢怎么办?教你简单快速重置法