原创 基于Java语言的51单片机串口通讯PC机程序

2008-10-21 14:49 6747 6 9 分类: MCU/ 嵌入式

基于Java语言的51单片机串口通讯PC机程序<?XML:NAMESPACE PREFIX = O />


李群林


电气与信息工程学院 测试计量技术与仪器,湖南大学,湖南 长沙,410082


 


摘要:由于Java语言的诸多优点,Java得到了广泛的应用,如今利用Java开发串口通讯已相当成熟,实现简单,可移植性强。文章详细介绍了如何配置开发环境以及使用Java串口API函数编写PC机程序。本程序比使用C++语言编写的串口通讯程序更容易理解,且移植性非常强,视图与控制分开,便于维护和升级。


关键字:JavaJBuilderKeilCJava Communications API串口通讯,RS232,单片机


 


 


1 硬件部分(KeilC


点击看大图


<?XML:NAMESPACE PREFIX = V />


1 硬件电路图


串口通讯硬件部分电路,收发器件采用max2325V供电。J31接一单片机如AT<?XML:NAMESPACE PREFIX = ST1 />89C52,单片机的串口与max2321011脚相连。


max232与微机通过9针接头相连。


本文的实验环境是AT89C52,单片机的内部程序是用KeilC语言编写的,程序功能非常简单,检测到开始信号后从串口读数据,然后把读入的数据发送给串口,遇到结束符停止。C语言代码如下供大家参考。在uv3中含有两个文件comm.hcomm.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 At89C51,Fclk is 11.059MHz                                 */


/* Compiler is keilC51 compiler                                           */


/*******************************************************/


 


#include<stdio.h>


#include<string.h>


#include<reg51.h>


#include<comm.h>


 


 


void commInit(){


//**************************//


// 8051串口初始化                     //


//**************************//


       SCON    = 0x52;


       PCON    = 0x80;


 


       TMOD   = 0x21;


       TH1      = 0x0FA;


       TL1       = 0x0FA;


       TCON    = 0x40;


//*****************************************************//


// 串口控制器工作于方式18位,频率可变。接收允许      //


// 串口波特率系数SMOD = 1                                              //


// 定时器1工作于方式18位自动装载。定时器0方式116 //


// 11.059M晶振,波特率 = 9600TH1 = 0x0FA;            //


//                      19200           0x0FD             //


//                      57600              0x0FF                //


// 3.6864M晶振                   9600            0x0FE             //


//                                        19200            0x0FF                    //


// #3.6864M晶振工作于方式2                                                 //


// #SMOD = 1时,波特率 = 115200                      //


// 开中断TR1 = 1                                       //


//*****************************************************//


}


 


 


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<length-1 && a!=CR && a!=LF; i++ ){


                                                                      //读入数据包,如果溢出或者回车则结束


                            str = a;


                            scanf("%c",&a);


                     }


                     str = 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 APIJava开发工具JBuilderX


Java(TM) Communications API Specification 2.0(Windows Platform)Sun公司为Windows平台提供的一个串口API扩展,可以到 http://java.sun.com/products/javacomm/ 下载。Sun公司还提供了其他操作系统下的API下载,移植性是Java先天的优势,如果需要在其他操作系统运行程序,不需要改动程序本身,只要在操作系统下植入相应的API库函数即可实现。


JBuilderBorland公司出品的一款功能强大的可视化Java集成开发工具,可以快速开发包括复杂企业级应用系统的各种Java程序,本文的程序都用其实现。当然我们以可使用其他优秀的开发工具,例如开放源代码的Eclipse,功能强大,插件丰富。


在下载Java Communications API压缩文件里找到三个文件:comm.jarwin32comm.dlljavax.comm.properties,这三个文件是把API安装到Windows环境中的重要文件,我们把他们放在我们的JDK里面。


comm..jar复制到%JAVA_HOME%\jre\lib\extjavax.comm.properties复制到%JAVA_HOME%\jre\libwin32comm.dll复制到%JAVA_HOME%\bin即可。这样我们的程序就可以在Windows环境中运行了,Java Communications API压缩文件中自带有例子,我们可以尝试一下。


