Skip to main content

Robotics: Using Lego Mindstorms and Java

February 21, 2005

{cs.r.title}









Contents
Inside LEGO Mindstorms
Getting and Setting up leJOS
LeJOS API
   Motor
   Sensor
   Buttons
   Communication
Peek Under the Hood
Test Drive
Conclusion
Resources

In the past, robotics and related research was pretty much limited to serious hobbyists or to those working at big corporations, research institutions, and universities. The introduction of affordable Robotics Invention System by LEGO Mindstorms has helped make the art and science of robotics available to every one. The LEGO Robotics Invention Kit has all the necessary LEGO components to design, build and program a fully functional robot in very little time requiring very little prior experience. In this article we will explore the brain of LEGO Mindstorms that will enable us to program the robot to perform the functions we intend to. You will look at installing and using the Java-based leJOS firmware.

Inside LEGO Mindstorms

The brain of the LEGO Mindstorms is the programmable microcontroller brick, called RCX. The RCX evolved from MIT's programmable brick, which was originally developed so that a developer could design and build small mobile devices that work independently without requiring a physical connection to a host machine. Programs are created on a host computer and downloaded to the brick, either through an infrared link or through a wired connection. Once the program is downloaded into the brick, the mobile device can start functioning independently from the host machine.

The RCX is the programmable brick that was designed and developed by LEGO. At the core of the RCX brick is a Hitachi H8 3297 microcontroller that has 32K of external RAM. There is also an on-the-chip 16K ROM that has the drivers that run when the RCX is turned on for the first time. The 32K of RAM is logically divided into sections: 16K for the firmware, 6K for storing user programs, and 10K for interpreting the byte code and handling the program execution. The RCX can be programmed to simultaneously control up to three motors, three sensors, and an infrared communication device.

The RCX is programmed using the legOS-supplied GUI utility tool, which abstracts the details of the programming semantics and provides an intuitive user interface, where the user can drag and drop to assemble components to generate the program. The program that is generated can then be downloaded into the firmware using the infrared tower. The default firmware, however, can be replaced with a variety of alternate firmware, driven by the choice of programming languages. In our case, we will be replacing the original firmware with the Java-based leJOS firmware that will enable us to develop the user programs in Java.

Getting and Setting up leJOS

Firmware is the operating system that runs inside of the RCX brick, which is responsible for executing the user programs. A brand new RCX does not come with a firmware pre-installed; the LEGO Mindstorms Robotics Invention System kit includes a CD-ROM that contains the LEGO Firmware. The user has to download appropriate firmware into the RCX. As far as the firmware that supports the Java programming model goes, there are two choices available: leJOS and TinyVM. Both of these projects are open source and are hosted on SourceForge.net. In this article, we will primarily look at leJOS, with occasional reference to the TinyVM project. The leJOS firmware has a virtual machine for interpreting the Java bytecode, as well as additional software resources to load and run Java programs.

To set up leJOS, first obtain the leJOS binaries from www.lejos.org. The binary is distributed in .zip format. After extracting the contents of the .zip file to the local file system, the next step is setting up the environment variables. The environment variables that need to be set up are:

  • Setting LEJOS_HOME to the directory where leJOS was extracted:
    • SET LEGOS_HOME=<Directory where leJOS .zip was extracted>
  • Setting the CLASSPATH environment variable to include leJOS classes:
    • SET CLASSPATH=.;%CLASSPATH%;%LEGOS_HOME%\lib\classes.jar;%	
                           LEGOS_HOME\lib\ pcrcxcomm.jar
  • Setting the PATH environment variable to include the leJOS bin directory:
    • SET PATH=%PATH%;%LEGOS_HOME%\bin;
  • Setting the RCXTTY environment variable to the tower device communication mechanism:
    • SET RCXTTY=USB (if communication is via USB) or SET RCXTTY=COM1 (for communication via the serial port)

