Aim
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 #define COMMAND_FORWARD 1 #define COMMAND_TURN_LEFT 2 #define COMMAND_TURN_RIGHT 3
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.