PC-CONTROLLED
SCANNING TUNNELING MICROSCOPE

ECE 476 SPRING 2002 FINAL PROJECT

 
C CODE (MCU)
  1. stm.c

JAVA CODE (PC)
  1. CmdInterp.java
  2. Draw.java
  3. Message.java
  4. SerialTalk.java
  5. TokenReader.java

 
C CODE
 
/* ECE 476 Spring 2002 Final Project
 * Scanning Tunneling Microscope MCU Software
 * Sunarni Maulan
 * Engin Ipek
 * Cornell University
 */

#include <Mega163.h>
#include <stdio.h>
#include <Math.h>        
#include <delay.h>
     
#define begin {
#define end }

void init(void);
void cal(void);//calibration & scanning function	
void scan(void);         

bit r_ready;  //signal ready, receive buffer is full
unsigned char r_buffer[2];  //receive buffer
unsigned char r_char, r_index; //character received and the index
unsigned char z, x, y, r, a, vbias;   //specification received from PC                         
unsigned char stepy, stepx, countx ;  //current steps
unsigned char vx, vy, vinc;           //digital voltage 
unsigned char i;                              
                                      //data read from ADC
int Ain, avg; 

float bias;       
float v1, v2, c, h;                   //calibration variables
float vz, vstep;
float Vref, Aref;                     //voltage reference of DAC and ADC
      

//**********************************************************
//Entry point and task scheduler loop
void main(void)                                                                                             
begin    

    //Initialize
    init();
		stepmax = 60*Vref;    //min step
 		stepmin = stepmax/256;     
 		
  	//Usual endless loop
  	while(1)
  	begin         
  	  //Interpreting the command received from PC        
  		if(r_ready)
  		begin
  			r_ready=0;
  			if (r_buffer[0] == 'z') z = r_buffer[1];
  			if (r_buffer[0] == 'x') x = r_buffer[1];    
  			if (r_buffer[0] == 'y') y = r_buffer[1];    
  			if (r_buffer[0] == 'r') r = r_buffer[1];   
  			if (r_buffer[0] == 'a') a = r_buffer[1]; 
  			if (r_buffer[0] == 'b') vbias = r_buffer[1];        
  			if (r_buffer[0] == 'c') cal();
  			if (r_buffer[0] == 's') scan();	  
  		end	   		  
  	end    
  	
end  
 
//**********************************************************  
//UART character-ready ISR
interrupt [UART_RXC] void uart_rec(void)
begin          
	r_char = UDR; //get the commancd        
	r_buffer[r_index] = r_char; //put in buffer
	if (r_index == 0) r_index = 1;
	else
	begin          
		r_index = 0;//reinitialize index 
		r_ready = 1;//signal ready
	end  
end	 

//**********************************************************  
//UART_TXC ISR
interrupt [UART_TXC] void uart_tra(void)
begin                      
 	vx = vx + vinc; //increment vx              
  PORTB = vx;     //send vx to DAC
  UCSRB.6 = 0;    //disable TXC interrupt
end     

//**********************************************************
//ADC ISR     
interrupt [ADC_INT] void adc_done(void)
begin      
   	Ain = ADCH;    //Read input from ADC 
end 

//**********************************************************      
//Calibration
void cal(void) 
begin     
     
  //set vbias      
  bias = (float)vbias;  
  bias = (bias/Vref)*256;       
  vbias = (unsigned char) bias;
  PORTB = vbias;          
	
  //Take two measurements at different point
  //to find the value of constant c.
  
  //The sleep statment lowers digital noise   
  //and starts the A/D conversion 
  //Take 1st measurement        	
  #asm
    sleep
  #endasm  
  
  putchar('m');  
  putchar(Ain);            
  delay_us(2000); 
  
  v1 = (float)avg ;  
  v1 = (v1/256)*Aref ; //(fraction of full scale)*Aref 
  	
  //increase z by h
  vz =  h/60; //1V = 60nm
  vz = (vz/Vref) * 256;    
  //PORTC = (unsigned char)vz;  
 
  //Take 2nd measurement
  #asm
  	sleep
  #endasm          
  
  putchar('n');        
  putchar(avg);    
  
  v2 = (float)avg ;  
  v2 = (v2/256)*Aref ; //(fraction of full scale)*Aref       

  c = log(v1/v2);
  c = c/h;   //h or z??
  
  //Back to original position
  PORTC = 0x00;
  
  //calibration done
  //send done signal to PC              
  putchar('c'); 
  putchar('d');  
    
