State machine programming
Line following is just one of the functionalities that the Renesas MCU Rally robot needs in order to complete a track as there are three other types of obstacles:
- 90 degree turns (marked by two white stripes across the track),
- left and right lane changes (marked by white stripes across the corresponding half of the track), and
- elevation changes (unmarked).
A structured way of solving these additional problems is to introduce state machine programming. The robot's behaviour can then be described as a set of states and transitions between them. For example, the robot starts in a FOLLOW_LINE state, in which it follows the line, but then switches to a MAKE_90_DEGREE_TURN state when it detects the white stripes that mark the turn.
Simple state machines in C
A rudimentary way of programming state machines in C is to use an enum
as a data structure and switch
statements. The following code introduces an enum
variable state
, and can be extended to an arbitrary number of named states.
enum states_t
{
FOLLOW_LINE,
MAKE_90_DEGREE_TURN
} state = FOLLOW_LINE;
Then, in the while
loop of the main program, the robot's behaviour in each of the states and the state transitions need to be specified.
switch (state)
{
case FOLLOW_LINE:
// write the line following code
// write the white stripe detection code
// if white stripes detected, state = MAKE_90_DEGREE_TURN
break;
case MAKE_90_DEGREE_TURN:
// follow line, but very slowly (for example)
// detect when the turn was made
// if turn made, state = FOLLOW_LINE
break;
}
Example program
The following example program uses the state machine programming principle and represents a good basis for controller development. The program works with the default MCUCar robot with 8 line sensors. Feel free to use it as a starting point. It should, however, be studied, modified, and improved.
#include "renesas_api.h"
#define UPRAMP 60.0
#define FAST 30.0
#define MEDIUM 20.0
#define SLOW 10.0
#define BRAKE -40.0
#define STRICT 2000.0
#define LOOSE 20.0
enum states_t
{
FOLLOW,
CORNER_IN,
CORNER_OUT,
RIGHT_CHANGE_DETECTED,
RIGHT_TURN,
LEFT_CHANGE_DETECTED,
LEFT_TURN,
RAMP_UP,
RAMP_DOWN
} state = FOLLOW;
double last_time = 0.0;
int left_change_pending = 0;
int right_change_pending = 0;
int detected_state = FOLLOW;
int main(int argc, char **argv)
{
wb_robot_init(); // this call is required for WeBots initialisation
init(); // initialises the renesas MCU controller
while (wb_robot_step(TIME_STEP) != -1)
{
update();
unsigned short *sensor = line_sensor();
double *angles = imu();
float line = 0, sum = 0, weighted_sum = 0;
bool double_line = 0;
bool left_change = 0;
bool right_change = 0;
for (int i = 0; i < 8; i++)
{
weighted_sum += sensor[i] * i;
sum += sensor[i];
if (sensor[i] < 500)
{
double_line++;
if (i < 4)
{
left_change++;
}
else
{
right_change++;
}
}
}
line = weighted_sum / sum - 3.5;
switch (state)
{
case FOLLOW:
motor(FAST, FAST, FAST, FAST);
handle(STRICT * line);
if (angles[1] > 0.1)
{
last_time = time();
state = RAMP_UP;
printf("RAMP UP\n");
}
else if (angles[1] < -0.1)
{
last_time = time();
state = RAMP_DOWN;
printf("RAMP DOWN\n");
}
else if (double_line > 6)
{
if (detected_state == CORNER_IN)
{
last_time = time();
state = CORNER_IN;
printf("CORNER IN\n");
}
detected_state = CORNER_IN;
}
else if (time() - last_time > 0.2 && right_change > 3)
{
if (detected_state == RIGHT_CHANGE_DETECTED)
{
last_time = time();
state = RIGHT_CHANGE_DETECTED;
printf("RIGHT_CHANGE_DETECTED\n");
}
detected_state = RIGHT_CHANGE_DETECTED;
}
else if (time() - last_time > 0.2 && left_change > 3)
{
if (detected_state == LEFT_CHANGE_DETECTED)
{
last_time = time();
state = LEFT_CHANGE_DETECTED;
printf("LEFT_CHANGE_DETECTED\n");
}
detected_state = LEFT_CHANGE_DETECTED;
}
break;
case CORNER_IN:
if (time() - last_time < 0.1)
{
motor(BRAKE, BRAKE, BRAKE, BRAKE);
}
else
{
motor(SLOW, SLOW, SLOW, SLOW);
}
handle(LOOSE * line);
if (time() - last_time > 0.2 && (left_change > 3 || right_change > 3))
{
last_time = time();
state = CORNER_OUT;
printf("CORNER OUT\n");
}
break;
case CORNER_OUT:
motor(MEDIUM, MEDIUM, MEDIUM, MEDIUM);
handle(STRICT * line);
if (time() - last_time > 0.70)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case RIGHT_CHANGE_DETECTED:
motor(MEDIUM, MEDIUM, MEDIUM, MEDIUM);
handle(LOOSE * line);
if (double_line == 0)
{
last_time = time();
state = RIGHT_TURN;
printf("RIGHT_TURN\n");
}
if (time() - last_time > 2.5)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case RIGHT_TURN:
motor(MEDIUM, MEDIUM, MEDIUM, MEDIUM);
if (time() - last_time < 0.45)
handle(-40);
else if (time() - last_time < 0.6)
handle(20);
else if (double_line > 1)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case LEFT_CHANGE_DETECTED:
motor(MEDIUM, MEDIUM, MEDIUM, MEDIUM);
handle(LOOSE * line);
if (double_line == 0)
{
last_time = time();
state = LEFT_TURN;
printf("LEFT TURN\n");
}
if (time() - last_time > 2.5)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case LEFT_TURN:
motor(MEDIUM, MEDIUM, MEDIUM, MEDIUM);
if (time() - last_time < 0.45)
handle(40);
else if (time() - last_time < 0.6)
handle(-20);
else if (double_line > 1)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case RAMP_UP:
motor(UPRAMP, UPRAMP, UPRAMP, UPRAMP);
handle(STRICT * line);
if (angles[1] < 0.05)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
case RAMP_DOWN:
motor(SLOW, SLOW, SLOW, SLOW);
handle(STRICT * line);
if (angles[1] > -0.05)
{
last_time = time();
state = FOLLOW;
printf("FOLLOW\n");
}
break;
}
};
wb_robot_cleanup(); // this call is required for WeBots cleanup
return 0;
}