C Handout 7 – Loops, #define and arrays

(10/24/00)

 

Loops

Often you will want to execute a section of code more than once.  The mechanism for doing this is called a loop.  We will discuss only tow of the loops available in C: the while loop and the for loop.

 

while loops                                                                          

The while loop is used to execute a sequence of operations more than once.  The syntax is:

while (logical-expression) C-statement-block

 

In this construct the C-statement-block gets repeatedly executed as long as the logical expression is TRUE.  You’ve already seen this construct in action.  The program you wrote last week had the form:

          void main() {

while (1) {

.

.  /* the bulk of your code would go here */

.

              }

          }

In this example the code following the while loop gets executed forever, since the logical expression, (1), always evaluates to TRUE.

 

If we wanted the program to stop when you hit the stop button we could use the following:

          void main() {

while (!stop_button()) {

.

.  /* the bulk of your code would go here */

.

              }

          }

 

Recall that stop_button() returns TRUE if the stop button is pressed, and FALSE if not.  So this code will loop, as long as the stop button is not pressed.

 

 

for  loops                                                                             

There is another loop construction that is quite useful for cycling through a series of values:

for (initialization; logical-expression; action)

C-statement-block

 

This construct is a bit more complicated, and is probably best expressed with an example.  Assume an integer variable, speed, has been declared elsewhere in the program, along with the function SetSpeed(). 

          /*Accelerate slowly (so wheels don’t spin*/

for (Speed=0; Speed<=100; Speed=Speed+10;) {

     SetSpeed(Speed, Speed);

     sleep(0.1);

          }

 

The for loop is really just a shorthand for the while loop.  The code above is equivalent to this:

          /*Accelerate slowly (so wheels don’t spin*/

Speed=0;                        /* initialization*/

while (Speed<=100){             /* logical expression*/

     SetSpeed(Speed, Speed);

     sleep(0.1);

     Speed=Speed+10;            /* action*/

          }

 

Note that the initialization statement comes before the loop, the logical expression is checked by the while loop, and the action is taken at the end of the loop.



The C pre-processor (#define)

Any program can be difficult to read.  To make C code a little easier to read, you can use the pre-processor to read you can use the C pre-processor to define constants to make your code easier to read.  The pre-processor directives (i.e., #define’s) must go at the top of your file.

 

For example if we put the following lines at the top of our file.

#define   FALSE         0

     #define   TRUE          !FALSE

     #define   LEFTMOTOR     0

     #define   RIGHTMOTOR    1

     #define   FWD_FULL      100

     #define   RVS_FULL      -100

     #define   STOP_MOTOR    0

     #define   LEFT_BUMPER   !digital(7)

     #define   RIGHT_BUMPER  !digital(8)

 

When the C compiler comes across the defined term, it replaces it with the expression on the left.  The defined term does not need to be in all caps, but this is a convention to distinguish these constants from variables.  This allows us to write more easily understood code.  This is probably best seen with some code snippets.

 

To control the motor, we could use command such as:

     motor(LEFTMOTOR,FWD_FULL);   /*Set left motor to max spedd*/

     motor(RIGHTMOTOR,STOP_MOTOR); /*Stop the right motor*/

 

If we use the previous example with the while loop, we could write:

     void main() {

while (stop_button()==FALSE) {

.

.  /* the bulk of your code went here */

.        

     }

     }

 

Important:  #define’s are not variables, they cannot be changed.

The following code would produce an error.                                      

     FWD_FULL=95; /*Error, #defined quantities cannot be changed!!!*/

 

 

Advanced Directives:  Feel free to skip this section if you want to:

Pre-processor directives can have arguments (this example is from the interactive C help page).

#define RIGHT_MOTOR 0

#define LEFT_MOTOR  1

#define GO_RIGHT(power) (motor(RIGHT_MOTOR,(power)))

#define GO_LEFT(power)  (motor(LEFT_MOTOR,(power)))

#define GO(left,right) {GO_LEFT(left); GO_RIGHT(right);}

void main()

{

   GO(0,0);

}



Arrays

Often it is convenient to be able to have multiple data stored, without using a separate variable for each.  The structure used to implement this in C is called an array.

 

For example, you might want to store the readings from the four photo-cells on your robot in a single entity.  You could do this with an array.

 

void main() {

int i;        /*loop counter*/

int cds[4];   /*Array declaration*/

 

/*Sample all 4 CdS cells*/

for (i=0;i<4;i++) {

cds[i]=analog(i);

}

 

printf(“\nCds Cell 3 reads %d.”,cds[3]);

     }

 

The array has 4 elements, c[0], c[1], c[2], and c[3].  Note that the array index starts at 0, and so only goes to N-1, where N is the length of the array. 

 

Initializing arrays.

Often you will want to initialize an array to specific values.  As an example, we might want to define 4 threshold values for the light sensors, to distinguish light from dark.  We could define an array, and then use 4 separate lines to assign values, or we could do it with a single line:

int threshold[]={120, 131, 119, 110};

This code  fragment define 4 threshold values for the light sensors, to distinguish light from dark, one for each of the four sensors.    You could compare these 4 values against the four readings to determine which is sensing light vs dark.

 

 

A short example that the concepts introduced.

 

#define FALSE 0

#define TRUE  !FALSE

/*

     Use #define to define constants. 

     #define's are implemented as a simple text substitution.

     These need two go at the top of the file

/*

 

 

/*

     Define an integer globally.

     done is a flag to tell main program when to end.

*/

int done;

 

 

 

/*

     This function will be used as a separate process.

     It plays 3 notes, defined in the array c[].

*/

void PlayNotes() {

int i;                     /*loop counter*/

     float MyNotes[]={250.0, 250.0, 100.0};

 

     for(i=0;i<3;i++) {         /*loop trough notes*/

          tone(MyNotes[i],0.5);  /* Play note*/

          sleep(0.1);            /* delay*/

     }

     done=stop_button(); /*Done will be TRUE if stop is pressed.*/

}

    

 

 

void main() {

     int i=0;           /*Declare a variable and initialize it.*/

    

       done=FALSE;        /*This is a global variable*/

 

     while (!done) {

     PlayNotes();

     sleep(1.0);

     i++;

}

 

printf(“\nPlayed notes %d times.”,i);

}   

 

Note that in this example, the main routine stops running while the notes are being played.  There is an example later in this handout where the main routine continues to run while the music plays.



Review Sheet – Loops, #define and arrays

(Use both sides – problems 1 through 4)

Due 10/26 when you come to lab.

 

Name:______________________________________

You may work in groups, but each person should turn in a sheet.

 

1) What is the sequence of numbers printed by the following?

#define MAXN  10

 

void main() {

int i;

 

     for (i=0; i<MAXN; i+=2) {

          while (i==6) i--;

          printf(“\n %d”,i);

     }

}

  

 

2) Write a snippet of code that performs the following.  Given the array x[], compute a new array y[] that is the average of two consecutive values of x[].  Note that there is one less element in y[] than in x[].  Start with