end   

//**********************************************************          
//Scan
void scan(void) 
begin   
  
  //set Vz
  diff = z - d; 
  vz =  diff/60;  //1V = 300nm
  vz= (vz/Vref) * 256;    
  PORTC = (unsigned char)vz; //float --> char  ??*/               
  
  //count steps -- 1 step = r nm
  stepx = x/r; 
  stepy = y/r;  
  countx = stepx;
  	 
  vinc = r; 
  PORTB = 0;
  PORTC = 0; 
			
  //start scanning       
  while (stepy-- > 0)  
  begin           
  		while (stepx-- > 0)  
  		begin                
  				//Averaging is off
  				if (a == 0)    
  				begin            
  		   		  #asm
    					sleep
       		 	  #endasm          
			      UCSRB.6 = 1; //enable TXC interrupt
			      putchar(Ain);  //send data to PC
			      delay_us(2000);   
			    end 
			    
			    //Averaging is on
			    else   
			    begin  
			    //Take 'a' measurement and compute the average
			    for (i = 0; i < a; i++)
  			  	begin     
  		   			delay_us(2000); 
  		   			#asm
    						sleep
       		 		#endasm          
       			    avg = avg + Ain;  
           		end       
          	
       			avg = avg/a;   
       			UCSRB.6 = 1;    //enable TXC interrupt
       			putchar(avg);    //send data to PC
          end	      
  		end
  		   
  		stepx = countx;       //recover stepx
  		vx = 0;					//reset vx
  		PORTB = vx;
		vy = vy + vinc; //increment vy               
  		PORTC = vy;    
	end 
	vy = 0;          
	
	//return to initial position
	PORTC = 0;      
 	PORTB = 0;	

end

//**********************************************************   
//Initialize
 void init(void)
 begin
 	DDRA = 0x00; //input port : ADC 
 	DDRB = 0xff; //output port : Vbias, Vx 
 	DDRC = 0xff; //output port : Vz, Vy 
 	DDRD = 0x00; //input UART 
    
    //Initialize output PORT
 	PORTC = 0;      
 	PORTB = 0;
 	
 	//Set UART
 	UCSRB = 0x10 + 0x08 + 0x80;//enable RXC interrupt
 	UBRR = 51;   //baud rate for 8MHz
   
        
   	//init the A to D converter 
  	//channel zero/ left adj /int Aref
   ADMUX = 0b11100000;   
   //enable ADC and set prescaler to 1/64*8MHz=125,000
   //and set int enable
   ADCSR = 0x80 + 0x06 + 0x08; 
   MCUCR = 0b01010000; //enable sleep and choose ADC mode                             
 
      
	//Initialize variables    
    r_index  = 0;  
 	r_ready  = 0; 
 	h = 0.03;
	dir = 0; 
	vx = 0;
	vy = 0; 
    vinc = 1;

 	//check Vref & Aref, then change accordingly   
 	Aref = 5.0;           
 	Vref = 5.0;      
 	                
 	#asm
 		sei
 	#endasm
 end
 
JAVA CODE
  i) Interprets command
package stmcontroller;

/**
* Title: STM Controller Software
* Description: Handles communication to MCU and receives data
* Copyright: Copyright (c) 2002
* Company: Cornell University
*  @author Engin Ipek
*  @version 1.0
**/

