MathCS - Robotics

Programming in Java


Ground Rules of Java Programming

In the last section we introduced the Java Ground Rules and applied them to create a few simple programs dealing with NXT components. Here we will revisit these ground rules, but apply them to general Java programming concepts. Recall that Java, just as Perl, C++, Basic, Java, Scheme, Ruby, NXT-G, is a computer programming language used to provide instructions to a computer.

  1. A Java program is written by a human (the programmer) as a plain text file called the source code. It is checked for typos and other errors by the Java compiler, which translates the source code into machine code if it finds no errors. The machine code can then be executed by a computer one line after the other in sequence. The computer will do no more and no less than what your sequence of instructions specify.
  2. A Java program is written as a sequence of statements, one per line, in accordance with the the grammar and syntax rules of the Java programming language.
  3. Java is case-sensitive, i.e. the word “LCD” and “Lcd” are considered to be different.
  4. A valid Java statement must end with a semi-colon ; unless it starts a group.
  5. Java names can not contain any spaces or strange symbols and cannot start with numbers.
  6. Java uses three sets of parenthesis/brackets:
    • curly brackets “{ … }” to group statements together
    • regular parenthesis “(…)” to denote inputs to functions and for math expressions
    • square brackets “[ … ]” to denote what’s called arrays
  7. Java uses two types of comments you can provide "plain English" explanations inside a program. Comments can be written in any language and are ignored by the computer
    • Single-line comments start with //
    • Multi-line comments are enclosed in /* … */
  8. The computer is hopelessly detail-oriented: when you create a Java program, everything must be exactly right – forget a semicolon, miss-spell a word ever so slightly, use an extra space in a word, or forget the right kind of closing brackets and your program will not be understood.
  9. Just because your program is spelled correctly (grammar and vocabulary) does not mean it will also work correctly – all programs indeed require extensive testing.
  10. Every (almost) Java program has a unique program name and looks like  this as a minimum(most specific programs, of course, contain additional lines of code):
public class ProgramName
{
	// This is a one-line comment	
	public static void main(String[] args) throws Exception 
	{
         /*
            This is a multi-line comment
         */	
   }
}

To help us generate programs, compile them, and download them to our NXT brick for execution we will be using a widely known and handy program called Eclipse (known in the trade as an IDE – Integrated Developing Environment). Its use has been explained previously.

table of content


Doing the Math: Variables and Operations

Previously, commands like Motor.A.rotate(180) apply specifically to the NXT kit. Of course there are also numerous terms that apply and can be useful in any Java application, whether they have to do with robots or not. The most elementary construction is that of a variable:

A variable is a name for a specific segment in the computer's memory. Every Java variable must have a type and should get an assigned value when it is setup. Everything and anything that your program needs to remember must be stored in a variable. In other words, variables are kind of named portions of the robot's mind. A variable name consists of letters, possibly followed by numbers, no spaces or strange characters, and usually starts with a small letter.

Variables can be of complicated types (such as variables of type TouchSensor or SimpleNavigator) or of more elementary types (so-called basic types). The basic types in Java are:

Java type English meaning Exampes
int integer number 123, -42, etc.
float decimal number 67.99f, -23.0f, etc. (note the mandatory f after the digits)
char single character 'a', 'X', '1', etc. (a single letter enclosed in single quotes)
boolean logical constant true, false
String list of characters aka text "Bert Wachsmuth" (0 or more characters enclosed in double quotes)

Java knows a few additional basic types, but the aboves ones are the most common ones. To define variables of specific types you would use code similar to the following:

int x1 = -42;
float y1 = -9.7f;
char letter = 'b';
boolean state = true;
String name = "Bert";
TouchSensor touch = new TouchSensor(SensorPort.S1);

Note that while touch is not a variable of basic type, its definition follows a similar pattern and touch is a variable of type TouchSensor.

There are several basic operations that you can do with these variables, depending on their type:

for int and float op  
  + adds two numbers and returns their sum; if both numbers are int the sum will be int, otherwise float
  - subtracts two numbers and returns their differencee;  if both numbers are int the difference will be int, otherwise float
  * multiplies two numbers and returns their product;  if both numbers are int the product will be int, otherwise float
  / divides two numbers and returns their ratio;  if both numbers are int the ratio will be int, otherwise float
  % computes the remainder after integer division; for example 5 % 2 = 1 and 5 % 3 = 2

Example:

public class MathTest 
{
	public static void main(String args[]) throws Exception
	{
		LCD.clear();
		int x1 = 20;
		int x2 = 10;
		float pi = 3.1415f;
		float r = 2.0f;
		float area = pi*r*r;
		LCD.drawString("Result 1: " + (x1 - x2)/(x1 + x2), 1, 1);
		LCD.drawString("Result 2: " + area, 2, 1);
		LCD.drawString("Result 3: " + (x1 % x2), 3, 1);
		Button.ENTER.waitForPressAndRelease();
	}
}

