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

李群林

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

 

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

关键字:Java,JBuilder,KeilC,Java Communications API,串口通讯,RS232,单片机

 

 

1 硬件部分(KeilC)



java 单片机 java编写单片机程序_语言

图1 硬件电路图

串口通讯硬件部分电路,收发器件采用max232,5V供电。J31接一单片机如AT 89C 52,单片机的串口与max232的10和11脚相连。

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

本文的实验环境是AT 89C 52,单片机的内部程序是用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 0x 
    
      0F 
           //数据包长度 
   
 
uchar      CR          = 0x0D; 
    
uchar      LF          = 0x 
    
      0A 
    ; 
    
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 At 
    
      89C 
    51,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; 
    
//*****************************************************// 
    
// 串口控制器工作于方式1,8位,频率可变。接收允许      // 
    
// 串口波特率系数SMOD = 1                                              // 
    
// 定时器1工作于方式1,8位自动装载。定时器0方式1,16位 // 
    
// 
    
      11.059M 
    晶振,波特率 = 9600,TH1 = 0x0FA;            // 
    
//                      19200           0x0FD             // 
    
//                      57600              0x0FF                // 
    
// 
    
      3.6864M 
    晶振                   9600            0x0FE             // 
    
//                                        19200            0x0FF                    // 
    
// # 
    
      3.6864M 
    晶振工作于方式2                                                 // 
    
// #在SMOD = 1时,波特率                      // 
    
// 开中断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[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两次即可。

现在环境已经配置好了,我们要开始正式工作了。

java 单片机 java编写单片机程序_jbuilder_02

图2 Configure Library

java 单片机 java编写单片机程序_jbuilder_03

图2 New Librariy Wizard

java 单片机 java编写单片机程序_java_04

图3 Select comm. Library

 

3 程序开发JBuilderX

 

1.  与51单片机交互信息,数据库存取(Data)

为了保证数据传输的顺利进行,单片机与PC之间通讯要建立一个协议,在本实例中,采用如下协议:

程序打开串口后,程序发送“启始”符(0x01)表示通讯开始。

通讯开始后,程序就开始发送和接收数据包,数据包以“结束”符(0x00,0x0D, 0x 0A )表示结尾。由于单片机受控于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 != 0x     
      0A 
    ) { 
    
            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


java 单片机 java编写单片机程序_语言_05

图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 Proctor,www.borland.com , 2004 年 3 月 14 日 ,http://bdn.borland.com/article/0,1410,31915,00.html

2.  The Java Communications API Documentations,www.sun.com,2004年11月

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

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

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