import javax.comm.*;
import java.util.*;
import javax.comm.*;
import java.io.*;
//Commad interpretor Class
public class CmdInterp {

//Messages for communicating to the MCU
public static byte[] xscan = {(byte)'x',(byte)0};
public static byte[] yscan = {(byte)'y',(byte)0};
public static byte[] zheight = {(byte)'z',(byte)0};
public static byte[] resolution = {(byte)'r',(byte)0};
public static byte[] sampleBias = {(byte)'b',(byte)0};
public static byte[] startCal = {(byte)'c',(byte)'a'};
public static byte[] startScan= {(byte)'s',(byte)'c'};
public static byte[] numAverages = {(byte)'a',(byte)0};
public static boolean beginCal = false;
public static boolean beginScan = false;
public static String fileName = "";
public static Draw topology;
public static int xrange = 0;
public static int yrange = 0;
public static int stepsize = 0;

  public CmdInterp() {


  }

//**********************************************************
public static void main(String[] args) {
  TokenReader in = new TokenReader(System.in);

  //Create new GUI
  topology = new Draw();
  //Initialize communication to the MCU
  SerialTalk S= new SerialTalk();
  //Read commands
  S.readCmd();


  //Wait until user starts calibration
  while(!beginCal){}
  System.out.println("CAL BEGAN");

  //Transmit user specified parameters
  S.write(xscan);
  S.write(yscan);
  S.write(zheight);
  S.write(resolution);
  S.write(sampleBias);
  S.write(numAverages);
  S.write(startCal);


  //Wait for calibratio to end
  while(!S.calDone){}
  System.out.println("CAL DONE");

  //Wait until userstarts scan
  while(!beginScan){}

  //Start reading data
  S.readData(xrange/stepsize, yrange/stepsize);
  S.write(startScan);
  System.out.println("SCAN BEGAN");
  while(!S.scanDone){}
  System.out.println("SCAN DONE");

  FileOutputStream out; // declare a file output object
  PrintStream p; 	// declare a print stream object

  try{

    // Create a new file output stream
    // connected to "myfile.txt"
    out = new FileOutputStream(fileName);

    // Connect print stream to the output stream
    p = new PrintStream(out);

    //Write the results to the file
    for(int xIndex = 0; xIndex < SerialTalk.lengthX; xIndex++){
      for(int yIndex = 0; yIndex < SerialTalk.lengthY; yIndex++){
        p.print (SerialTalk.dataArray[xIndex][yIndex]+" ");
        if(yIndex ==SerialTalk.lengthY-1) p.println("");
      }
    }

    //Close the file
    p.close();
  }

  catch (Exception e){
    System.err.println ("Error writing to file");
  }

}

}


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

  ii) Makes GUI and real time plotting
package stmcontroller;