Note: The result of the second computation is 12.566, which makes sense, but the result of the first calculation is (20-10)/(20+10) = 10/30 = 1/3 = 0, strangely enough (make sure to verify). That is because the division of two integers will always result in an integer. Since mathematically 1/3 = 0.3333, the computer chops off the decimals to turn it into an integer (no rounding occurs). How could you compute the correct mathematical answer with the NXT? What is the third result?

There are a number of more advanced mathematical functions available (trigs, logs, etc.) but we will check them out in the next section. For now we'll discuss a more sophisticated and (somewhat) illustrative example.

Example: Going the Distance

Suppose you have constructed a differential drive robot with one motor attached to port A and the second one to port B. If you used the standard wheels, you will find that the wheel diameter is 5.6 cm. Compute the degrees that the wheels must rotate to advance the robot by exactly 50 cm. Verify your computation by running the program on your robot. Then modify your program so that the robot drives forward 1 m.

Finally an interesting problem! Let's draw a picture first:

Arc length of a sector

The circle of radius R represents a sideways view on our robot. The circle needs to roll and cover a distance of 50 cm. You recall from trigonometry that the length of a sector with angle t of a circle with radius R is t*R, as long as the angle is measured in radians. Since we want that length to be 50, we have:

t*R = 50  or, solved for the angle  t = 50/R

Thus, to cover a distance of 50 cm our wheel must rotate by an angle of t = 50/R - but that angle is in radians while we need degree (why?)s. But to convert radians into degrees you have the formula:

(angle t in radians) = Pi * (angle A in degrees) / 180

Putting both equations for t together we get:

50/R = Pi*A/180 or, solved for the angle in degrees A = (180*50) / (Pi*R)

To keep everything even more flexible, we use the variable D to designate the desired distance (instead of the fixed number 50) to get the following equation: in order for a wheel of radius R to roll along a distance D, it needs to rotate by an angle A in degrees, where

A = (180*D) / (Pi*R)

Now we have done the heavy lifting and derived a formula for the angle in degrees that our motors must rotate to cover the desired distance. We can now translate that into a flexible program for our differential drive robot (recall the Motor commands introduced previously), where we use comments inside our program to explain the meaning of the variables:

public class RotateExample
{
	public static void main(String args[])
	{
		// The distance to move forward
		float distance = 50.0f;
		
		// The radius of the wheels
		float radius = 2.8f;
		
		// The approximate value of Pi
		float Pi = 3.1415f;
		
		// The angle in degrees according to our formula
		float angle = (180*distance)/(Pi*radius);
		
		// Now rotating both wheels by that many degrees and
		// the robot should drive forward by 'distance'
		Motor.A.rotate(angle);
		Motor.C.rotate(angle);
	}
} 

However, this may or may not be right. To verify whether the program works you need to download the program to the NXT and run it. If the robot moves forward for approximately 50 cm our program is correct.

But, when you do run the program, the robot does not move forward at all. It first rotates to the left for a while, spinning only its left wheel, then to the right for about the same time, spinning only its right wheel. So the program does not work correctly and we need to find the error. Incidentally, an error that happens when you run a program is called a run-time error. Errors that you make while typing a program are compile-time errors. Compile-time errors are no problem to fix, because the compiler will recognize them and flag them for you. Fixing it might be a little tricky, but at least the compiler will tell you where the error is and what could be wrong. You can even try to auto-correct! Run-time errors are another matter, however: you do know something wet wrong because the program does not perform as expected. You need to carefully trace your program by hand, trying to find the exact line that executes when the problem occurs.

In our case it is pretty simple to see that the line Motor.A.rotate(angle) causes the problem: it spins up motor A but waits until that motor has rotated for the desired angle. Then it stops and the program continues. Next, motor B rotates by itself, again a problem. What we need is a command that tells motor A to spin for a number of degrees, but without waiting: while motor A is spinning for he amount of degrees specified, the program needs to continue and start spinning motor B, and then the program should wait until both motors are done spinning. When we look through the motor commands we indeed find two version of the "rotate" command: we need to use one of the version to start spinning motor A but continue execution, and then the other to spin motor B and wait until it is done. Thus, we need to replace the line

Motor.A.rotate(angle);

by

Motor.A.rotate(angle, true);

and now the program should work correctly. Make sure to try it out! It is now easy to make the robot drive forward for 1 m. Try it! Can the robot drive backwards, too?

It is worth reviewing our strategy to solve this problem:

how_to_program
  1. Construct a robot suitable for the task
  2. Create a model or drawing for the situation, including mathematical formulas
  3. Created an algorithm, i.e. a sequence of steps based on our model and formulas that should solve the task at hand
  4.  Convert algorithm into a Java program, keeping our program flexible by introducing variables and readable by using comments
  5. We tested the program by running
  6. Refine construction, model, algorithm and/or program

This approach will turn out to be useful for many problems in the future. Note that we combined step 3 (algorithm)and 4 (program) in our "drive forward" example, but for more complicated tasks we will develop an algorithm separately from the Java program.