Once the environment variables are set up, the leJOS firmware can be uploaded into the RCX brick. This is accomplished by executing the MSDOS batch file firmdl.bat, which is provided along with the distribution. Essentially, this batch file transfers the JVM image file lejos.srec for the RCX, which is supplied in the LEGOS_HOME/bin directory of the distribution. This firmware file is in Motorola's "S Record" format. Of course, the RCX should be turned on and aligned properly with the tower for successful transfer of the firmware to the RCX. When this command returns without error, the leJOS firmware has been downloaded into the RCX. During the download process, the counter in the LCD window of the RCX is incremented until it reaches 1662. Each unit indicates that 10 bytes of data has been downloaded into the RCX; the total size of the firmware is 16624 bytes. The RCX device will beep twice when the download completes successfully. The display in the LCD screen of the RCX will now refresh to show the voltage level and the number 0.

Figure 1
Figure 1.

If the user wants the RCX to go back to its original state, the leJOS firmware can be deleted simply by removing the batteries from the RCX brick for about 30 seconds. Consequently, when the battery is removed, the leJOS firmware will be completely removed, along with any user programs. The following image indicates the RCX and tower setup during the leJOS download process.

Figure 2
Figure 2.

The next step in the process is to test drive the robot by writing an application. The code can be written using any standard Java IDE or your favorite text editor. Once the code is written, it needs to be compiled using the batch file lejosjc.bat provided with the leJOS distribution. All that this batch files does is compile the source with 1.1 as target by passing the target option to the VM. This will not work with JDK version 5.0 (and above). The output from the compiler, the standard Java byte code, then becomes input for lejoslink.bat, which produces a binary file for download. The linker's main function is to figure out all of the byte code that the program requires and then bundle it up. The operating mechanism here is different than it is for regular Java applications, where the virtual machine loads up the classes in a lazy manner. In the RCX world, once the program is downloaded, the RCX does not establish a connection to the host to request more byte codes at runtime. The last step in the process is to download the binary file produced from the linker into the RCX using the supplied MSDOS .bat file lejosdl.bat.

LeJOS API

In order to get a concrete understanding of a technology, it's imperative that we dive into writing code and then see for ourselves how it works. However, before we do that, let's take a look the core leJOS API that covers various programming semantics dealing with RCX, such as navigation, sensors, communication, vision, and speech.

Motor

The LEGO motor is represented by josx.platform.rcx.Motor class and has methods that provide important mechanisms to control the robot. This class has three static member variables that logically represent the three black motor output connectors in the RCX brick, labelled A, B, and C. The motor class has a few control methods, including forward(), backward(), and stop(). Invoking these results in the motor performing the operations; moving forward and backward, and stopping. The other important method is flt(). This will let the motor float free and is especially useful when the robot is navigating a sharp curve. The other important method that will enable the developer to set and get the power level of the motor is setPower(int). This method takes an integer number between 0 and 7 as parameter. Here, 7 represents the highest power level and 0 the lowest. getPower() returns the current power level as an integer.

Sensor

The LEGO sensor is represented by the josx.platform.rcx.Sensor class and has methods to read the sensor's values. This class has three static member variables that logically represent the three gray output connectors in the RCX brick named 1, 2, and 3. Before the sensor can be used, the mode and type of the sensor needs to be set using the method setTypeAndMode(). The josx.platform.rcx.SensorConstants interface provides constants that provide the type and mode for the sensor.

Use sensors in conjunction with josx.platform.rcx.SensorListener to provide an event-based mechanism that reacts, based on sensor events. The stateChanged() method of the object that has registered with the sensor object will be notified when the state of the sensor changes. For example, the following inner class deals with handling an event generated by a touch sensor.

   Sensor.S1.setTypeAndMode (SensorConstants.SENSOR_TYPE_TOUCH, 
                                     SensorConstants.SENSOR_MODE_BOOL);
   Sensor.S1.addSensorListener (new SensorListener() {
    public void stateChanged (Sensor src, int oldValue, int newValue) {
       boolean flag = src.readBooleanValue();
       if ( flag) {
// do some thing important here!!!
       }
     }
   });
