Top

Bumper Mechanism

This page describes how the collision sensor of the DIY robot vacuum works. Below is a video showing the sensor mechanism in action. The robot changes direction when it hits a wall.

How it works

When the robot hits a wall, the bumper pushes one of the switches closer to the wall. The robot can then know which side the wall exists and change direction accordingly.

Release Position

While the robot is not in contact with any object, the bumper is pushed forward by the two switches.

Pushed Position

When the robot collides with a wall, the switch is pressed by the front bumper. This signals the microcontroller that it needs to stop moving forward and change direction.

Bumper Removed

Bumper

Back Side of Bumper

Code

Below is Arduino source code to achieve the motion in the video. When a collision is detected, the robot first backs off a little. Then, the robot turns in the direction opposite to the wall. Finally, the robot moves forward in a straight line again.

#include <AFMotor.h>


// Pins
const uint8_t BUMPER_BUTTON_LEFT_PIN  = A4;
const uint8_t BUMPER_BUTTON_RIGHT_PIN = A5;


// Motors
// Initialized to RELEASE and speed 0
AF_DCMotor motor_right(1);
AF_DCMotor motor_left(2);
AF_DCMotor motor_fan(3);


// States
// Not using enum because enum is int (16 bits)
const uint8_t STATE_START        = 0;
const uint8_t STATE_GO_STRAIGHT  = 1;
const uint8_t STATE_GO_BACK      = 2;
const uint8_t STATE_TURN_LEFT    = 3;
const uint8_t STATE_TURN_RIGHT   = 4;


const int START_PAUSE_TIME_MS = 1000;
const int GO_BACK_TIME_MS     = 1000;
const int TURN_TIME_MS        = 1000;
const int WHEEL_DUTY_FORWARD  =   80;
const int WHEEL_DUTY_BACKWARD =  -50;


uint8_t state = STATE_START;
unsigned long timer_ms = 0;
bool is_wall_on_left = false;
int wheel_duty_left_prev = 0;
int wheel_duty_right_prev = 0;


void setup()
{
  // Debugging pins
  // Arduino's pin 13 (PB6)
  pinMode(LED_BUILTIN, OUTPUT);

  // Bumper button pins
  pinMode(BUMPER_BUTTON_LEFT_PIN, INPUT_PULLUP);
  pinMode(BUMPER_BUTTON_RIGHT_PIN, INPUT_PULLUP);

  timer_ms = millis();

  set_fan_on(true);
}


void loop()
{
  // Bumper
  int left_button = digitalRead(BUMPER_BUTTON_LEFT_PIN);
  int right_button = digitalRead(BUMPER_BUTTON_RIGHT_PIN);

  int wheel_duty_left = 0;
  int wheel_duty_right = 0;
  
  switch (state)
  {
    default:
    case STATE_START:
      if (millis() - timer_ms > START_PAUSE_TIME_MS)
      {
        state = STATE_GO_STRAIGHT;
      }
      break;
    
    case STATE_GO_STRAIGHT:
      if (right_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = false;
        state = STATE_GO_BACK;
      }
      else if (left_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = true;
        state = STATE_GO_BACK;
      }
      else
      {
        wheel_duty_left = WHEEL_DUTY_FORWARD;
        wheel_duty_right = WHEEL_DUTY_FORWARD;
      }
      break;
      
    case STATE_GO_BACK:
      if (millis() - timer_ms > GO_BACK_TIME_MS)
      {
        if (is_wall_on_left)
        {
          state = STATE_TURN_RIGHT;
        }
        else
        {
          state = STATE_TURN_LEFT;
        }
        timer_ms = millis();
      }
      wheel_duty_left = WHEEL_DUTY_BACKWARD;
      wheel_duty_right = WHEEL_DUTY_BACKWARD;
      break;
    
    case STATE_TURN_LEFT:
      if (millis() - timer_ms > TURN_TIME_MS)
      {
        state = STATE_GO_STRAIGHT;
      }
      else if (right_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = false;
        state = STATE_GO_BACK;
      }
      else if (left_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = true;
        state = STATE_GO_BACK;
      }
      else
      {
        wheel_duty_left = 0;
        wheel_duty_right = WHEEL_DUTY_FORWARD;
      }
      break;
    
    case STATE_TURN_RIGHT:
      if (millis() - timer_ms > TURN_TIME_MS)
      {
        state = STATE_GO_STRAIGHT;
      }
      else if (right_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = false;
        state = STATE_GO_BACK;
      }
      else if (left_button == LOW)
      {
        timer_ms = millis();
        is_wall_on_left = true;
        state = STATE_GO_BACK;
      }
      else
      {
        wheel_duty_left = WHEEL_DUTY_FORWARD;
        wheel_duty_right = 0;
      }
      break;
  }
  
  if (wheel_duty_left != wheel_duty_left_prev)
  {
    set_wheel_duty(&motor_left, wheel_duty_left);
    wheel_duty_left_prev = wheel_duty_left;
  }

  if (wheel_duty_right != wheel_duty_right_prev)
  {
    set_wheel_duty(&motor_right, wheel_duty_right);
    wheel_duty_right_prev = wheel_duty_right;
  }
}


// duty: -255 to 255 inclusive (else clamped to range)
void set_wheel_duty(AF_DCMotor* motor, int duty)
{
  uint8_t command;
  uint8_t duty_uint8;
  int abs_duty = abs(duty);

  // Convert duty (int) to
  // duty_uint8 (uint8_t: 0 to 255)
  if (abs_duty > 255)
  {
    duty_uint8 = 255;
  }
  else
  {
    duty_uint8 = (uint8_t) abs_duty;
  }

  // Set command (direction) based on the sign of duty
  // At low duty (<10), wheel does not turn so set duty to 0
  if (duty_uint8 < 10)
  {
    duty_uint8 = 0;
    command = BRAKE;
  }
  else
  {
    if (duty > 0)
    {
      command = FORWARD;
    }
    else
    {
      command = BACKWARD;
    }
  }

  // Set speed and direction
  motor->run(command);
  motor->setSpeed(duty_uint8);
}


void set_fan_on(bool is_fan_on)
{
  // Set speed and direction
  if (is_fan_on)
  {
    motor_fan.setSpeed(255);
    motor_fan.run(FORWARD);
  }
  else
  {
    motor_fan.setSpeed(0);
    motor_fan.run(RELEASE);
  }
}