table of content


To Be or Not to Be: Logic Operators

In addition to doing math with numbers, Java can also work with boolean variables. Remember that a boolean variable is one that can have one of two possible values, called true or false. They can be manipulated by the following operators:

for boolean    
  && logical "and", i.e. A && B is true only if both A and B are true
  || logical "or", i.e. A || B is true if either A or B or both are true
  ! logical "not", i.e. !A is true if A was false
  == equality, returns true if left and right sides have the same value, false otherwise
  > greater than
  >= greater than or equal
  < less than
  <= less than or equal

Example:

public class LogicalTest 
{
	public static void main(String args[]) throws Exception
	{
		LCD.clear();
		boolean x1 = true;
		boolean x2 = false;
		boolean riddle1 = ( (x1 && x2) || (!x2) );
		boolean riddle2 = (3 / 2) > (3.0f / 2.0f);
		LCD.drawString("Riddle 1: " + riddle1, 1, 1);
		LCD.drawString("Result 2: " + riddle2, 2, 1);
		Button.ENTER.waitForPressAndRelease();
	}
}

Note: At first this mathematical ability might seem pointless. After all, a simple calculator can do the math calculations just fine, even without all of this typing, compiling, downloading, and executing. And what could this boolean stuff be possibly be good for, it seems confusing at best!

All true, but the power of the above operations will only become apparent when we create more useful programs later. For now, bear with me and recall that learning English grammar is (with all due respect) pretty dull, too, yet it enables you to write beautiful poems and novels eventually.

table of content


Making Decisions: Conditionals

In most real-live situations a robot does not simply execute the same instructions one after the other and then quits. Instead it needs to make decisions, evaluate a particular situation, and react sometimes like this, other times differently. Also, it often repeats some operations until a certain goal is matched, then switches to some other behavior. To model these situations we need to introduce two Java constructions called a conditional and a loop. Once we have those, we can begin to create robots with interesting features.

A conditional lets your program make a decision: it can execute some code only if a certain condition is true. For example, you might need a robot to drive forward if the path ahead is clear, otherwise it should make a left turn. Or your robot should stop doing whatever it is doing if you yell "stop". Or it should take a picture if it detects some motion. Or ... plenty of examples should come to mind ...

Java conditionals come in three parts. As usual, we first provide the exact syntax, then we'll show a few simple examples, and eventually we'll create complex robots:

Simple Conditional:

if (condition_is_true)
{
   // execute this code
}

Conditional with Alternative:

if (condition_is_true)
{
   // execute this one code
}
else
{
   // execute this other code
}

Nested Conditional:

if (condition_1_is_true)
{
   // execute code 1
}
else if (condition_2_is_true)
{
   // execute code 2
}
...
else if (condition_N_is_true)
{
   // execute code N
}

Each condition must be a boolean expression or a statement that evaluates to a boolean value. Since there are three types of conditionals, a natural question is when to use which variation:

Examples: For each of the following programs, decide which conditional is appropriate, then create the complete program:

  1. a program to play a tone if the touch sensor is pressed
  2. a program to decide if the light sensor sees light or dark
  3. a program to decide if an obstacle is close (< 25 cm), near (between 25 and 50 cm), far (between 50 cm and 1 m), or very far (>= 1 m)

The first is a simple conditional, the second a conditional with alternative, and the last a nested conditional. The corresponding programs would go as follows:

Solution to 1. We assume a touch sensor is hooked up to port 1 of the brick.

public class Conditional1Test
{
	public static void main(String args[])
	{
		TouchSensor touch = new TouchSensor(SensorPort.S1);
		if (touch.isPressed())
		{
			Sound.playTone(440, 1000);
			Thread.sleep(1000);
		}
	}
}

To test the program, we need to run it at least twice: once while pressing down on the touch sensor (we should hear a 1 second long beep) and once without pressing on it (the program should exit without any extra sounds). Make sure to verify this.

Solution to 2. We assume a light sensor is hooked up to port 2 of the brick. We first need to decide what "black" and "white" mean. Recall that the light sensor includes the xxx operation, which reports the measured light intensity from xxx to 900. A reasonable definition, therefore, might be to define a light intensity of less than xxx as black, otherwise white. But that is exactly our conditional statement:

 if (light intensity is les than xxx)
    report black
otherwise
    report "white"

 

public class Conditional2Test
{
	public static void main(String args[])
	{
		LightSensor light = new LightSensor(SensorPort.S2, false);
	}
}

Solution to 3. We assume the ultrasound sensor is hooked up to port 3 of the brick

public class Conditional3Test
{
	public static void main(String args[])
	{
		UltrasonicSensor echo = new UltrasonicSensor(SensorPort.S3);
	}
}

Repeating Ourselves: Loops

 

table of content


Divide and Conquer: Defining and using Functions

details

 

table of content


Bert G. Wachsmuth
Last modified: 03/29/13