Buttons

There are four rubber buttons on top of the RCX brick. The red button is for powering the RCX brick on and off, and leJOS doesn't provide any API abstraction for this button. leJOS provides three constants that defines the other three button on the RCX. Button.VIEW defines the black "view" button, Button.PRGM defines the gray "Prgm" button, and Button.RUN defines the green "Run" button. Buttons, like sensors, fire events when the button is pressed or released. The listener object registered with a button defines two callback methods, buttonPressed() and buttonReleased(), where appropriate handling logic can be defined.

Communication

The leJOS comes with rich communication APIs. Using the API, it's possible to achieve communication from the RCX to the host PC, from RCX to RCX, and from the remote control to the RCX. The leJOS communication API can be found in the java.io, josx.rcxcomm, and josx.rcxcomm.remotecontrol packages. The communication classes use streams to send and receive data. The leJOS has most of the basic streams, such as InputStream, OutputStream, DataInputStream, and DataOutputStream. However, some of the functionalities that are available with streams in the core JDK are not available here due to memory restrictions. Additionally, the leJOS does not provide support for object streams and serialization.

The data transfer between the RCX and the host PC is possible through either the serial tower or USB tower in the Windows environment. In other environments, such as Linux or Mac OS X, only the serial mechanism is currently supported. There are four different port implementations that can be used to address communication between the RCX and host PC.

  • RCXPort: This class supports reliable streams using low-level communication. This makes use of native methods to read and write bytes to and from the IR tower. It ensures that all packets get through. Communication will stop when the IR tower is not in view or in range, and will resume when it comes back into view.
  • RCXF7Port: This is an interface similar to java.net.Socket and supports not-so reliable streams using the F7 LEGO firmware opcode. This class uses serial communication and not low-level communication.
  • RCXLNPPort: This is an interface similar to java.net.Socket and uses the legOS Networking Protocol (LNP). This version of RCXPort uses the Integrity Layer of LNP. This ensures that packets are not corrupted, but does not ensure that they get through. Packets can get lost.
  • RCXLNPAddrPort: This is an interface similar to java.net.Socket and supports point-to-point connections using the LNP Addressing protocol layer. The LNP Integrity layer is used to ensure that packets are not corrupted, but they are not guaranteed to get through. Packets can get lost.









Peek Under the Hood

In order to get a decent understanding of what is going on, it is important that we look under the hood to see what really happens. With leJOS, this can be accomplished by using the emulation software that is bundled with the distribution. Here we will write a very simple program that sets the power of motor A to maximum (7) and then starts the motor in the forward direction. The code is as follows:

import josx.platform.rcx.*;
public class SimpleTest {
  public static void main (String[] aArg) {
    Motor.A.setPower(7);
    Motor.A.forward();
  }
}

The source is compiled as follows to the byte code lejosc SimpleTest. Then, by running the emulation binary on the byte code, the following output is produced. Running the emulation software this way will not produce the linked binaries, but just display the instruction set.

C:\Tech\Article\Lego\Dev\Legos\src>emu-lejos.exe SimpleTest
& ROM call 3: 0x1A4E (8192, 4, 7)
& ROM call 3: 0x1A4E (8192, 1, 7)

Figure 3
Figure 3.

As can be seen from the above image, the method calls setPower() and forward() to motor A are translated into native calls. The rom.h header file, which can be found under LEJOS_HOME/rcx_impl, gives us clues as to how these instructions are translated into native calls. For instance, the instruction 0x1A4E (8192, 4, 7) is a call into the following function:

static inline void control_motor (short code, short mode, short power)
{
    __rcall3 (0x1a4e, code, mode, power);
}

The three parameters are code, mode, and power. From the comments of the rom.h source code, the following are the possible values for code, mode, and power.

  • code=2000: Control motor 0
  • code=2001: Control motor 1
  • code=2002: Control motor 2
  • modes: 1=forward, 2=backward, 3=stop, 4=float
  • power: valid are 0..7, values are taken modulo 8

