ENGR/CS 101 - LEGO Project

Fall 2001 - Basic C Using the Robotics Library

This handout basic programming constructs and how they are implemented using the C programming language syntax and semantics using the UE robotics library.

What is a program?

A program generally consists of three parts, constants, variables, and control statements. A constant is name whose associated value cannot be changed. Basically, anywhere the constant name is used in a program, its value is substituted. A variable is a named piece of memory that can be used to store some data. This data can be input by the user of the program or it can be the result of some sensor or it can be computed using other pieces of data. In most languages, we can also specify the type of constants and variable, which restricts the type of data that can be stored in the variable. Some common types include integer, floating point, character, and string.


Control statements tell the computer what to do with the data. There are several different kind of control statements:

Main program

The main program in Keil C for the robotics controller has the following syntax:

   /* included libraries go here */

   /* constant definitions go here */

   /* function prototypes go here */

   void main (void)
   {
       /* variable declarations go here */

       /* executable statements go here */
   }

   /* function definitions go here */
Sometimes we want to write notes to ourselves about what the program is doing. This can be done by using comments which in C begin with /* and end with */. Everything between these marks is ignored. So in the above example, we would replace the comments with the actual code that does those things.

Included libraries

If we had to write code for absolutely everything every time we wanted a new program, it would take a very long time to write a program and most of it would be the same as previous programs. Luckily, others have already written these parts and put them in a library. For example, the routines to read sensors and control motors and LEDs on the LEGO board are contained in a library. We tell the compiler we want to use this library by including it in our program. Since the name of the LEGO robotics library is robotic6a.h, we write at the beginning of our programs.

   #include "robotics6a.h"
Every LEGO project program must have this line at the beginning.

Constants

Sometimes we'd like to give a constant value a name, so we can remember why we used a particular value or to make it easy to change the value in a lot of places. This can be done using the #define syntax. For example,

   #define REVERSE_TIME 20
   #define TURN_TIME 10
will define the word REVERSE_TIME to have value 20 and the word TURN_TIME to have value 10. If we discovered that the turn time isn't long enough to turn the vehicle enough with the value 10, all we have to do is change the constant definition of TURN_TIME to something larger, like 20, instead of having to find all the places in the program that we used 10 and change them all. By convention, constant names are written in all uppercase letters with words separated by underscores.

Variables

Recall that a variable is a memory location where a piece of data can be stored and retrieved. Variables are declared (made known to the compiler) in C using the following syntax:

type namelist ;
The namelist is one or more names separated by commas. A name must start with a letter (either uppercase or lowercase), and can be any combination of letters, digits, or the underscore character ('_'). C is case sensitive, which means that it cares about the difference between uppercase and lowercase, so that the name sum is different than SUM or Sum. Keil C supports an number of different types, but for now we will only be using two of them: unsigned char which is used to hold characters and small positive integers, and int which is used to hold integers that may be either positive or negative. Here are some examples:

   unsigned char i, j, k;
   int sum;
Each type has some literal values. For the integers, it is what you'd expect. Examples are: 0 1 23 42 -16 -42. For characters, the character value is enclosed in single-quotes. For example, 'A' 'z' '3' '?'. Note that integer 3 is not the same as character '3'.