/**
* Title: STM Controller Software
* Description: Handles communication to MCU and receives data
* Copyright: Copyright (c) 2002
* Company: Cornell University
* @author Engin Ipek
* @version 1.0
**/

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Draw extends Frame{
public static Panel mk;
public static int numRows = 40;
public static int numCols = 40;

	Panel x;

        Label xScan = new Label("X Range (nm)    :");
	Label yScan = new Label("Y Range (nm)    :");
	Label zHeight = new Label("Tip Height (nm) :");
	Label resolution = new Label("Stepsize (nm)    :");
	Label sampleBias = new Label("Sample Bias (V):");
        Label fileName = new Label("    Output File      :");
        Label numAvg = new Label("Number of averages per data point");

        TextField xScanTxt = new TextField(10);
        TextField yScanTxt = new TextField(10);
        TextField zHeightTxt = new TextField(10);
        TextField resolutionTxt = new TextField(10);
        TextField sampleBiasTxt = new TextField(10);
        TextField fileNameTxt = new TextField(10);
        TextField numAvgTxt = new TextField(10);

        Button StartCal = new Button("Start Calibration");
        Button StartScan = new Button("Start Scan");

        CheckboxGroup cbg = new CheckboxGroup();
        Checkbox avgOn = new Checkbox("Averaging On", cbg, false);
        Checkbox avgOff = new Checkbox("Averaging Off", cbg, true);
        Label[][] GridLabel;
        Panel Grids;

        //make the grid for real-time imaging
        public Panel MakeGrid(int rows, int cols){
		GridLabel = new Label[rows][cols];

		Panel GRIDS = new Panel(new BorderLayout(5, 5));
		Grids = new Panel(new GridLayout(rows, cols, 1, 1));
		Panel Index = new Panel(new GridLayout(1,cols));

		for(int i =0; i < cols; i++){
			String s = i + " ";
			Index.add(new Label(s));
		}

		for (int i=0; i < rows; i++){
			for( int j=0; j < cols; j++){
				GridLabel[i][j] =  new Label();
			}
		}

		for(int i=rows-1; i>=0; i--){
			for (int j=0; j<cols;j++){
				Grids.add(GridLabel[i][j]);
			}
		}

		//GRIDS.add(Index,BorderLayout.NORTH);
		GRIDS.add(Grids,BorderLayout.CENTER);
		return GRIDS;

	}

        //Create the GUI
	public Draw(){
		x=new Panel(new GridLayout(10,1));
                Panel x1 = new Panel();
                Panel x2 = new Panel();
                Panel x3 = new Panel();
                Panel x4 = new Panel();
                Panel x5 = new Panel();
                Panel x6 = new Panel();
                Panel x7 = new Panel();

                Panel x8 = new Panel();
                Panel x9 = new Panel();

                Panel x10 = new Panel();
                Panel x11 = new Panel();

                x9.setLayout(new GridLayout(2, 1));
                x10.setLayout(new GridLayout(2, 1));

		x.setForeground(Color.black);
                x.setBackground(Color.lightGray);
                x1.add(xScan);
                x1.add(xScanTxt);
                x2.add(yScan);
                x2.add(yScanTxt);
                x3.add(zHeight);
                x3.add(zHeightTxt);
		x4.add(resolution);
                x4.add(resolutionTxt);
                x5.add(sampleBias);
                x5.add(sampleBiasTxt);
                x6.add(fileName);
                x6.add(fileNameTxt);
                x7.add(StartCal);
                x7.add(StartScan);

                x9.add(avgOn);
                x9.add(avgOff);
                x10.add(numAvg);
                x10.add(numAvgTxt);
                x11.add(x9);
                x11.add(x10);

                x.add(x1);
                x.add(x2);
                x.add(x3);
                x.add(x4);
                x.add(x5);
                x.add(x6);
                x.add(x11);
                x.add(x7);

                setLayout(new GridLayout(1,2));
                this.setSize(1000,500);
                this.setResizable(false);
                add(x);
                mk = MakeGrid(40,40/*numRows,numCols*/);

                add(/*MakeGrid(40,40)*/mk);

               // GridLabel[1][1].setBackground(Color.blue);
                //repaint();
                addListeners();
		setVisible(true);
	}

	public void addListeners(){

                StartCal.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent evt){

                                CmdInterp.xrange =  Integer.parseInt(xScanTxt.getText());
                                CmdInterp.yrange =  Integer.parseInt(yScanTxt.getText());
                                CmdInterp.xscan[1] = (byte) CmdInterp.xrange;
                                CmdInterp.yscan[1] = (byte) CmdInterp.yrange;
                                CmdInterp.zheight[1] = (byte) Integer.parseInt(zHeightTxt.getText());
                                CmdInterp.stepsize =  Integer.parseInt(resolutionTxt.getText());
                                CmdInterp.resolution[1] = (byte) CmdInterp.stepsize;
                                CmdInterp.sampleBias[1] = (byte) Integer.parseInt(sampleBiasTxt.getText());
                                CmdInterp.fileName = fileNameTxt.getText();

                                if(avgOn.getState())
                                CmdInterp.numAverages[1] = (byte) Integer.parseInt(numAvgTxt.getText());
                                else
                                CmdInterp.numAverages[1] = 0;
                                CmdInterp.beginCal = true;

			}

		});

		StartScan.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent evt){
                            CmdInterp.beginScan = true;

			}

		});

	}

}


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

  iii) Creates message for user
package stmcontroller;