Going back to the source code, the first line of code, Motor.A.setPower(7), translates into & ROM call 3: 0x1A4E (8192, 4, 7), which is a function call into control_motor, with the code as 2000 (8192 as hex is 2000), mode as 4 (flt), and the power level as 7. The second method call, Motor.A.forward(), is translated into & ROM call 3: 0x1A4E (8192, 1, 7). This is delegated to the native system as a function call into control_motor, with the code as 2000 (8192 as hex is 2000), mode as 1 (forward), and power level as 7. As it can be observed, no query is made to find out what the power level is between the first call and the second call. This is because the power level of 7 is cached as an instance variable in the Motor class, and hence, when the second call is made, the cached value is used to send the right power level to the underlying native subsystem.

Test Drive

Finally, it is now time to write some code and see how everything fans out. To start with, we will write a very simple program that will enable our robot to move forward until it encounters an obstruction. When stopped by the obstruction, the robot will back up, turn to the right and go forward again. This process continues until the robot is stopped by the user pressing the green Run button on the RCX brick. Essentially, the core purpose of this robot is to wander aimlessly. But in spite of such a non-productive purpose, the means of getting to the end will help us understand how to effectively make use of the leJOS API.

We will use the behavior model provided by the leJOS architecture to implement the above requirement. The concept of behavior is actually simple and extensible. Conventional robot programming is always thought as a flow control process. For example, the following diagram specifies what we are trying to achieve, using traditional flowcharts.

Figure 4

Figure 4.

The Behavior API encapsulates behavior in an object and exposes callback methods that enable an external agent to control the behavior of the robot. This also helps developers to add, modify, or remove behaviors easily without visiting the other areas of the code. The Behavior interface has three important methods and they are:

  • boolean takeControl(): This method returns a Boolean value. A value of true indicates that this behavior should become active. For example, if we are trying to model a touch sensor, this would return true if the sensor is touched; otherwise it should return false.
  • void action(): When the method takeControl() returns true, it means the behavior should be made active. This method will be called when the behavior becomes active. This is where the core logic of the expected behavior goes.
  • void suppress(): This purpose of this method is to cancel whatever the action() intends to do. This method can also be used to update any data before the behavior completes.

The above three methods define the core control areas for behavior. Any class that implements the Behavior interface is required to provide concrete implementation for these methods. Once various behaviors for a robot are defined and implemented, the next step is to have an arbitrator work with the behavior objects. It's the responsibility of an arbitrator to call the appropriate method in a defined sequence.

The arbitrator is a concrete class that regulates how and when a behavior becomes active. The arbitrator defines a constructor that takes in an array of behavior objects. The priority level of the behavior objects is determined by the array index. For example, the behavior object that is at the end of the array has higher priority over the behavior object that is at the beginning of the array. So when two behavior objects return takeControl() to be true at the same time, which action() to call is determined based on which behavior object has an higher array index. For the arbitrator to start arbitrating, the start() methods should be called.

In our example we will define three different behavior objects. The primary purpose of the MoveForward object is to define the behavior of moving forward. The three methods are implemented as follows.

public class MoveForward implements Behavior {
public boolean takeControl() {
return true;
}

public void suppress() {
Motor.A.stop();
Motor.C.stop();
}

public void action() {
Motor.A.forward();
Motor.C.forward();
}
}

The method takeControl() returns true all the time because that is what we want to do--keep moving forward. The action() lets the motors A and C rotate in the forward direction. suppress() stops both the motors.

Our next behavior is to steer away from obstacle. While the robot is in motion, if an obstacle is encountered, then the touch sensor is activated. When this happens, the robot backs up and then turns to the right. The three behavioral methods for this class are as follows:

public class SteerObstacle implements Behavior {
public boolean takeControl() {
return Sensor.S2.readBooleanValue();
}

public void suppress() {
Motor.A.stop();
Motor.C.stop();
}

public void action() {
Motor.A.backward();
Motor.C.backward();
try {
Thread.sleep(1000);
} catch (Exception e) {}
Motor.A.stop();
try {
Thread.sleep(1000);
} catch (Exception e) {}
Motor.C.stop();
}
}

