LMICSE: Lego Mindstorms in Computer Science Education

Site Map | Contact Us
Project Overview | Staff | Grant Information
Short Workshops | Primary Workshops
CS 1 | Data Str. & Algo. | Prog. Languages | Architecture | Intelligent Sys. | Operating Sys. | Net-centric
Ada | C | C++ | Java | Lisp

Java Threading of JNI Serial Port Classes

small logo

Introduction

Multi-threaded programming is an important feature of modern software, and Java provides a well-organized set of classes, interfaces, and methods for creating threaded programs. Port/Device communication underlies many of our popular user applications such as the software packages that download images from digital cameras, upload mp3s to our iPods, and transfer data from webcams to display on a screen. Java does not typically provide classes for directly interfacing with serial or USB ports. It does, however, provide a scheme for treating C code as if it were wrapped inside a Java class called the Java Native Interface (JNI). Wireless networking is also an important basis upon which many of our applications operate. Protocols are rules specifying, among other things, the exact format of messages allowed to be passed between units in a network (wireless or otherwise).

Project Overview

In this project you will gain experience in all four areas by designing a system that sends formatted infrared signals to two Lego RCX units to make them play short tunes based on music data stored in two plain text files. The top-level portion of your system will be written in Java, and will contain two threads (besides the main thread) to communicate with the two RCXs. You will communicate with the RCXs using a serial Communicator class that relies on C code targeted for the Windows XP platform. The Java Native Interface (JNI) will be used to set up a link between this code and the Java Virtual Machine (JVM). The two threads must be coordinated so that they send the correct command to each RCX (and receive the correct acknowledgment) so that there are no serious delays in the music being performed. You’ll be interleaving invocations of the serial class’s methods to do this.

In the interest of time, I’ve given you all of the C code and the directions for making the dynamic link library (DLL) that Java will need to have available for its JNI. You are free to modify it and recompile it in Visual Studio if you think you’d rather enforce threadsafety at the C level of this project instead of relying on Java’s higher-level synchronized construct.

Here are the files you need:

I’m also summarizing the protocol that the software (firmware) on the RCX expects so that you can quickly write a method to translate the notes that I’ll be expecting your program to tell the RCXs to play into bytecode sequences that the RCXs can understand.

Step 1: Create DLL for Java to use Serial Ports

The JNI requires that the C/C++ code be compiled as a dynamic link library. This is so that the code can be loaded into memory on demand by the operating system into memory whenever the JVM loads a class whose methods are based on “foreign code” like C/C++. To do this in the Windows XP environment that the project will run on, you’ll need to use Microsoft Visual Studio on the PCs in the Software Engineering Lab (M158). Once you’ve created the DLL, you can also copy that file to the PCs in the Intelligent Systems Lab (M156). The computers in M156 do not have large enough hard drives to store Visual Studio, so the actual creation of the DLLs is not possible in that lab.

The file communications.cpp contains all of the code for basic serial port access operations from within Windows. It is in Appendix A of this handout, and you can also download it from the course website. If you look at the code you should see that it looks just like any regular C code except that the procedures all have the JNIEXPORT and JNICALL modifiers in their declarations. JNIEXPORT tells Visual Studio to make a procedure visible from the compiled DLL. That is, the compiler will consider Java code that calls the procedure as “safe,” and will allow the Java code to use the C code. The JNIEXPORT modifier allows a programmer to bundle “internal” helper procedures (that one wouldn’t really want the Java code to accidentally be able to use for fear that the access could corrupt privileged data) with “public” procedures that form the application programmer interface (API) of the DLL.

Different languages use different bit-level representations for various data types. Some might treat characters as 7-bit signed integers and ignore the sign bit while others treat them as 8-bit unsigned integers and pay close attention to that eighth bit. When we combine compiled code from two languages, we need to be able to tell the compilers whether any translation needs to be done to data being passed from one language as procedure parameters or a procedure return value to code in another language. The JNICALL modifier tells the compiler when to set up machine code that will translate C data type representations into Java data type representations, and vice version.

