EE/CS: Basic C Programming Using the Robotics Library
Summer 2002
This handout explains basic computer science and engineering concepts, and explains basic programming constructs and how they are implemented using the C programming language syntax and semantics with the UE robotics library.
Computer systems are generally divided into two parts: hardware and software. Hardware is what most people think of as a computer. It is the physical machine broken down into its components. These include the central processing unit (CPU), which is the brains of the computer; main memory (also called RAM), which is where programs and data reside when a program is running; storage devices (also called secondary memory) such as a floppy disk drive, a hard disk drive, or a CD-ROM drive that provide permanent storage for programs and data; input devices such as a mouse, a keyboard, or a light sensor; and output devices such as a monitor, a printer, or an LED.
Software is what makes the computer useful to a human user.
Normally, a user interacts with a computer through a piece of software
called the operating system. This is the software that allows
a user to run a program, manage their files, etc. Common operating
systems include DOS, Windows, MacOS, Linux and Unix. Each
program (and the operating system) is a set of instructions for
a computer to follow. When we run a program, the program along
with some data from the user is loaded into the computer's main
memory, then we say the computer executes the program on the
data. This usually results in some output showing the results.
A program is a set of instructions. These instructions are written in a programming language. Like all languages, each programming language has a syntax, the rules that say whether a sentence is a legal construction, and a semantics, the meaning of the sentences in the language. Normally, most programs are written in a high-level language. A high-level language is more like English. Some common ones are BASIC, Java, C, C++, and Scheme. These languages have ``instructions'' that are more complicated than a single CPU machine instruction and each high-level instruction needs to be translated into an equivalent series of CPU machine instructions.
Programming language instructions are stored at text in a
source file and then used as input data to a program called a
compiler. The compiler translates the source program into the
CPU instructions and stores these into an object file. Many
times a program will consist of more than one object file, so then a
program called a linker will take these object files as input
data and produce an executable file, which is what we usually
mean when we talk about program, a file that is executable. When we
run a program, the executable file is loaded into the
computer's main memory and its instructions are used to make the
computer perform the specified task. Generally, the operating system
is responsible for loading a program, though the LEGO robotics project
uses a special purpose loader.
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. The rest of
the sections describe what this code looks like.
Note that this an all other code examples are indented to show
the structure of the program constructs. While indenting is not
required, it makes the program much easier to read. Most companies
that write software require that programmers follow a particular
indenting scheme.
#include "robotics6b.h"Every LEGO project program must have this line at the beginning.
#define LIGHT_PULSE 10 #define TURN_TIME 10will define the word LIGHT_PULSE 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 a 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'.
To give a variable a value in C, 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:
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 Unary Negation - Equality == Addition + Inequality != Subtraction - Less than < Multiplication * Less than or equal <= Division / Greater than > Integer remainder % Greater than or equal >= 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);
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:
/* 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 body is 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 body is 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.
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.
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 robotics6b.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 does not have a result to return, for example,
the function to control the motors. Its prototype is:
void SetMotor(unsigned char motorID, int speed);Since it does not 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 does not 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 robotics6b.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 */
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.