摘要:由于Java语言的诸多优点,Java得到了广泛的应用,如今利用Java开发串口通讯已相当成熟,实现简单,可移植性强。文章详细介绍了如何配置开发环境以及使用Java串口API函数编写PC机程序。本程序比使用C++语言编写的串口通讯程序更容易理解,且移植性非常强,视图与控制分开,便于维护和升级。
关键字:Java,JBuilder,KeilC,Java Communications API,串口通讯,RS232,单片机
1 硬件部分(KeilC)
图1 硬件电路图
串口通讯硬件部分电路,收发器件采用max232,5V供电。J31接一单片机如ATC52,单片机的串口与max232的10和11脚相连。
max232与微机通过9针接头相连。
本文的实验环境是ATC52,单片机的内部程序是用KeilC语言编写的,程序功能非常简单,检测到开始信号后从串口读数据,然后把读入的数据发送给串口,遇到结束符停止。C语言代码如下供大家参考。在uv3中含有两个文件comm.h和comm.c,代码分别为:
/********************************************************/
/* comm.h */
/* serial port define, only use in comm project */
/********************************************************/
#define uchar unsigned char
#define uint unsigned int
#define length 0x0F //数据包长度
uchar CR = 0x0D;
uchar LF = 0x0A;
uchar ESC = 0x1B;
uchar SYNC = 0x01; //数据包启始符
uchar PID = 0x00; //数据包标示字
uchar ADDR; //串口接收外部命令(片内地址)
uchar DATA; //串口返回片内数据
uchar ENDP = 0x00; //数据包结束符
uchar ACK = 0x06; //串口确认
uchar ERROR = 0x18; //串口错误
uchar wrong[] = "Bad command";
/*END*/
/*******************************************************/
/*comm..c */
/* Write time is 2005.4.15,By Gooseli */
/* Copyright is changsha HUNU unversity gooseli.com */
/* Cpu is AtC51,Fclk is 11.059MHz */
/* Compiler is keilC51 compiler */
/*******************************************************/
#include #include #include #include void commInit(){ //**************************// // 8051串口初始化 // //**************************// SCON = 0x52; PCON = 0x80; TMOD = 0x21; TH1 = 0x0FA; TL1 = 0x0FA; TCON = 0x40; //*****************************************************// } uchar flag; uchar readln(); void println( uchar *str ); main(){ commInit(); //初始化串口 while(1){ flag = readln(); } } uchar readln(){ uchar a; uchar str[length]; int i; scanf("%c",&a); //寻找起始符,回车则开始 if( a==SYNC || a==LF ){ while(1){ printf("\\n>>"); //printf(">>"); scanf("%c",&a); if( a==ENDP || a == ESC ){ //如果ESC则对话结束 break; } for( i="0"; i str[i] = a; scanf("%c",&a); } str[i] = ENDP; //为数据包添加结束符,“\\0” printf("%s",str); //输出输入值 /*To do something by yourself*/ } return ACK; } printf("\\n%s\\n>>",wrong); return ERROR; } /*END*/ 2 配置运行环境(JDK) Java通讯库函数Java Communications API,Java开发工具JBuilderX。 此Java(TM) Communications API Specification 2.0(Windows Platform)是Sun公司为Windows平台提供的一个串口API扩展,可以到 http://java.sun.com/products/javacomm/ 下载。Sun公司还提供了其他操作系统下的API下载,移植性是Java先天的优势,如果需要在其他操作系统运行程序,不需要改动程序本身,只要在操作系统下植入相应的API库函数即可实现。 JBuilder是Borland公司出品的一款功能强大的可视化Java集成开发工具,可以快速开发包括复杂企业级应用系统的各种Java程序,本文的程序都用其实现。当然我们以可使用其他优秀的开发工具,例如开放源代码的Eclipse,功能强大,插件丰富。 在下载Java Communications API压缩文件里找到三个文件:comm.jar,win32comm.dll,javax.comm.properties,这三个文件是把API安装到Windows环境中的重要文件,我们把他们放在我们的JDK里面。 把comm..jar复制到%JAVA_HOME%\\jre\\lib\\ext,javax.comm.properties复制到%JAVA_HOME%\\jre\\lib,win32comm.dll复制到%JAVA_HOME%\\bin即可。这样我们的程序就可以在Windows环境中运行了,Java Communications API压缩文件中自带有例子,我们可以尝试一下。 接下来我们要把Java Communications API安装到JBuilder里面,如果JBuilder不是使用的外部的JDK,照上面的的步骤再做一次。假如我们外部的JDK和JBuilder的JDK是同一的JDK,我们就直接跳到下一步。 1)打开JBuilder,为我们的任务建立一个工程,给它起个有意义的名字,不多讲了。JBuilder会自动生成两个文件,如,工程名为comm,就会生成文件commApplication和commFrame。 2)选择Tools菜单,选择Configure Libraries…,如图1所示。 3)点击New按钮,为JBuilder增加一个函数库。如图2,点击OK即可。 4)下一步为你的工程增加这个库函数,以便你在工程里调用它们。选择Project菜单中的Project Properities选项,左侧选中Paths,右侧选中Required Libraries,单击Add,出现一个小的对话框,选择我们刚才增加的comm函数库,如图3,点击OK两次即可。 现在环境已经配置好了,我们要开始正式工作了 图2 Configure Library 图2 New Librariy Wizard 图3 Select comm. Library 3 程序开发(JBuilderX) 1. 与51单片机交互信息,数据库存取(Data) 为了保证数据传输的顺利进行,单片机与PC之间通讯要建立一个协议,在本实例中,采用如下协议: 程序打开串口后,程序发送“启始”符(0x01)表示通讯开始。 通讯开始后,程序就开始发送和接收数据包,数据包以“结束”符(0x00,0x0D, 0x0A)表示结尾。由于单片机受控于PC机,所以单片机一般不主动发送数据,只有在PC机发送一个“命令”,它才会发送一个“回应”。 如果程序“停止”符(0x00),则通讯结束。 2. 界面设计(View) 这部分设计主程序的视图,即使用者看到的部分,包括按钮,下拉菜单,文字编辑框等。 为了程序的可读性,我们将所有的视图从主程序中分离出来,作成Bean的形式,然后在主程序中调用它们。Java提供了五种布局管理形式FlowLayout,,GridLayout, GridBagLayout, BorderLayout,,CardLayout。灵活的运用这些布局,可以达到各种各样的效果,其中GridBagLayout功能强大,使用灵活,本文主要采用这种布局。 3. 主程序设计(Control) 这部分设计程序的实现方法,逻辑步骤。 1)首先定义串口,输入输出流等,如下所示: package comm; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.comm.*;//包含comm类包,才能使用其API函数 import java.io.*; import java.util.*; public class CommFrame extends JFrame implements Runnable, SerialPortEventListener { JPanel contentPane; //定义一个JPanel,将视图Bean包含进来 BorderLayout borderLayout1 = new BorderLayout(); IOBean ioBean = new IOBean();//右侧视图Bean类事例化 ControlBean controlBean = new ControlBean();//左侧视图Bean类事例化 //Communination define static CommPortIdentifier portName;//定义串口 int portId; static Enumeration portList; InputStream inputStream;//定义输入流 OutputStream outputStream;//定义输出流 SerialPort serialPort; Thread readThread;//定义一个线程,程序全双工通讯 static String TimeStamp; //Construct the frame public CommFrame() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit();//程序初始化 commInit();//串口初始化 }catch(Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception {……} public void commInit() {……} public void commClose() {……} public void commWrite() {……} public void CommRead() {……} public void run() {……} public void serialEvent(SerialPortEvent event) {…….}//代码如下 //Overridden so we can exit when window is closed protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { commClose(); System.exit(0); } } } 2)串口初始化,首先监测串口是否被占用,如果没有被占用则打开串口。打开输入输出流以便下面的程序从串口读写数据,定义串口的波特率,位数,停止位,奇偶校验,在使用过程中可以改变这些内容以适应不同的需求。 public void commInit() { //Communination ports owned or not portId = 1; try{ portList = CommPortIdentifier.getPortIdentifiers(); while (portList.hasMoreElements()) { portName = (CommPortIdentifier) portList.nextElement(); if (portName.getPortType() == CommPortIdentifier.PORT_SERIAL) { if (portName.isCurrentlyOwned()) {//串口是否被占用 ioBean.Receiver.append("\\nCOM"+portId+"Ownedby"+ portName.getCurrentOwner()); TimeStamp = new java.util.Date().toString(); portId ++; }else if (portName.getName().equals("COM" + portId)) { break; } } } //Communination ports init try { serialPort = (SerialPort) portName.open("Gooseli_MCU_Control_App", 2000);//打开串口 controlBean.CommPortID.setText("COM" + portId); controlBean.OnOff.setText("ON");//开关按钮置开状态 controlBean.OnOff.setSelected(true); TimeStamp = new java.util.Date().toString(); System.out.println(TimeStamp + ": msg2 - SerialPort COM" + portId + " is opend"); ioBean.Receiver.append("\\nCOM" + portId + " is opend");//显示区域显示串口被打开 } catch (PortInUseException e) { System.out.println(e); ioBean.Receiver.append("\\nCOM" + portId + " " + e); } try { inputStream = serialPort.getInputStream();//打开输入流 } catch (IOException e) {} try { outputStream = serialPort.getOutputStream(); outputStream.write((byte)0x01);//向串口写入启始符开始传送数据包 ioBean.Receiver.setText("\\nCOM" + portId + ">>" + "Start"); controlBean.begin.setSelected(true); } catch (IOException e) {} try { serialPort.setSerialPortParams(9600,//波特率 SerialPort.DATABITS_8,//数据位 SerialPort.STOPBITS_1,//停止位 SerialPort.PARITY_NONE);//校验位 } catch (UnsupportedCommOperationException e) {} CommRead();//程序开始从串口读数据 }catch(Exception e) {} } public void commClose() { try { inputStream.close(); outputStream.close(); serialPort.close(); System.out.println(TimeStamp + ": msg2 - SerialPort COM" + portId + " is closing"); ioBean.Receiver.append("\\nCOM" + portId + " is closing"); }catch (Exception e) { System.out.println(e); } } 3)程序初始化,这里定义了一些事件,以便控制程序的运行。例如开始按钮的事件定义如下: private void jbInit() throws Exception { contentPane = (JPanel) this.getContentPane(); contentPane.setLayout(borderLayout1); this.setSize(new Dimension(400, 300)); this.setTitle("Serial Ports Communication Current"); contentPane.add(ioBean,BorderLayout.CENTER); contentPane.add(controlBean, BorderLayout.WEST); controlBean.OnOff.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { JToggleButton toggle = (JToggleButton) ae.getSource(); if (toggle.isSelected()) { controlBean.OnOff.setText("ON"); commInit(); } else { controlBean.OnOff.setText("OFF"); commClose(); } } } ); controlBean.begin.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { JToggleButton toggle = (JToggleButton) ae.getSource(); if (toggle.isSelected()) {//如果按钮被按下,则开始 controlBean.begin.setText("Start"); try { outputStream.write((byte)0x01);//发送起始符 ioBean.Receiver.setText("\\nCOM" + portId + " " + "Start"); } catch (IOException e) {} } else {//如果按纽复位 controlBean.begin.setText("Stop"); try { outputStream.write((byte)0x00);//发送结束符 ioBean.Receiver.append("\\nCOM" + portId + " " + "Stop"); } catch (IOException e) {} } } } ); ioBean.jButton2.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { commWrite(); } } ); } 4)读写串口,使用多线程,实现全双工通讯。主函数CommFrame实现了Runnable接口,在程序中只需要重写run函数即可实现多线程。 public void commWrite() { String outString = ioBean.jTextField1.getText(); if (outString.equals("clear")) { ioBean.Receiver.setText("\\nCOM" + portId +" Receive:"); } ioBean.jTextField1.setText("Gooseli:"); try { //outputStream.write((byte)0x01); outputStream.write(outString.getBytes()); outputStream.write((byte)0x0D); //outputStream.write((byte)0x00); ioBean.Receiver.setText("\\nCOM" + portId + ">>" + outString); } catch (IOException e) {} } public void CommRead() { try { serialPort.addEventListener(this); } catch (TooManyListenersException e) {} serialPort.notifyOnDataAvailable(true); readThread = new Thread(this); readThread.start(); } public void run() { try { Thread.sleep(20000); } catch (InterruptedException e) {} } 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: StringBuffer readBuffer = new StringBuffer(); String scannedInput = null; int c; try { while ((c = inputStream.read()) != 0x00 && c != 0x0D && c != 0x0A) { readBuffer.append( (char) c);//数据包以回车或者换行表示结束 } scannedInput = readBuffer.toString(); ioBean.Receiver.append("\\n" + scannedInput); TimeStamp = new java.util.Date().toString(); System.out.println(TimeStamp + ": scanned input received:" + scannedInput); inputStream.close(); } catch (IOException e) {} break; } } 4. 测试程序 程序运行后如图四所示。 为了方便程序运行,我们作一个批处理文件,和程序生成的jar文件放在一个目录里,这样就可以很方便的在含有Java虚拟机的系统里运行我们的程序了。可以将JBuilder运行窗口(Messages)的信息直接拷贝过来,当然也可以自己建立。 我的批处理文件如下所示: @echo off @REM 设置路径包括Java虚拟机和程序jar文件路径 set path=%JAVA_HOME%\\bin set classpath=%cd%\\comm.jar @REM 运行程序,注意在主程序前增加包名,否则找不到主函数 echo Now initializing the program,please wait a minite... java comm.CommApplication 图4 Test my programme