If you look closely at the parameter lists of the procedures in communications.cpp, you’ll notice that each has an initial parameter called “env” that is a pointer to a data structure of type JNIEnv. Each also has a second parameter called “obj” of type jobject. The parameter is how C code is given access to the information in the slots on an object in which it functions as a method. The first parameter is how information about the state of the JVM is passed to C code.

The following directions will show you how to create a DLL from it using Visual Studio. You’ll need to use both the communications.cpp file in Appendix A and its header file in Appendix B.

  1. Open Visual Studio
  2. Select New Project from the start page.
  3. Choose Win32 Project and name it “communications”
  4. On the next menu, click Application Settings on the left Side.
    1. Select the options "DLL" and "Empty Project"
    2. Click Finish
  5. In the Projects bar on the left side, right click on Source Files
    1. Choose "Add Existing..." and find "communications.cpp"
  6. In the Projects bar on the left side, right click on Header Files
    1. Choose "Add Existing..." and find "communications.h"
  7. Right Click on the "communications" project in the Projects bar
    1. Select "Properties"
    2. In the Properties Menu, select the folder C/C++
    3. Click on Additional Include Directories and Add
              C:/%Your Java Home Directory%/include/
              C:/%Your Java Home Directory%/include/win32/
    4. Exit the menu
  8. Click on the Build Menu on the top menu bar
    1. Select Build All

At this point, you’re done building the DLL. All you need now is to copy the resultant .dll file it into the directory of the java program that you are using it with.

Step 2: Write Java Code that Uses the JNI to use the DLL

Once the native C code has been turned into a DLL, you’ll need to write Java code that produces a “wrapper” class whose methods make use of the DLL’s exported procedures. In appendix C I’ve given you a complete Java file that does this for this project. The file defines a class called “Communicator” with the public methods “Open,” “Close,” “setupPort,””setPortTimeout,” “getReply,” and “writePacket.” If you examine the code you’ll see that these methods rely on private methods declared with the native modifier. This tells the Java compiler to expect to look for compiled C code to define those methods, and the “loadLibrary” method tells the Java compiler to look for the compiled code in a file named “communications.dll”.

To use this code and the .DLL file correctly, you’ll need to place them in the same directory, and pass Communicator as an additional argument to javac when you are compiling the Java code that you’ll be writing for the project.

I’m assuming that most of the Communicator methods are self-explanatory. The “set” methods need some extra background. You use “setupPort” to set the baud rate of the serial port. You should set it to 2400 in this project. You use “setPortTimeout” to tell the serial IR Tower how many milliseconds to wait before concluding that there is no more IR data to read from the RCX. A good number here would be anything between 80 and 300, depending on how bright the lighting is in the room you’re working with. You might need to experiment with this number if you don’t get the whole response back from an RCX.

We send a message to the RCX by storing the bytes of the message into an array and then passing it to the “writePacket” method. This method returns true if all bytes of the message were successfully transmitted out of the IR tower. The “getReply” method is what we use to see if an RCX has sent back an acknowledgment message. It returns returns the single next byte from the buffer on the tower. You'll need to call it in a loop until all the bytes have been read. You should be storing this sequence of bytes into an array on your own. The .getReply method returns a 0 when there are no more bytes left to read, but be careful here. Remember that the first three bytes of all of our messages will be 55 FF 00 in hexadecimal. That third byte is a bona fide 0, which will be dutifully returned by the .getReply() method. Instead, we need to modify the "stop" criteria to look for 0's being returned *in a row*. If we get one 0, we need to see if the next call returns a non-zero value, in which case the 0 is actually part of the reply. If another two 0’s are returned, we then definitely know (for our project, anyway) that the first zero was "end of message." This is a case where interested students might want to modify the C++ code to return NIL when there is nothing in the buffer to read.

Step 3: Write Java Code that Communicates with RCXs