接下来我们要把Java Communications API安装到JBuilder里面,如果JBuilder不是使用的外部的JDK,照上面的的步骤再做一次。假如我们外部的JDKJBuilderJDK是同一的JDK,我们就直接跳到下一步。


 


1)打开JBuilder,为我们的任务建立一个工程,给它起个有意义的名字,不多讲了。JBuilder会自动生成两个文件,如,工程名为comm,就会生成文件commApplicationcommFrame


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)表示通讯开始。


通讯开始后,程序就开始发送和接收数据包,数据包以结束符(0x000x0D, 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


 


4 展望


Java作为一个逐渐成熟的程序设计语言,在网络和嵌入式系统越来越多地得到广泛的应用,但是微机底层程序开发发展缓慢。本文应用Java作为串口上位机程序开发语言,是考虑到Java语言的各种优越性,主要是其平台无关性,强大的可移植性,这一点在不久的将来会受到更多的重视。


本文代码可免费使用,如果需要本文程序的源代码,请于本作者联系,gooseli@163.com


 


参考资料:


1.  A How-To on Accessing serial ports in the Windows environment using the Java Communications API By Rick Proctorwww.borland.com 2004314http://bdn.borland.com/article/0,1410,31915,00.html


2.  The Java Communications API Documentationswww.sun.com200411


3.   JBuilder 9集成开发实例解析,张洪斌编,机械工业出版社,20041


4.  精通JBuilder 9,飞思科技产品研发中心编,电子工业出版社,20038


5.  单片微机原理及应用,丁元杰、赵秀菊、陈瀛清编,机械工业出版社,19998

文章评论3条评论)

登录后参与讨论

billzhu_345737527 2014-12-28 13:45

学习了

lixiao19882006_504830100 2012-7-26 09:37

界面设计中IOBean和ControlBean是不是自定义的类啊,我没查到的

zousheng_2008_642315269 2011-7-29 12:01

正学习,谢谢
相关推荐阅读
abcdezh_877078903 2009-07-24 09:29
数字地、模拟地、信号地、交流地、直流地、屏蔽地、浮地
转自:http://ouravr.com/bbs/bbs_content.jsp?bbs_sn=3351198&bbs_page_no=1&bbs_id=9999除了正确进行接地设计、...
abcdezh_877078903 2009-07-20 15:37
转载的C51的编程规范
转贴地址http://blog.sina.com.cn/s/blog_5e112fd50100cifv.html收集的C51的编程规范 编程首要是要考虑程序的可行性,然后是可读性、可移植性、健壮性以及...
abcdezh_877078903 2009-07-20 15:31
PS/2鼠标
Microsoft标准串口鼠标(两键)采用7个数据位,1个停止位, 无校验位的方式以1200BPS的速率传送数据(有部分为2400bps) 数据格式采用Mirosoft规定的3字节格式如下: Byte...
abcdezh_877078903 2009-07-02 17:14
手把手教你写程序
非常实用,特转载共大家分享!谢谢原作者!http://bbs.21ic.com/club/bbs/bbsview.asp?boardid=11&t=3355604&tp=%CA%D6%...
abcdezh_877078903 2009-06-25 15:46
9针VGA显示接口引脚定义
VGA 是 Video Graphics Adapter(Array) 的缩写,信号类型为模拟类型,显示卡端的接口为 9 针母插座:显示器连线端的接口为 9 针公插头: 引脚定义PinNameDirD...
abcdezh_877078903 2009-06-25 14:55
PS/2鼠标接口的设计与实现(转)
http://www.dzkf.cn/html/zonghejishu/2007/0911/2599.html当前嵌入式系统技术已得到了广泛应用,但传统嵌入式系统的人机接口多采用小键盘操作的文本菜单方...
我要评论
3
6
1
2
3
4
5
6
7
8
9
0
关闭 热点推荐上一条 /4 下一条