/**
 * Title: STM Controller Software
 * Description: Handles communication to MCU and receives data
 * Copyright: Copyright (c) 2002
 * Company: Cornell University
 * @author Engin Ipek
 * @version 1.0
**/

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class Message extends Frame{

	Panel x;

        Label Message;

        //Send message to user for calibration
	public Message(double voltage){
		x=new Panel();

                Message = new Label("Please adjust Vref to: " + voltage + " and hit StartScan");
		x.setForeground(Color.black);
                x.setBackground(Color.lightGray);
                x.add(Message);



                this.setSize(500,500);
                this.setResizable(false);
		add(x);

		setVisible(false);
	}

}


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

  iv) Receives data from the MCU
package stmcontroller;
import java.io.*;
import java.util.*;
import javax.comm.*;
import java.awt.*;
//Serial Communication Class for Reading/Writing
public class SerialTalk implements SerialPortEventListener, Runnable {

    static CommPortIdentifier portId;
    private static final int DATA = 1;
    private static final int CMD = 2;
    private static int TYPE = 0;
    private static int dataIndexX = 0;
    private static int dataIndexY = 0;
    private static int cmdIndex = 0;
    public static int lengthX = 0;
    public static int lengthY = 0;

    public int k =  0;
    public int c =  0;
    public int measure1 = 0;
    public int measure2 = 0;
    public int measure3 = 0;
    public boolean scanDone = false;
    public boolean calDone = false;

    OutputStream outputStream;
    InputStream inputStream;
    SerialPort serialPort;
    Thread readCmdThread, readDataThread;
    int[] cmdArray = new int[2];
    public static int[][] dataArray;

    public SerialTalk() {

         //get the port identifier for COM1
         try{portId = CommPortIdentifier.getPortIdentifier("COM1");}
              catch(NoSuchPortException e){}
        //Open a serial port connection at COM1
        try {
            serialPort = (SerialPort) portId.open("SimpleReadApp2", 7000);
        } catch (PortInUseException e) {}

        //Get the output stream for this connection
        try {
        outputStream = serialPort.getOutputStream();
        } catch (IOException e) {}
        //Get the input stream
        try {
            inputStream = serialPort.getInputStream();
        } catch (IOException e) {}

        //Initialize RS232 parameters
        try {
            serialPort.setSerialPortParams(9600,
                SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
        } catch (UnsupportedCommOperationException e) {}
        readCmdThread = new Thread(this);
        readDataThread = new Thread(this);
    }

  //Read commands sent by MCU
  public void readCmd(){
      cmdIndex = 0;
      serialPort.removeEventListener();
      try {
          serialPort.addEventListener(this);
	} catch (TooManyListenersException e) {}
        serialPort.notifyOnDataAvailable(true);
        TYPE = CMD;
        readCmdThread.stop();
        readDataThread.stop();
        readCmdThread.start();
  }

  //Read data sent by MCU
  public void readData(int numStepsX, int numStepsY){
      dataIndexX = 0;
      dataIndexY = 0;
      serialPort.removeEventListener();
      dataArray = new int[numStepsX][numStepsY];
      lengthX = numStepsX;
      lengthY = numStepsY;
      try {
          serialPort.addEventListener(this);
	} catch (TooManyListenersException e) {}
        serialPort.notifyOnDataAvailable(true);
        TYPE = DATA;
        readCmdThread.stop();
        readDataThread.stop();
        readDataThread.start();
  }

  //Process the commands/data sent by MCU
  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:
            //Process commands
            if (TYPE == CMD){
              try {
                  while (inputStream.available() > 0) {
                      cmdArray[cmdIndex] = (byte) inputStream.read();
                    // System.out.println("COMMAND:" + cmdArray[cmdIndex]);
                      if(cmdIndex == 0)
                        cmdIndex = 1;

                      else if (cmdIndex == 1){
                        cmdIndex = 0;
                        switch(cmdArray[0]){
                          case (byte)'m':
                            measure1 = cmdArray[1];
                            System.out.println("m received:"+ measure1);
                            break;
                          case (byte)'n':
                            measure2 = cmdArray[1];
                            System.out.println("n received:"+ measure1);
                            break;
                          case (byte)'p':
                            measure3 = cmdArray[1];
                            System.out.println("p received:"+ measure1);
                            break;
                          case (byte)'c':
                          //System.out.println("Caldone");
                            calDone = true;
                            final Message M = new Message(CmdInterp.xrange*0.017);
                            M.setVisible(true);
                            break;
                        //  case (byte)'s':
                          //  scanDone = true;
                            //break;

                        }
                      }

                  }

              } catch (IOException e) {}
            }
            //Process data
            else if (TYPE == DATA){
              try {
                  while (inputStream.available() > 0) {
                      System.out.println("P");
                      dataArray[dataIndexX][dataIndexY] = inputStream.read();
                      int currentPoint = dataArray[dataIndexX][dataIndexY];
                      if((dataIndexX == lengthX-1) &(dataIndexY == lengthY - 1))
                      scanDone = true;
                     if((currentPoint >= 0) & (currentPoint<50))
                          CmdInterp.topology.GridLabel[dataIndexX][dataIndexY].setBackground(Color.black);
                      else if((currentPoint > 50) & (currentPoint<100))
                          CmdInterp.topology.GridLabel[dataIndexX][dataIndexY].setBackground(Color.darkGray);
                      else if((currentPoint > 100) & (currentPoint<150))
                          CmdInterp.topology.GridLabel[dataIndexX][dataIndexY].setBackground(Color.gray);
                      else if((currentPoint > 150) & (currentPoint<200))
                          CmdInterp.topology.GridLabel[dataIndexX][dataIndexY].setBackground(Color.lightGray);
                      else if((currentPoint > 200) & (currentPoint<=256))
                          CmdInterp.topology.GridLabel[dataIndexX][dataIndexY].setBackground(Color.white);
                      CmdInterp.topology.repaint();
                      if(dataIndexX == lengthX-1) {
                        dataIndexX = 0;
                        dataIndexY++;
                      }
                      else
                        dataIndexX++;

                      //System.out.println((char)dataArray[dataIndex]);
                  }

              } catch (IOException e) {}
            }

            break;
        }
    }

    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {}
    }

    public void write(byte[] message){
          try {
            outputStream.write(message);
          } catch (IOException e) {}
    }
}


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

  v) Token Reader