void main() {

     float x[]={1.0, 2.5, 3.0, 2.0, 2.5};

     float y[4];

 

     /*add your code here*/

 

 

}

 

 

3) Write code that performs the following.  Given a function represented by the dotted line, the array x[] and the array t[]represent sampled values of the function (i.e., the pairs (t[],x[]) represent the coordinates shown as “x” on the plot). 

Compute and print the approximate value of the integral as given by the sum of the areas of the rectangels show, between t=0 and t=5.  Start with

void main() {

     float x[]={1.0, 2.5, 3.0, 2.0, 3.5};

     float t[]={0.0, 1.0, 2.0, 4.0, 5.0};

 

     /*add your code here*/

 

 

}

 

 

4) For the CdS sensors on the robot, a lower reading represents more light fallin on the sensors.  Assume the sensors are all identical (not a good assumption in practice).  Write a snippet of code that speeds up the left motor (0), and slows down the right motor (1) as long as more light falls on sensor 2 (left inside sensor) than on sensor 3 (right inside sensor).

 

 Multi-tasking (Feel free to skip this subject if you want to).

 

The version of C that we are using allows you to make the processor perform more than one operation at a time.  This is referred to as multi-tasking.  To start a process that will run at the same time as your main program, use the function “start_process()”.  After this routine is executed, the operating system will run each concurrent process at the same time.  In actuality, each process is run for a short time before being pre-empted by the next process .  This pre-emption process happens many times per second -- and it appears to you, the programmer, as if the processes are running at the same time, in parallel.

 

An example is given on the next page, in which a process is started to play music, while the main program continues to do other things.  This is in contrast to the previous example, in which the main program suspends execution while the music plays.

 

If you want more information on this multi-tasking, either consult the “help” that comes with Interactive C, or talk to me.

 

The following overview is taken from the Interactive C help manual and describes the process in more detail:

Overview

 

One of the most powerful features of IC is its multi-tasking facility.  Processes can be created and destroyed dynamically during run-time.

 

Any C function can be spawned as a separate task.  Multiple tasks running the same code, but with their own local variables, can be created.

 

Processes communicate through global variables: one process can set a global to some value, and another process can read the value of that global.

 

Each time a process runs, it executes for a certain number of ticks, defined in milliseconds.  This value is determined for each process at the time it is created.  The default number of ticks is five; therefore, a default process will run for 5 milliseconds until its "turn" ends and the next process is run.  All processes are kept track of in a process table; each time through the table, each process runs once (for an amount of time equal to its number of ticks).

 

Each process has its own program stack. The stack is used to pass arguments for function calls, store local variables, and store return addresses from function calls.  The size of this stack is defined at the time a process is created.  The default size of a process stack is 256 bytes.

 

Processes that make extensive use of recursion or use large local arrays will probably require a stack size larger than the default.  Each function call requires two stack bytes (for the return address) plus the number of argument bytes; if the function that is called creates local variables, then they also use up stack space.  In addition, C expressions create intermediate values that are stored on the stack.

 

It is up to the programmer to determine if a particular process requires a stack size larger than the default.  A process may also be created with a stack size smaller than the default, in order to save stack memory space, if it is known that the process will not require the full default amount.

 

When a process is created, it is assigned a unique process identification number or pid.  This number can be used to kill a process.



A short example that uses all of the concepts introduced.

 

#define FALSE 0

#define TRUE  !FALSE

/*

     Use #define to define constants. 

     #define's are implemented as a simple text substitution.

     These need two go at the top of the file

/*

 

 

/*

     Define two integers globally.

     i is a loop counter.

     done is a flag to tell main program when to end.

*/

int i;

int done;

 

 

 

/*

     This function will be used as a separate process.

     It plays 3 notes, defined in the array c[];

*/

void PlayNotes() {

     float c[]={250.0, 250.0, 100.0};

     for(i=0;i<3;i++) { /*loop trough notes*/

          tone(c[i],0.5);    /* Play note*/

          sleep(0.1);        /* delay*/

     }

     done=TRUE;              /*Signal main process that we are done.*/

}

    

 

 

void main() {

     done=FALSE;                  /*We are NOT done (set it FALSE)*/

     start_process(PlayNotes());  /*Start the note-playing process*/

     while (!done) {              /*Loop while done is false*/

          printf("\nPlaying note #%d.",i);  /*Print info about note*/

          sleep(0.1);

     }

     printf("\nDone!");

}   

 

 

In this example, the main process keeps running while the music is playing.  In the previous example, the main process was not running while the music was playing.