vrijdag 26 juni 2015

Switching States

In the previous post we prepared a radio controlled boat to operate on an Arduino Uno board, and set up the general software architecture. In this post, we will implement the states that the boat can be in:
  • manual: the boat is controlled by the radio controller
  • autonomous: the boat follows a pre-programmed route.
A nifty little trick with the radio controller will help us to switch between the two. If the speed control is pulled backward and forward within one second, the boat will switch between these states. It is a bit like switching gears in your car. One consequence of this approach is that an additional state called BLOCKED is required when the speed control is pulled backward. After a second, it will revert to normal operation.

Note: the full software is available on GitHub

Switching States

First we create a new tab that contains the software for the radio controller. Create a new tab called RC_Propulsion. If you have implemented the code in the previous post, you will probably by now recognize the pattern that was used previously:

//Radio controls for Propulsion
#define IN_UP 8
#define IN_DOWN 7

#define MAX_PULSE 40

enum Direction{
  STOP = 0,
  FORWARD = 1,
  BACKWARD = 2
};

static Direction direction = STOP;

static int directionTimer = 0;

void RCP_Init(){
  pinMode( IN_UP, INPUT ); //Set input mode
  pinMode( IN_DOWN, INPUT ); //Set input mode
}

/**
 * Update the RC through the timer
 */
void RCP_Update(){
  directionTimer++;
}

boolean RCP_Up(){
  return digitalRead( IN_UP );
}

boolean RCP_Down(){
  return digitalRead( IN_DOWN );
}

int RCP_Direction(){
  if( RCP_Up()){
    direction = FORWARD;
    directionTimer = 0;
  } else if( RCP_Down() ){
    direction = BACKWARD;
    directionTimer = 0;
  }
  else{
    if( directionTimer > MAX_PULSE ){
      direction = STOP;
      directionTimer = 0;
    }
  }
  return direction;
}

/**
 * Returns true if the direction should be forward
 */
boolean RCP_Forward(){
  return ( direction == FORWARD );
}

/**
 * Returns true if the direction should be backward
 */
boolean RCP_Backward(){
  return ( direction == BACKWARD );
}

/**
 * Returns true if the direction should be forward
 */
boolean RCP_Stop(){
  return ( direction == STOP );
}


Even though the code is more elaborate than the LED control, the general idea is still very much the same. The two inputs determined by the propulsion control are used to determine a direction for the boat. The corresponding variable is used by the rest of the code to steer the boat. It also returns to a stop state with a small delay, in order to become less sensitive to losses in the communication. This means that when the communication is lost, the boat will stop

The Left and Right controls are fairly similar and are controlled in the RC_Direction tab:
//Constants
#define IN_LEFT 9
#define IN_RIGHT 10

#define MAX_PULSE 40

enum Steering{
  STRAIGHT = 0,
  LEFT = 1,
  RIGHT = 2,
};
static Steering steering = STRAIGHT;

static int steeringTimer = 0;

void RCD_Init(){
  pinMode( IN_LEFT, INPUT );
  pinMode( IN_RIGHT, INPUT );
}

/**
 * Update the RC through the timer
 */
void RCD_Update(){
  steeringTimer++; 
}

boolean RCD_Left(){
  return digitalRead( IN_LEFT );
}

boolean RCD_Right(){
  return digitalRead( IN_RIGHT );
}

int RCD_Steering(){
  if( RCD_Left()){
    steering = LEFT;
    steeringTimer = 0;
  } else if( RCD_Right() ){
    steering = RIGHT;
    steeringTimer = 0;
  }
  else{
    if( steeringTimer > MAX_PULSE ){
      steering = STRAIGHT;
      steeringTimer = 0;
    }
  }
  return steering;
}

/**
 * Returns true if the direction should be forward
 */
boolean RCD_LeftTurn(){
  return ( steering == LEFT );
}

/**
 * Returns true if the direction should be backward
 */
boolean RCD_RightTurn(){
  return ( steering == RIGHT  );
}

/**
 * Returns true if the direction should be forward
 */
boolean RCD_Straight(){
  return ( steering == STRAIGHT );
}


Last we need to define the state controller:

#define BLOCK_BLINK 5
#define AUTO_BLINK 10

#define BLOCK_TIME 80

enum State{
   MANUAL,
   BLOCKED,
   AUTONOMOUS 
};
static State state = MANUAL;

enum Control{
  INIT,
  START,
  COMPLETED 
};
static Control control;

static int counter;
static int downKeyTeller = 0;

void State_Init(){
  state = MANUAL;
  control = INIT;
  counter = 0;
}

/**
 * Update the RC through the timer interrupt
 */
static State prevState;
void State_Update(){
  counter++;
  downKeyTeller++;
  switch( control ){
    case INIT:
      if( RCP_Down()){
        downKeyTeller = 0;
        prevState = state;
        state = BLOCKED;
        control = START;
      }
      break;
    case START:
      if( downKeyTeller > BLOCK_TIME ){
        state = prevState;
        downKeyTeller = 0;
        control = COMPLETED;
        break;
      }
      if( RCP_Up() ){
        switch( prevState ){
          case MANUAL:
             state = AUTONOMOUS;
             break;
          case AUTONOMOUS:
            state = MANUAL;
            break;
         }
         control = COMPLETED;
      }
       break;
    default:
      if( !RCP_Down())
        control = INIT;
      break;
  }
}

/**
 * Control the states
 */
void State_Control(){
  switch( state  ){
    case MANUAL:
      Motor_ManualControl();
      Rudder_ManualControl();
      break;
    case AUTONOMOUS:
      Autonomous_Motion();
      break;
    default:
      break;
  }
}

byte State_GetState(){
  return state;
}

boolean State_IsBlocked(){
  return state == BLOCKED;
}

boolean State_IsManual(){
  return state == MANUAL;
}

boolean State_IsAutonomous(){
  return state == AUTONOMOUS;
}

This code bit of code is is a bit more elaborate than the previous, but still by large follows the same conventions as the previous. The State_Update() function is the most complex part, and determines the switching of state through the propulsion control. This part, like all Update() functions is managed by the Timer.
The State_Control() function is called by the main loop. When in MANUAL mode, the function directly controls the motor and rudder functions, and switches to AUTONOMY when the state changes correspondingly.

With these changes, we only need to add two tabs for the motor and the rudder in order for the code to compile, and to let our boat to be controlled by the Arduino. We will discuss this code in the next post.

Geen opmerkingen:

Een reactie posten