In this case, the takeControl() method return true or false depending on whether the touch sensor is touched or not. The action() method starts the motors A and C in the backward direction for 1000 milliseconds and then stops the A motor. The C motor is continued backwards for another 1000 milliseconds. With one motor stationary and the other motor rotating, we get a rotational movement of the robot towards the right.

The last behavior object is to stop the motors when the green Run button on the RCX is pressed. The three behavioral methods are as follows.

public class Stop implements Behavior, ButtonListener {
private boolean buttonPressed = false;
public Stop(){
Button.RUN.addButtonListener(this);
}

public boolean takeControl() {
return buttonPressed;
}

public void suppress() {
Motor.A.stop();
Motor.C.stop();
}

public void action() {
Motor.A.stop();
Motor.C.stop();
}

public void buttonPressed(Button b){
if ( buttonPressed )
   buttonPressed = false;
else
   buttonPressed = true;
}

public void buttonReleased(Button b){}
}

In this case, we have implemented a ButtonListener to find out if the green Run button of the RCX was pressed. In the button is pressed, then we return a true and if the button is pressed again, we return a false. A listener to the button could be added as Button.RUN.addButtonListener(this); and two callback methods, buttonPressed() and buttonReleased(), need to be implemented. The action() and suppress() methods in this behavior simply stop both the A and C motors.

Finally, we have implemented three behaviors. To arbitrate the behavior, we implement the following arbitration code.

public class Wanderer {
public static void main(String[] args) {
Wanderer wanderer = new Wanderer();
wanderer.wander();
}

private void wander(){
Behavior b1 = new MoveForward();
Behavior b2 = new Stop();
Behavior b3 = new SteerObstacle();
Behavior [] bArray = {b1, b2, b3};
Arbitrator arby = new Arbitrator(bArray);
arby.start();  
}
}

As it can be seen, we simply create three behavior objects and one arbitrator object. Of course, the arbitrator knows what behaviors to arbitrate based on the array of behavior objects passed into its constructor. Finally, the arbitrator is started by the start() method.

Now that the source code for the application is written, we will have to compile it, link it to create a downloadable binary, and upload the binary into RCX for execution. This is accomplished as follows.

Compile the source using the provided lejosc batch file.

>lejosc com\article\lejos\sample1\MoveForward.java
>lejosc com\article\lejos\sample1\SteerObstacle.java
>lejosc com\article\lejos\sample1\Stop.java
>lejosc com\article\lejos\sample1\Wanderer.java

Link the source to create the binary file, called Wanderer.bin. The name can be anything, but for sake of clarity, it's recommended that you use the filename ".bin."

>lejoslink -o Wanderer.bin com.article.lejos.sample1.Wanderer

Finally, upload the binary using the provided batch file.

>lejosdl Wanderer.bin

Figure 5
Figure 5. Click image for full-size screen shot.

In the above image, notice the message "Tower not responding." This happened because the RCX was not turned on. However, sometimes a developer might encounter issues during the transfer process. When this happens, turning the RCX off and then back on seems to fix the problem.

When the code is downloaded into the RCX, pressing the Run button causes the program to be executed. When this happens, the robot starts moving in the forward direction until it encounters an obstacle. When it does, the touch sensor is activated, which causes the robot to back up for one second and then turn to right. After turning to the right, the robot continues forward until it encounters another obstacle. Pressing the green Run button will now cause the robot to stop. Pressing the Run button will result in robot starting to wandering again.

Conclusion

In this article, we just scratched the tip of the iceberg. The leJOS API provides a rich set of functionality to explore other areas like communication, navigation, vision, and speech. leJOS and its peer TinyVM let Java enthusiasts program their robots in their favorite language, with the only limitation being the extent of the imagination.

Resources

width="1" height="1" border="0" alt=" " />

Krishnan Viswanath is currently working for JPMorgan Chase & Co. in Kansas City, MO.