Design and build a mobile robot to act as follows:

  • Randomly "cruise" around
  • Turn towards and approach a source of bright light (torch)
  • Stop if the source of bright light is above a predetermined threshold, not to bump into it
  • Detect a collision with an obstacle and "escape" by backing up and rotating through a suitable angle.

Design Stages

To fulfil this assignment, the design of the command code went through two phases. Initially, we drafted some simple code for fulfilling the most taxing part of the specification, which we thought was the light-following (using Braitenberg behaviour). However, to extend our code to perform the other actions we decided to use a more modular system.

General Design Notes

Due to the placing of the motors in our robot, we drive the robot forwards by setting the motors to reverse (OnRev(MOTOR)) and drive the robot in reverse by setting the motors forward (OnFwd(MOTOR)). We hope to address this problem in future designs.

Initial Design: Light Following

For our first design we wanted to use the values obtained from the light sensors to direct our robot to turn and move towards the strongest lightsource. Since we're using a differential drive robot, this could be achieved by using the light source values to either add to or suppress the power of one of the drive motors.

For the additive method, we would have to use the crossover method so that a high value on the left light sensor increases the power of the right motor and vice versa. However, this has the effect of the robot moving slowly when far away from the light (since little overall light level means little power to both motors) and then causing it to speed up as it gets closer to the lightsource.

However, for this assignment we want the robot to stop moving before it reaches the lightsource, so a suppressive behaviour is appropriate. Here, a high value on the left light sensor decreases the power to the left motor and vice versa. This has the effect that the robot moves the fastest when in darkness, and gradually slows down as it gets closer to the lightsource.


Second Design: Modular Tasks

Once the light following task was complete, we realised that adding the other assignment tasks would require adding lots of if statements, each directly controlling the two motors. To avoid this, we decided to rewrite the code in a style that would allow new tasks to be added easily and would separate the motor control from the decision-making.

We used a system where we initially defined the range of different actions that the robot could perform, and coding the control code for these actions in a separate motorControl function that reads a global variable containing the current action to be executed and then controls the motors directly to execute that action.

#define COMMAND_NONE -1

Next, we defined a set of tasks for fulfilling each of the different stages of our robots behaviour, namely the random movement, light following, stopping and collision response. Each of the tasks examine the sensors to determine the relevant command to fulfil their part of the robot's behaviour and store the command in separate global variables. If a task does not need an action to be performed (for instance, the collision response task when no collision has been detected), it simply sets its global variable to the COMMAND_NONE command.

// Global variables for storing the current command
// from each task
// Random movement command
// Follow light command
// Stop at light command
// Collision response (run away) command
// Sets the randomCommand to a random movement every second
// Sets the followCommand to follow a lightsource only
    // if the light level is above a certain threshold

Each of these tasks is continually running, updating their global command variables based on the current values of the sensors. To convert all of these different commands into one single command that the motorControl function can execute, we use another concurrent task called the arbiter, which decides which of the tasks that produced a command has precedence and copies its command to the global motorCommand variable.

task arbitrate() {
    // The list of issued commands is processed with
    // precedence given from top to bottom. Finally,
    // motorControl is called to execute the command.

Light Sensor Calibration

Since room light levels can change drastically, rather than using a preset value for the ambient level, we added a calibration stage to our command program's initialisation. This stage takes samples from both light sensors over two seconds and then takes the ambient threshold to be five levels above the maximum recorded value.


"Intelligent" Random Movement

After implementing the basic random movement algorithm specified above, we noticed the robot making a number of stupid decisions. Often, it would turn so often that it wouldn't move outside of a small area, and sometimes it would even turn in one direction and then immediately turn back on itself.

To get the robot to explore more, we set a bias on forward movement, so that seven out of nine times it would choose to move forward rather than turn. To avoid the immediate turn back effect, we had to add state variables to each of the motors to remember in which direction they are currently moving, then make sure that a turn cannot be in the opposite direction to the current movement. By adding functions for controlling all motor movement that update the motor state variables, this even disallows turning back after a turn initiated by a completely separate task, such as the collision response task.


Full Code Listing

 * Defines a set of commands that the robot will respond
 * to.
#define COMMAND_NONE -1
#define COMMAND_STOP 6
 * Defines nice names for each of the inputs and
 * outputs.
#define LIGHT OUT_B
 * Defines the light values at which the robot
 * will respond (the Attract Threshold is the amount
 * by which one light sensor must be greater than
 * the other to cause a turn in that direction, if
 * either sensor exceeds the Repel Threshold then the
 * robot is too close to the light and will stop).
#define FULL_POWER 9
#define HALF_POWER 3
 * Defines nice names for motor states
#define STATIONARY 0
#define FORWARD 1
#define REVERSE 2
 * Global variables to keep track of the ambient level
 * and the current state of the left and right motors.
 * Global variables to hold the current commands produced
 * by each of the tasks, as well as the Motor Command which
 * is the command being executed.
     * This task computes the command to be issued when
     * the robot is moving randomly, and stores it in
     * the randomCommand variable.
     * When deciding in which direction the robot should
     * turn, the current direction of each motor is checked
     * to make sure that the robot doesn't immediately turn
     * back on itself.
     * This task computes the command to be issued when
     * the robot is following a light, and stores it in
     * the followCommand variable.
     * This task computes the command to be issued when
     * the robot is too close to a light, and stores it in
     * the stopCommand variable.
     * A sound is played when the robot stops because it's
     * too close to the lightsource.
     * This task computes the command to be issued when
     * the robot hits an object, and stores it in
     * the runCommand variable.
     * A sound is played when it hits something, then
     * the robot moves in the direction opposite to
     * its current motion for a second before turning
     * left for a second.
     * This task takes all the commands produced by the
     * other tasks and finds the command of the highest
     * precedence that is not set to COMMAND_NONE. In
     * this case, running away after hitting an object
     * takes the highest precedence, followed by stopping
     * when too close to the light, following the light
     * and finally moving randomly. If all commands are
     * set to COMMAND_NONE, the robot waits.
 * Functions for controlling our motors and keeping
 * track of their state (Note that, due to our design,
 * a motor is set to Reverse to move Forward, and Forward
 * to go in Reverse).
 * Takes the Motor Command produced by the arbiter and
 * applies it by calling the motor functions.
 * Since we want to produce Braitenberg behaviour for the
 * light following, we cannot simply use the TURN_LEFT and
 * TURN_RIGHT commands to turn towards the light. Instead,
 * a FOLLOW_LIGHT command causes this function to be called
 * which varies the power to each motor based on the current
 * values of the light sensors.
 * A sound is played to alert the user that the robot has
 * detected a light source and it moving towards it.
     * To calculate the ambient threshold (the value
     * of light below which the robot will perform
     * random movement), we sample the light sensor
     * values over two seconds and take the maximum,
     * then add 5 to it to allow for subtle variations.
     * A sound it played once this configuration stage
     * is completed.