CSC 161 Grinnell College Spring, 2015
 
Imperative Problem Solving and Data Structures
 
 

Laboratory Exercise: Conditionals

Goals

The purpose of this lab is to introduce different types of conditional statements in C, practice simple control flow, and gain experience using the Scribbler 2 sensors.

Preparation before Class

Introduction

Programs that don't make decisions are of limited usefulness. Sophisticated programs are able to make decisions based on the results of various tests and act according to the input data or current state of execution. To perform these tests and manage the flow of execution, C provides conditional statements:

When using the Scribbler 2 robots, activities may depend upon the immediate environment of the robot. To learn about this environment, Scribbler 2 robots contain several sensors. This lab utilizes four types of sensors to provide experience with conditional statements.

Be sure to review the course's description of Sensors for the Scribbler 2 Robot before proceeding in this lab. As you will see, some sensors are located on the robot itself, and some are located on the Fluke that plugs into the robot. A common error for beginners is to program based on sensors located in a different place than expected.

When working with the Scribbler robots, conditionals are how the robot can be programmed to make decisions based on data gathered from its sensors.

Quick Review of the Syntax for if Statements and Variations

The statements if and if else share the same foundation. The following notes are based on comments by David Cowden.

The if Statement

The syntax of a standard if statement is as follows:

  if (TEST)
     result();

The result will only execute if the TEST evaluates to true. In C, true means anything that is not 0. Thus, a statement:

  if ( 1 )
     result();

will always execute.

Note: An if will only determine the execution of the statement directly following it. To group multiple statements together into one, curly brackets: { and } are used. For example, consider the code segment:

  if (TEST)
     {
        result1();
        result2();
     }

Here, both result1() and result2() will be executed if TEST is true, and neither of these statements will be executed if TEST is false. In contrast, consider the following code: segment

  if (TEST)
     result1();
     result2();

Here, only result1() is considered as being part of the if (indenting notwithstanding). Thus, if TEST is true, then result1() and result2() are executed. However, if TEST is false, then only result1() is skipped. Since result2() is outside the if statement, result2() will be executed whether or not TEST is true.

Using Sensors

As electrical instruments, all sensors are subject to experimental error. That is, multiple queries to a sensor may yield different results. For some sensors, this variability may be modest, but for other sensors, successive queries may produce substantially different results.

To manage some variability, the MyroC package takes several readings and averages. In particular, most sensor functions include a second parameter that specifies how many values from the sensor should be averaged. For example, the following call takes 3 readings and returns their average:

  int avgValue = rGetLightTxt("left", 3);

Of course, with this syntax, getting a single reading corresponds to giving the second parameter the value 1.

Work Started in Class

Using Light Sensors

According to the course documentation for Sensors for the Scribbler 2 Robot, the light sensors on the front of the Scribbler 2 robots are reasonably consistent, so we start there. Note that the light sensors return values near 0 for bright lights and large values (about 65,000) when the area is quite dark.

  1. Copy the program light-sensor-example.c to your account. Note the coe contains instructions regarding how to compile.

    Compile the program and run it several times to determine how it works.

    • For some runs, do not move the robots, and examine the output to determine what variability is present in the readings printed.
    • For some runs, move the robot so it is facing a relatively bright light or a relatively dim area. Again, compare the results obtained.
  2. Add statements to determine the light levels in front of the robot and on the right.

    • To what extent do the light levels seem to vary from the left to the center and to the right?
    • How might you change the robot's environment (e.g., cover some sensors, or create a shadow over part of the robot) to impact the light readings?

Using the Obstacle Sensors

One set of obstacle sensors utilize the IR sensors on the Scribbler 2 body, and another set of obstacle sensors utilize sensors on the fluke. Again, review Sensors for the Scribbler 2 Robot for details.

  1. Use the IR sensors on the Scribbler 2 body to determine whether there is an obstacle in front of the robot. This can be done using the the rGetIRTxt function. Unlike the light sensors, the function returns only a one (true), or zero (false) depending on whether there is an obstacle close to the scribbler's IR sensors.

    As with the light-sensor-example.c program, report several readings, and comment upon the variability that you observe.

  2. Change the robot's behavior, depending upon whether or not an obstacle is present. If an obstacle is present, the program should beep three times at a high pitch. If there are not obstacles in front of any of the sensors, it should move forward for 1 second.

Avoiding Obstacles

Testing one sensor is great, but the robot has two IR sensors. It would be more interesting if we tested both the sensors and acted according to the resulting values. To do this you can employ an else if test.

  1. Rather than beeping, consider telling the robot to turn if it senses an obstacle in front of one of its sensors. If there is an obstacle in front of the left sensor, turn right, and if there is an obstacle in front of the right sensor, turn left. This is a simple, but effective, way to avoid an obstacle.

    Change your code so the robot behaves as described. One way to proceed would be to use the following form:

      if ( left sensor )
          turn right;
      else if ( right sensor )
          turn left;
    

    Compile, run, and verify your code works before moving on.

    For future references (i.e., for the Homework section of the lab), test what happens in the case where there is an obstacle in front of both sensors.

Another Approach for Conditionals: the Switch Statement

Again, initial notes are based on comments by David Cowden.