The yellow RCX units that you’ll be working with each consists of a lot of hardware (infrared port, LCDs, gray sensor ports, black motor ports, a CPU, RAM, etc) and require some software to manage all of it. We call the software “firmware,” and it is a cross between an operating system and a virtual machine. You’ll need to make sure that your RCX units have no pre-existing firmware in memory before proceeding with the project. To do this, just take out one battery and press the green on/off button. That will clear any software left in RAM, and allow you to download new Mnet.lgo firmware into the RCX. You should visit either Dr. Klassner or Drew Jacobs to get the correct firmware installed on your RCX, and to have the correct RCX ID stored into your RCX. Make sure that the batteries are never taken out longer than for a minute, otherwise you’ll lose the firmware from memory and have to start again. This can be done in the Software Engineering Lab or the Intelligent Systems Lab.

The RCX firmware that we’re using executes commands in much the same way that the JVM or a CPU does. It loads an opcode byte (an instruction) into a command register, loads in any arguments (operands) that the instruction requires, and then executes the instruction. These instructions can either be stored on board the RCX as a program in memory alongside the firmware, or they can be sent individually to the RCX via infrared signaling. In this project we’ll be sending them from the desktop PC through the infrared tower.

Any signal that we want the RCX to respond to must conform to the firmware’s infrared (IR) protocol. All messages must have the format:

55 FF 00 OPCODE ~OPCODE ARG1 ~ARG1 ... ARGN ~ARGN SENDER ~SENDER TARGET ~TARGET SUM ~SUM

This means that the first three bytes of any message must contain the hexadecimal values 55, FF, and 00 (85, 255, and 0 in decimal). The next two bytes in the message must be the code for the command we want to execute, followed by the bitwise complement of that bytecode. The next set of bytes consists of the arguments that the command needs, with each byte of the arguments followed by its bitwise complement. After the last argument byte we place the 1-byte identifier of the machine that is sending the data (01 in our case) followed by the complement of that value. We then place the 1-byte identifier of the machine that the message is intended for (your RCX’s id in this case), followed by the bitwise complement of that value. In the final two bytes of the message we place the checksum of the message and the checksum’s bitwise complement. The checksum is the low-order 8 bits of the sum of all the (uncomplemented) bytes of the opcode, arguments, and sender and target.

The RCX continuously polls its IR port register to see if a new opcode has been received. If the same opcode (even if the arguments are different!) must be sent to an RCX two or more times in a row, then the RCX needs some indication that the new signal is in fact a new message and not just an old copy of an opcode already invoked earlier. This is done by alternating the 4th bit (bit number 3 if we’re counting with the low order bit labeled as 0) from a 0 to a 1 to a 0 to a 1, etc. each time the command is repeated.

When the RCX receives a message, it executes the command and sends back an acknowledgment message. The acknowledgement always starts with an exact duplicate of the original message, followed by a response byte, followed by its complement, followed by the “sender” and “target” fields and a new checksum and checksum complement for the response byte. You can thus check to see whether an RCX received your message or not, and whether it was the right one. In this project you’ll only need to know one opcode: PLAY-TONE. This opcode has hexadecimal value 23 or 2B, depending on whether or not the toggle bit is set. The opcode must have three bytes of operands. The first two bytes (making up the first argument) are the frequency of the tone, and the third byte (the second argument) is the duration of the tone in hundredths of a second.

The protocol and the RCX are “little-endian.” This means that values that are spread out over more than one byte (for example, the frequency argument value) must be arranged so that the low bits come first and the high bits come later. Therefore, when you put together the command package, make sure that the low-order byte of the frequency value is placed in the message array before the high-order byte.

If you send up to 5 “PLAY-TONE” requests to an RCX, these will be buffered so that the RCX can play one note while receiving more requests. However, if you send more than 5 notes before the RCX has a chance to play the first, then the buffer will “overflow” and the new notes (beyond the first five) will not be stored, but will be forgotten and not played. It is important for you to keep the buffer from overflowing by making sure that your threads in your Java program don’t send too many messages too quickly to the pokey RCXs! You should consider using .yield and .sleep thread methods in your Java code for this.