In addition, it is possible to declare a collection of characters to be used as a string. We will use them to represent messages to be written to a display. They are enclosed in double-quotes. For example, "/Forward", "/Bump Left". (The leading `/' character causes the display to clear before writing the string.) Note the the string "3" is not that same as the character '3'.

Assignment statements and expressions

To give a variable a value, you can assign a value to it by using the following syntax:

variable = expression;
The expression can be a literal value (e.g., 10), a variable name, a function call (more on that later), or a mathematical, relational, boolean, or bitwise expression involving literals, variables, function calls or other expressions. The operations supported as operators by C include:
Operation Operator Operation Operator
Unary Negation - Equality ==
Addition + Inequality !=
Subtraction - Less than <
Multiplication * Less than or equal <=
Division / Greater than >
Integer remainder % Greater than or equal >=
Operation Operator Operation Operator
Logical negation (NOT) ! Bitwise XOR ^
Logical conjunction (AND) && Bitwise AND &
Logical disjunction (OR) || Bitwise OR |
The logical operators compute a result of 1 (for true) or 0 (for false). The mathematical operations have usual precedence and associativity, and are of higher precedence than the relational and logical operators. Some examples of assignment statements and expressions:

   var1 = 1;
   var2 = var1 + 5;
   var3 = var1 < var2;
   var4 = var1 + var2 - var3;
   ch1 = 'A';

Input/Output

Because the LEGO controller board does not have a keyboard or a monitor, we cannot use built-in C language constructs for input and output. Instead, the controller board allows a program to read a sensor or write to the display or output port (that can be used to drive an LED, for example) or make the buzzer sound.


We have two types of sensors, switches and an A to D converter (that can be used to read light sensors). There are two functions to read input ports and one function for reading the A to D converter. They are used as follows:

Function Specification
ReadInputPort(num) num is 1-8 or 17-24 corresponding to the outside 8 pins on each side; returns 1 if switch closed, 0 if open
ReadP1(num) num is 1 or 2 corresponding to bits 1 or 2 of Port 1; returns 1 if switch closed, 0 if open
GetAtoD(chNum) chNum is channel number, returns value
Some examples:

   right = ReadInputPort(1);
   left = ReadInputPort(24);
   channel0 = GetAtoD(0);


The display can be written using one of three functions:

Function Specification
WriteString(s) Writes string s starting at current position. Wraps if too long for display.
WriteChar(c) Writes a character c at the current position.
WriteInt(n) Writes integer n hexadecimal format (2 characters) at the current position.
Writing the character '/', either singly or as part of a string, will cause the display to clear and current position set to 0.


To turn on and off an LED connected to an output port there is a function SetOutputPort (num, dir). The argument num is the number of the output port you want use (9-16, corresponding to the middle 8 pins) and dir is either 0 (for pull low) or 1 (for pull high). The library defines constants LOW and HIGH for use with this routine.


The buzzer can be turned on and off using the Sound(state) or Beep(num) functions. The Sound function turns the buzzer on if argument state is 1 and off if the argument is 0. The robotics library defines constants ON and OFF for use with this routine. Beep oscillates the buzzer for num cycles. Some examples:


   /* Write a message to display; clear first */
   WriteString ("/Forward ");
   /* Write an integer after the message */
   WriteInt (left);
   /* Turn on LED 1 */
   SetOutputPort (9, LOW);
   /* Turn off LED 1 */
   SetOutputPort (9, HIGH);
   /* Beep 5 times */
   Beep(5);

Selection statements

Selection statements are for choosing between different sets of statements to be run. The if-statement in C has the following syntax:
if ( condition )
{  
  statements for true condition
}  
else  
{  
  statements for false condition
}  
The condition can be any expression. The semantics of this statement is if the condition is true (a value that is not 0), then the statements for true condition are run and the statements for false condition are ignored. If the condition is false (a 0 value), then the opposite happens and the statements for the true condition are ignored and the statements for false condition are run. In both cases, after the appropriate statement is run, the next statement after the if-statement is run. The else portion is optional. If it is missing, nothing is run when the condition is false. Since the if-statement is just another statement, it can nested inside another if-statement. For example,

   if (ReadInputPort(1))  /* Right switched closed */
   {
      SetOutputPort(9, LOW);  /* Turn on LED 1 */
   }
   else
   {
      if (ReadInputPort(24))  /* Left switched closed */
      {
         SetOutputPort(16, LOW);  /* Turn on LED 8 */
      }
      else  /* Neither switch has closed */
      {
         InitializeIO();  /* Turn off all LEDs */
      }
   }
will light the appropriate LED depending on whether either of the bump switches has been closed.

Repetition

Repetition statements are used to repeat a set of statements. There are several kinds of repetition statements in C, but we'll only use two in this project. The first is a for-statement that repeats for a certain number of times by counting a range. The syntax for this is:
for ( variable = initial value ; variable <= final value ; variable = variable + step value )
{  
  statements to be repeated
}  
The variable starts out with the initial value and then it is compared with the final value. If it has not reached the final value, the statements to be repeated are run, then the variable is incremented by the step value. The comparison is done again, etc. When the variable becomes greater than the final value, the repetition stops, and the statement after the for-statement is run. For example,

   for (i = 1; i <= 10; i = i + 1)
   {
      if (ReadInputPort(1))  /* Right switched closed */
      {
         SetOutputPort(9, LOW);  /* Turn on LED 1 */
      }
      else
      {
         if (BumpLeft())  /* Left switched closed */
         {
            SetOutputPort(16, LOW);  /* Turn on LED 8 */
         }
         else  /* Neither switch has closed */
         {
            InitializeIO();  /* Turn off all LEDs */
         }
      }
   }
will check the bump sensors 10 times.


The other repetition statement is the while-statement. Its syntax is:

while ( condition )
{  
  statements to be repeated
}  
In this repetition statement, the condition is checked to see if it is true or false (just as for the if-statement). If it is true, the statements to be repeated are run. If it is false, the repetition stops and the next statement after the while-statement is run. For example, the while statement can be used to do the same thing as the preceding for-loop by explicitly counting:

   count = 1;
   while (count <= 10) /* repeat until count become greater than 10 */
   {
      if (ReadInputPort(1))  /* Right switched closed */
      {
         SetOutputPort(9, LOW);  /* Turn on LED 1 */
      }
      else
      {
         if (ReadInputPort(24))  /* Left switched closed */
         {
            SetOutputPort(16, LOW);  /* Turn on LED 8 */
         }
         else  /* Neither switch has closed */
         {
            InitializeIO();  /* Turn off all LEDs */
         }
      }
      count++;  /* Increment count */
   }
For the LEGO car, usually we want the program to just repeat continuously, so we write:

   while (1)  /* Repeat continuously, since the condition is always true */
   {
      /* statements to be repeated */
   }

Functions

A function is a subprogram. When a function is used, we say it is called, and the subprogram that uses the function the caller. When a function is called, the caller can pass data to it called the arguments, and the program jumps to the function code and executes it. When the function is finished, it can return a result, and the program jumps back to where the function was called and resumes executing.


A function call is just the name of the function and a comma-separated list of arguments you want to pass to the function. The syntax is:

function-name ( argument-list ) ;
An argument can be a constant, an expression, or a variable. If the function returns a result, you need to assign it to a variable to save it.


The robotics library routines are all functions. For example, when you write something like


    SetOutputPort(9, LOW);  /* Turn on LED 1 */
you are telling the program to go to the subprogram called SetOutputPort giving it the values 9 and LOW. The subprogram then does whatever it is suppose to do, in this case set output port 9 to LOW. The sensor reading functions like GetAtoD return values that you can use to determine the state of the sensor.


To define a function, there are two parts. First, you need to tell the program the name, the type of the returned result, and the types of the data that can be passed to it. (The names of the data can be given, too, but this is optional.) This is done in a function prototype, that is given before the main program. The syntax of a prototype is:

return-type name ( parameter-list ) ;
Where parameter-list is a comma-separated list of types (and names). For example, the file robotics6a.h contain all the prototypes for the functions you've been using. The one to get a value from the A to D converter is:

   unsigned char GetAtoD (unsigned char channel);
Which says that the function GetAtoD is passed an unsigned char as an argument and returns an unsigned char as a result.


Sometimes a function doesn't have a result to return, for example, the function to write a character on the display. Its prototype is:


   void WriteChar (unsigned char ch);
Since it doesn't need to return a result, its return type is set to void.


Once a prototype is written, the main program and any other functions can call the prototyped function. But, of course, we also need to write the actual subprogram to make it work. These are called function definitions and are usually placed after the main program (or in a separate library). Each function has its own definition. The syntax for the function definition is:

return-type name ( parameter-list )
{  
  variable-declarations
  executable-statements
}  
The first line looks just like the prototype (with parameter names) except it doesn't have a semicolon at the end. The rest looks just like the main program. If the the function should return a result, at least one of the executable statements will be a return statement which has the syntax:
return expression ;
And causes the value of the expression to be sent back to the caller as the result of the function call.


In robotics6a.c we can see the function definitions of the functions in the robotics library. For example, the A to D reading function is:


sbit notDone = 0xB5;  /* Port 3 Bit 5 */
unsigned char GetAtoD (unsigned char channel)
{
   /* OR 8 with channel for single ended entry */
   XBYTE[ATOD_IDX] = (channel | 8); 

   /* Delay to allow conversion to finish       */
   /* Interrupt sets P3.5 to 0 when finished */
   notDone = 1;
   while (notDone);  /* just waiting for change in state */
   
   /* Get the data and return it */
   return XBYTE[ATOD_IDX];
}  /* end GetAtoD */


There are a few other functions of interest in the robotics library. There are three initialization routines for the motors, display, and output ports. They are called InitializeMotors(), InitializeDisplay(), and InitializeIO(), respectively. There are two functions related to controlling the motors. AllStop() causes all motors to stop. SetMotor(motorID, speed) will set the motorID motor based on the speed, which should be an integer between -255 and 255. A negative speed will make the motor go ``left'' or ``reverse'', while a positive speed will make the motor go ``right'' or ``forward''. (Which actual direction these are depends on how the motors were wired to the controller.) A speed of 0 will cause the motor to stop. Finally, there is Delay(count), which causes the processor to ``busy wait'' for approximately 200 microseconds for each count.


You can write your own functions by putting the prototypes before the main program and the function definitions after the main program as was shown at the beginning of this handout. For example, after you build your vehicle and you know which way the motors turn, you could write a function Forward() that turned on the motors so that your vehicle goes in the forward direction.



Converted using latex2html on Tue Sep 25 13:53:22 CDT 2001