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.
Control statements tell the computer what to do with the data.
There are several different kind of control statements:
/* 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.
#include "robotics6a.h"Every LEGO project program must have this line at the beginning.
#define REVERSE_TIME 20 #define TURN_TIME 10will 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.
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'.
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 >=
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:
Operation Operator Operation Operator Logical negation (NOT) ! Bitwise XOR ^ Logical conjunction (AND) && Bitwise AND & Logical disjunction (OR) || Bitwise OR |
var1 = 1; var2 = var1 + 5; var3 = var1 < var2; var4 = var1 + var2 - var3; ch1 = 'A';
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:
Some examples:
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
right = ReadInputPort(1); left = ReadInputPort(24); channel0 = GetAtoD(0);
The display can be written using one of three functions:
Writing the character
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.
'/', 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);
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 ( condition ) {statements for true condition }else {statements for false condition }
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.
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 ( variable = initial value ; variable <= final value ; variable = variable + step value ) {statements to be repeated }
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:
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:
while ( condition ) {statements to be repeated }
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 */
}
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:
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-type name ( parameter-list ) {variable-declarations executable-statements }
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.