There is no extra RCX response byte for this message, beyond just the echo.

Step 4: Project I/O Specifications

You need to write your Java code so that it first opens two text files that will contain the notes for the two RCXs. These will have the names “song1.txt” and “song2.txt” and will be in the same directory as your program. You are free to read in the files one line at a time, or to read in each whole file into an array for faster transmission, if you think you need it. Each file’s line will represent a note and will have the following format. There will be two integers. The first will be a number representing the note’s frequency in Hertz. The second will be a number representing the duration of the note in hundredths of a second. You must pass this information to the appropriate RCX. There is no “end of file” code, and the files are not required to be of the same length. You’ll need to check whether there is any more data left in the files each time you read from them.

Step 5: Write Java Code the Coordinates Threads

The final “step” of this project is to write Java code that spawns two threads to communicate with your two RCXs to make them appear to play their music simultaneously. Be sure that you don’t try everything at once here. First make sure that you can send properly formatted data to the RCX, and verify that you got a correct response. If you don’t get a correct response, you should assume the note was lost and you should try sending the note request again.. Then, get one thread to communicate with one RCX. Finally, try getting two threads to perform correctly. Be careful that the Communicator object’s methods are used by only one thread at a time! Also, don’t let one thread close the port before the other is finished using it! You shouldn’t need to change the C code to enforce thread safety, but I won’t stop you if you want to.

You should make up some short tunes in test files to test your code on. Here are frequency codes that correspond to various notes:

A A# B C C# D D# E F F# G G#
440 466 494 523 554 587 622 659 698 740 784 831

Evaluation and What to Hand In

Your program will be evaluated on the basis of how well the two songs are played without losing too many notes. If you only get one thread to work, then you can expect about a 75-80 on the project. Note skipping will cost you, as well.

You should hand in:

  1. a separate (typed, please!) document of no more than 2 pages describing the overall program design, a description of “how it works,” and the design tradeoffs considered and made. Also describe possible improvements and extensions to your system (and sketch how they might be made). Be sure to include an explanation of why your threads might run faster or slower depending on whether the music files are loaded in all at once or if they are read in a line at a time and each line sent to the RCX before the next line is read in.
  2. a program listing (source code) containing inline commenting for the Java code. If you made no changes to the C code, then there is no need to hand that in.
  3. a separate description (no more than 1 typewritten page, please) of the tests you ran on your program to convince yourself that it is indeed correct. Also describe any cases for which your program is known not to work correctly.

Hints

  • In Java, remember that the bitwise complement operator is the tilde (~).
  • When you are using the .Open method, you need to pass in the string "\\\\.\\COM1" to open up serial port 1 or "\\\\.\\COM2" to open up serial port 2.
  • Your actual Java program should have a MusicPlayer class that will extend the Thread class. It is inside the .run method of this class that you'll be placing all the code for reading from a plain text song file, translating the notes into byte sequences to send to the RCX, sending the byte array to the RCX, and getting the RCX's response. The constructor for this class should take three input parameters: the serial communicator, the ID of the RCX that the given thread will need to communicate with, and the name of the song file the thread will need to read from.
  • The main method of your program should be where the single instance of the Communicator class is instantiated (i.e. where you call "new Communicator"), and where you create the two threads and call their .start() methods. It will be wise to make the main method (which is a thread itself) be responsible for opening and closing the Communicator object. Be careful to make sure that the serial port is closed when your program finally exits, otherwise the Windows OS will think that the port is still in use and won't let your program open the port again if you run it again in the same login session. You'll have to log out or even restart the computer to get the serial port back!
  • See Andrew Chang’s Independent Study to find out what the bytecode is that sets the ID of the RCX if you don’t have time to see me.

Acknowledgements

This project was created by Frank Klassner.