Sometimes you need to test a condition and perform one of many results based on the value of a variable. Rather than stringing a bunch of else ifs together, there is a switch statement in C.

switch operates a little differently than if. Instead of evaluating a Boolean expression, switch operates on an integer (or char) value as follows:

    switch (val) {
    case 17: do_this_if_val=17();
        break;
    case 21: do_that_if_val=21();
        break;
    case 43: do_something_else_if_val=43();
        break;
    default: no_case_action();
        break;
    }

do_that(); happens if val == 21. The default: is executed if no case is met. * Note: although a break after the default: is not necessary, it is common practice to add it. Further, execution continues until a break; statement is reached. Leaving the break; out of a case causes the default to be executed every time that case is also executed.

  1. Program light-sensor-switch.c is modified from the earlier light-sensing programs. In the original program, categories of light intensities were identified through nested if ... else if statements. Since the categories were organized in 10,000 groupings of light intensities, division of the light intensity by 10000 provides a simple way to identify the relevant category. This gives rise to the use of a switch statement.

    Compile and run light-sensor-switch.c under different light conditions. Then review the code to determine how they work.

  2. Remove the break statements from the program, and run them again. Explain in your own words what purpose the break statements address.

Homework

Testing Two Things at Once

As with earlier commentary in this lab, these notes are based on comments by David Cowden.

Consider the where there is an obstacle in front of both sensors. Using the previous code, first it would check if there is an obstacle in front of the left sensor. Since there is one, it would turn right. Then it would go on to whatever comes next, which could be going forward. However, the left sensor could still have the same obstacle in front of it if the robot didn't turn enough.

How could we make this code work better?

    if ( left sensor && !right sensor)
        turn right;
    else if ( !left sensor && right sensor)
        turn left;
    else if ( left sensor && right sensor )
        turn 180 degrees;
    else 
        go forward;

This checks for any situation that might occur and tells the robot what to do in each one.

The problem becomes even more prevalent if the data from the sensors was gathered beforehand (as they should be), and then the tests were executed:

   
    int left  = rGetIRTxt ("left", 3);
    int right = rGetIRTxt ("right", 3);
    if ( left )
        turn right;
    else if ( right )
        turn left;

In this case, if both were true, the robot would just turn right, then turn left, and end back where it started.

It is very important that you consider when exactly you are asking for sensor data from the robot. The above example illustrates the generally correct method of gathering data before examining it. To illustrate the importance, consider the following code:

    if ( rGetIRTxt("left", 3) && other_condition1 )
        result1();
    else if ( rGetIRTxt("left", 3) && other_condition2 )
        result2();
    else if ( rGetIRTxt("left", 3) && other_condition3 )
        result3();
    else if ( rGetIRTxt("left", 3x) && other_condition4 )
        result4();

By placing the call to the function rGetIRTxt in the if statement, you are actually gathering the data four [different] times. Calling the function many times is less efficient than calling it once, giving the value to a variable, and just using that variable. Suppose the robot is moving beforehand. When the program gets to the first test, rGetIRTxt fails and so execution moves on to the first else. But, by now the robot has moved in range of an obstacle. The value for rGetIRTxt would then be different when the robot is queried again -- causing the first else if to pass based on the altered value of the IR sensor. If you examine the logic in the case that all other_conditions are true, the program never should have gotten to the first else if if the value of rGetIRTxt was initially false .

To avoid the situation altogether, store the value from one call to rGetIRTxt first. Then perform all the tests on that variable.

The && and || Operators

To combine two tests, use the && (AND), and || (OR) operators. If you want certain code to execute only if both tests are true, use the AND operator. If your want to do something if either test is true use the OR operator.

  1. Copy the program combine-tests.c to your working directory and open it in an editor. This program is similar to parts of what you may have done in Steps 3 and 4 of this lab.

    1. Modify the program, so that the robot moves backwards if both sensors are blocked. That is,

      • if both sensors are blocked, the robot should move backward
      • if only the left sensor is blocked, the robot should turn right
      • if only the right sensor is blocked, the robot should turn left
    2. As a further variation, insert the following code within your program.

          if ( left || right )
            rBeep(1, 550);
      

      What happens when you put an obstacle in front of any of the sensors?

Switch to avoid obstacles

Steps 3 and 4 used the IR sensors to infer whether or not an obstacle was located in front of the Scribbler 2 body. In this Step, you are asked to use the obstacle sensors on the Fluke that plugs into the Scribbler 2. Values returned from the rGetObstacleTxt function range from 0 to 6000.

  1. Write a program that behaves in different ways, according to the data obtained from the rGetObstacleTxt function.

    1. Query the robot for the value of its obstacle sensors (these are the IR sensors located on the fluke dongle)
    2. convert the result to a range from 0 to 5 (or 1 to 6).
    3. use a switch statement that instructs the robot to perform one of 6 actions based on the range of values from rGetObstacleTxt.

    Some ideas might include moving forward for longer amounts of time the farther away the robot is from an obstacle, or beeping longer or multiply times if the robot is closer to an object.

    Hint: Remember integer division truncates any decimal in C.

Feedback Welcome

Development of laboratory exercises is an iterative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.