package stmcontroller;

// Simple text input class for Cornell CS
// H. Perkins  7/97, 8/97, 1/98, 6/98
// File input constructor added by M Godfrey, 1/98

// Inspired by an idea in J. Bishop "Java Gently", A-W, 1997.

import java.io.*;
import java.util.*;

class TokenReader {

//	Simple text input from keyboard and files.  Input is parsed into tokens separated
//  by whitespace.  Tokens are returned as either int, double, or String values depending
//  on the input method.  If an int or double is requested and the next token is not a
//  properly formatted integer or floating-point number, an error message is written to
//  the console (actually System.err) and the method will repeatedly read tokens until
//  one with the proper format is read.
//
//  Here is an example of the use of this class by a client:
//
//		TokenReader in = new TokenReader(System.in);
//
//		int    k = in.readInt();
//		double d = in.readDouble();
//		String s = in.readString();
//      String t = in.readLine();
//
//  The identifiers s, d, k, and in are arbitrary.  Also, note that function readLine()
//  discards any unused input on the current line and returns the next input line
//  as a single string.
//
//  TokenReader input does not throw any IOExceptions.  To detect the end of the stream,
//  function eof is provided.  The expression
//
//		in.eof()
//
//  evaluates to true if the end of stream in has been encountered, false otherwise.
//  Example:
//
//		k = in.readInt();
//		while (!in.eof()) {
//			
//			k = in.readInt();
//		}
//
//  If an attempt is made to read past the end of the stream, an error message is
//  written to System.err and the input function evaluates to an unspecified result.
//
//  Finally, the operation
//
//		in.waitUntilEnter();
//
//  prints a message on the screen and then reads and discards a line of input.  It can
//  be used to pause execution to, for example, ensure that the console window remains
//  visible until the user chooses to terminate the program (a problem with the Windows
//  console window).
//
//  Class TokenReader can be used to read any InputStream, not just System.in.  Use the
//  desired stream as the parameter in the "new TokenReader(...)" allocation.
//
//  Technical note:  TokenReader is not derived from InputStream or any other stream class
//  because it does not provide the normal interface of a java.io input stream.


// Local state

private DataInputStream theStream;			// the stream being read
private boolean			eofReached = false;	// = "end of theStream has been encountered"
private String			S;					// current input line
private StringTokenizer	T;					// parser for current input line
											//	T==null means no more available tokens
											//	on this line (including when eofReached).


// Constructors:  a TokenReader object for input stream s or ds. (The second constructor
// is provided to reduce overhead if the desired stream is already a DataInputStream.)

public TokenReader(InputStream s)
{
	theStream = new DataInputStream(s);
}

public TokenReader(DataInputStream ds)
{
	theStream = ds;
}


// Constructor:  a TokenReader object connected to file fileName.

public TokenReader(String fileName) {
	try {
	    FileInputStream fis = new FileInputStream (fileName);
	    theStream = new DataInputStream(fis);
	} catch (FileNotFoundException e) {
	    System.err.println ("Sorry, couldn't find file " + fileName
		    + " for some reason.");
	}
}


// Yield next integer from stream.
// If the next token is not an integer, print an error message on System.err and
// continue reading input until an integer is read.
// If the end of the file is encountered before finding an integer, the smallest
// integer Integer.MIN_VALUE is returned.
public int readInt ()
{
	String item = ""; 	// next token as a string

	if (T == null)
		refresh();

	while (true) {
		if (eofReached)
			return Integer.MIN_VALUE;

		try {
			item = T.nextToken();
			return Integer.parseInt(item.trim());
		}
		catch (NoSuchElementException e) {
			refresh();
		}
		catch (NumberFormatException e) {
			System.err.println("Integer expected but input was \"" + item + "\".  Please try again.");
		}
	}
}


// Yield next double from stream.
// If the next token is not a double, print an error message on System.err and
// continue reading input until a floating-point number is read.
// If the end of the file is encountered before finding an double, a NaN is returned.
public double readDouble ()
{
	String item = "";	// next token as a string

	if (T == null)
		refresh();

	while (true) {
		if (eofReached)
			return Double.NaN;

		try {
			item = T.nextToken();
			return Double.valueOf(item.trim()).doubleValue();
		}
		catch (NoSuchElementException e) {
			refresh();
		}
		catch (NumberFormatException e) {
			System.err.println("Double expected but input was \"" + item + "\".  Please try again.");
		}
	}
}


// Yield next string from stream or an appropriate error message if
// attempting to read past eof.
public String readString ()
{
	if (T == null)
		refresh();

	while (true) {
		if (eofReached)
			return "readString called after reaching end of TokenReader stream.";

		try {
			return T.nextToken();
		}
		catch (NoSuchElementException e) {
			refresh();
		}
	}
}


// Yield the next input line from stream as a single string or an
//    appropriate error message if attempting to read past eof.
// Unused input remaining in the current line is discarded.
public String readLine ()
{
	refresh();
	if (eofReached)
		return "readLine called after reaching end of TokenReader stream.";

	// return new line and mark current line as fully read
		String line = S;
		T = null;
		return line;
}


// Yield "end of stream has been reached" (i.e. no more data)
public boolean eof()
{
	return eofReached;
}


// Write prompt to output, then wait until user enters an input line
public void waitUntilEnter()
{
	System.out.println();
	System.out.println("Press Enter to continue.");
	try {
		theStream.read();
	} catch (java.io.IOException e) {}
}


// Read next input line into S and initialize tokenizer T to parse it.
// Print appropriate error messages to System.err if attempting to read
// past end of stream or some other I/O error occurs.
private void refresh ()
{
	// print error message and return if eof already encountered.
		if (eofReached) {
			System.err.println("Attempt to read past end of TokenReader stream.");
			return;
		}

	// read next line into S and T
		try {
			S = theStream.readLine();
		}
		catch (EOFException e) {
			eofReached = true;
			T = null;
			return;
		}
		catch (IOException e) {
			System.err.println("Unexpected error reading TokenReader input.\nIOException: " + e);
			return;
		}

		if (S == null) {
			eofReached = true;
			T = null;
		} else
			T = new StringTokenizer(S);
}

}  // end of class TokenReader