Learn to code

ARDUINO & CODES

Previous Post
Creating a proximity touch password lockbox with Neopixels and Arduino
Next Post
Create LED strip effects with Arduino

Making an autonomous BeerPong Robot Car with Arduino

in Arduino Project

The following Arduino Project will teach you how to make an awesome autonomous Beer Pong Robot Car.

This is a challenging project, so instead of buying a kit that includes all the parts (I think there is not a beerpong arduino kit on the market), I decided to make a custom design and make the parts myself. It was quite challenging and sometimes I stopped for a couple of days before continuing.

The features that I decided my beerpong robot should have are:

  • It should be a vehicle that can move forward, backwards and turn.
  • The robot should be able to turn on his own.
  • The robot must detect edges.
  • It should detect obstacles ahead and avoid them.
  • The car should be able to move a certain amount of seconds before going in another direction.

Does this sound a lot of fun for you? I hope it does, it certainly was fun to build and to play with.

What you will need to know?

Before continuing with this guide, you may want to check out any topics from the list below that sound unfamiliar.

Obstacle / Edge Ovoidance Sensors

The obstacle avoidance sensors are basically sensors that detect objects. If the Beerpong Robot car is driving and an obstacle comes to close, it will stop and move in another direction immediately. When an obstacle comes to close to a Digital Infrared Sensor the sensor will send a HIGH signal to the Arduino Board while sending a LOW signal if the road ahead is clear of objects.

For the analog Infrared Sensors it is different. Here you can set a threshold that can be used for determining how close you want the object to be. The analog infrared sensors can be found at the bottom of the Beerpong Robot car. The analog sensors are used for edge detection. The measured distance between the sensor and the object, for instance, a table, is measured and used to determine whether there is an edge. If there is an edge the Beerpong Robot stops and moves in another direction. In case the sensors measure low numbers, the robot is on the table, the robot can move in any direction you want.

To determines the value of the edge parameters I performed “trial and error”. The thresholds that are set in the sketch are saved enough to detect edges. The threshold and IR sensors are significantly important since these sensors prevent the Beer Pong Robot Car from falling off the table.

BeerPong Chassis

Before you can program the beer pong robot, the chassis of the robot should build. If you look a the beer pong game, it usually consists of 6 cups per side. Therefore, you will need a robot chassis that can hold 6 cups of beers.

This can be quite difficult to make because foamboard or cardboard will not be strong enough to hold six cups while moving.

I decided to make the chassis with hobby glass. You can, of course, chose any type of material but remember that it needs to be strong enough.

Prepare the materials

Firstly, you will firsty cut out all the materials from hobbyglass.

Preparing Hobbyglass

The best way to cut the hobby glass is by using an automatic fretsaw. The fretsaw enables you to cut out the outlines of the hobby glass quite easy.

The holes in the top of the BeerPong robot are created by using a hole saw. The small holes that will hold the sensors, motor brackets, and the M3 bolds are created by a simple drill machine.

Remember to use the right diameter of saws for the holes. Otherwise, the screws will not fit or stay too loose.

Connecting the Infrared Sensors

All the infrared sensors will be connected/soldered to the breadboard. The beadboard is used to divide the power among the infrared sensors through a 5V line. I soldered every component but you can, of course, use jumper wires to connect everything together.

Remember to use the right diameter of saws for the holes. Otherwise, the screws will not fit or stay too loose.

Connecting the Motor Driver

A crucial part of the Beerpong Robot is the Motor Driver. I choose to use the TB6612FNG driver. You can read more about this at TB6612FNG Motor Driver.

The motor driver for the Beerpong Robot is connected in a different way than the tutorial above. The ground and power are connected directly to the Arduino instead of the breadboard. Also, the power that is used for the motors comes directly from the Arduino Uno Board.

 

Mounting everything

Now you can mount everything together before continuing to the code part of this project. With M3 screws and distance buses you can connect the bottom and top of the Beerpong Robot Car.

Make sure that you can access the Arduino Uno at all time.

BreadBoard Layout

It is quite difficult to connect everything together. So be carefull and dubbelcheck everything!!

One side of the breadboard is for the powering the analog sensors while the other half is used for powering the digital IR sensors.

Arduino PinPin Label Motor Driver
10AIN1
12BIN1
4AIN2
8BIN2
5PWMA
6PWMB
9STBY
Arduino PinPosition on Beer Pong Robot
2Forward IR Sensor
3MiddleRight IR Sensor
13BackRight IR Sensor
7MiddleLeft IR Sensor
11BackLeft IR Sensor
Arduino PinPosition on Beer Pong Robot
A0BackLeft Analog Sensor
A1BackRight Analog Sensor
A2MiddleRight Analog Sensor
A3MiddleLeft Analog Sensor
A4Forward Analog Sensor

The Code

// Autonomous Beer Pong Robot Car
// https://www.arduinoplatform.com
// 
// This is the library for the TB6612 that contains the class Motor and all the
// functions
#include <SparkFun_TB6612.h>

// Pins for all inputs, keep in mind the PWM defines must be on PWM pins
// the default pins listed are the ones used on the Redbot (ROB-12097) with
// the exception of STBY which the Redbot controls with a physical switch
#define AIN1 10
#define BIN1 12
#define AIN2 4
#define BIN2 8
#define PWMA 5
#define PWMB 6
#define STBY 9

// these constants are used to allow you to make your motor configuration
// line up with function names like forward.  Value can be 1 or -1
const int offsetA = 1;
const int offsetB = 1;

//these constants are used for the IR sensors at the sides
const int irBackLeft = A0;
const int irBackRight = A1;
const int irMiddleRight = A2;
const int irMiddleLeft  = A3;
const int irForward = A4;

const int obstacleForward = 2;
const int obstacleMiddleRight = 3;
const int obstacleBackRight = 13;
const int obstacleMiddleLeft = 7;
const int obstacleBackLeft = 11;


const int tresholdEdge = 800;
long  randomNumber; // random number for seconds turning

// Initializing motors.  The library will allow you to initialize as many
// motors as you have memory for.  If you are using functions like forward
// that take 2 motors as arguements you can either write new functions or
// call the function more than once.
Motor motor1 = Motor(AIN1, AIN2, PWMA, offsetA, STBY);
Motor motor2 = Motor(BIN1, BIN2, PWMB, offsetB, STBY);


namespace BeerpongRobot
{
class Robot
{
  public:

    void initialize()
    {

      move();
    }

    void run()
    {
      int distanceForward = analogRead(irForward);
      int distanceLeft = analogRead(irMiddleLeft);
      int distanceRight = analogRead(irMiddleRight);
      int distanceBackLeft = analogRead(irBackLeft);
      int distanceBackRight = analogRead(irBackRight);

      int avoidForward = digitalRead(obstacleForward);
      int avoidBackward = digitalRead(obstacleMiddleRight);
      int avoidLeft = digitalRead(obstacleBackRight);
      int avoidMiddleLeft = digitalRead(obstacleMiddleLeft);
      int avoidBackLeft = digitalRead(obstacleBackLeft);


      unsigned long currentTime = millis(); 

      if (moving()) {

        // these are the Digital Sensors on the top of the robot
        if (avoidForward == 0) {
          back(currentTime);
        }
        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime); 
        }
        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime);
        }

        // now the analog sensors at the bottom of the Robot

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        // When a random amount of seconds is passed in the doneMoving function the robot will turn.
        if (doneMoving(currentTime))
          turn (currentTime);

      }

      if (backing()) {

        if (avoidForward == 0) {
          back(currentTime);
        }

        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime);
        }

        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime);
        }

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        if (doneBack(currentTime)) // hier komen de sensoren voor naar achteren
          turn(currentTime);
      }


      if (turning()) {

        if (avoidForward == 0) {
          back(currentTime);
        }

        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime); // gaat naar left
        }

        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime); // gaat naar right
        }

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        if (doneTurning(currentTime))
          MoveForwardBackward(currentTime);
      }



      if (right()) {

        if (avoidForward == 0) {
          back(currentTime);
        }

        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime); // gaat naar left
        }

        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime); // gaat naar right
        }

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        if (doneright(currentTime))
          MoveForwardBackward(currentTime);
      }

      if (left()) {

        if (avoidForward == 0) {
          back(currentTime);
        }

        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime); // gaat naar left
        }

        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime); // gaat naar right
        }

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        if (doneleft(currentTime))
          MoveForwardBackward(currentTime);
      }
    }

  protected:
    void move()
    {
      motor2.drive(255);
      motor1.drive(255);
      state = stateMoving;
    }

    bool right(unsigned long currentTime) {
      motor2.drive(255);
      motor1.drive(0);
      state = stateright;
      endStateTime = currentTime + random(1000, 1500);
    }

    bool doneright (unsigned long currentTime) {
      return (currentTime >= endStateTime);
    }


    bool left(unsigned long currentTime) {
      motor2.drive(0);
      motor1.drive(255);
      state = stateleft;
      endStateTime = currentTime + random(1000, 1500);
    }

    bool doneleft (unsigned long currentTime) {
      return (currentTime >= endStateTime);
    }


    bool back(unsigned long currentTime ) // ZELFDE ALS ACHTERUIT?????
    {

      motor2.drive(-255);
      motor1.drive(-255);
      state = stateBacking;
      endStateTime = currentTime + random(1000, 1500);

    }

    bool doneBack(unsigned long currentTime)
    {
      return (currentTime >= endStateTime);

    }


    bool MoveForwardBackward(unsigned long currentTime)
    {

      if (random(2) == 0) {
    

        motor2.drive(255);
        motor1.drive(255);
      }
      else {

        motor2.drive(-255);
        motor1.drive(-255);
      }

      state = stateMoving;
      endStateTime = currentTime + random(3000, 4000);
    }

    bool doneMoving(unsigned long currentTime)

    {
      return (currentTime >= endStateTime);
    }


    bool edgeForward(unsigned int distance)
    {
      return (distance >= tresholdEdge);
    }

    bool edgeLeft(unsigned int distanceLeft)
    {
      return (distanceLeft >= tresholdEdge);
    }

    bool edgeRight(unsigned int distanceRight)
    {
      return (distanceRight >= tresholdEdge);
    }

    bool edgeBackLeft(unsigned int distanceBackleft)
    {
      return (distanceBackleft >= tresholdEdge);
    }

    bool edgeBackRight(unsigned int distanceBackRight)
    {
      return (distanceBackRight >= tresholdEdge);
    }

    bool turn(unsigned long currentTime)
    {
      if (random(2) == 0) {
        motor2.brake();
        motor1.drive(255);
      }
      else {
        motor1.brake();
        motor2.drive(255);
      }
      state = stateTurning;
      endStateTime = currentTime + random(3000, 4000);
    }

    bool doneTurning(unsigned long currentTime)
    {
      return (currentTime >= endStateTime);
    }
    bool moving() {
      return (state == stateMoving);
    }
    bool turning() {
      return (state == stateTurning);
    }

    bool right() {
      return (state == stateright);
    }
    bool left() {
      return (state == stateleft);
    }

    bool backing() {
      return (state == stateBacking);
    }

  private:
    enum state_t {stateMoving, stateTurning, stateBacking, stateright, stateleft };
    state_t state;
    
    unsigned long endStateTime;

};
};

BeerpongRobot::Robot robot;

void setup()
{
  Serial.begin(9600);
  pinMode(irBackLeft, INPUT);
  pinMode(irBackRight, INPUT);
  pinMode(irMiddleLeft, INPUT);
  pinMode(irMiddleRight, INPUT);
  pinMode(irForward, INPUT);
  pinMode(obstacleForward, INPUT);
  pinMode(obstacleMiddleRight, INPUT);
  pinMode(obstacleBackRight, INPUT);
  pinMode(obstacleMiddleLeft, INPUT);
  pinMode(obstacleBackLeft, INPUT);  
}

void loop()
{
  robot.run();
}

Code Explanation

In this project there is a lot of coding involved.

The above sketch will enable you to detect edges and obstacles. This is done by setting thresholds for the Analog Ir Sensor and using Digital Ir sensors to detect objects.

Furthermore, the above code enables you to move the robot car into a predefined direction or let it randomly choose which direction to take.

Importing the library 

The first part of the code is to include all the libraries that are necessary for the project to function. The only library that we need is the library for the motor driver.

After you installed the library, TB66112.h, can be found in the search function of “manage libraries in the Arduino IDE, you will define the pins of the motor driver.

Remember, if you wire your connection differently please do not forget tho change them in your sketch.

// Autonomous Beer Pong Robot Car
// https://www.arduinoplatform.com
// 
// This is the library for the TB6612 that contains the class Motor and all the
// functions
#include <SparkFun_TB6612.h>

// Pins for all inputs, keep in mind the PWM defines must be on PWM pins
// the default pins listed are the ones used on the Redbot (ROB-12097) with
// the exception of STBY which the Redbot controls with a physical switch
#define AIN1 10
#define BIN1 12
#define AIN2 4
#define BIN2 8
#define PWMA 5
#define PWMB 6
#define STBY 9

// these constants are used to allow you to make your motor configuration
// line up with function names like forward.  Value can be 1 or -1
const int offsetA = 1;
const int offsetB = 1;

Declare the variables

As in every sketch that you will make, this one also needs variables to work with.

Firstly, the Analog IR sensors are defined. You will use these sensors for detecting edges.

Secondly, the Digital IR sensors are defined. You will use these sensors for detecting objects.

Furthermore, a threshold is defined that sets the value for the edge. Low numbers for the threshold means that it detects an edge, rather than a surface, faster than high threshold numbers. However, black surfaces do not reflect much light to the sensor, therefore, you should not define the threshold with very low numbers. The consequence can be that the sensor detects a black surface as an edge.

//these constants are used for the IR sensors at the sides
const int irBackLeft = A0;
const int irBackRight = A1;
const int irMiddleRight = A2;
const int irMiddleLeft  = A3;
const int irForward = A4;

const int obstacleForward = 2;
const int obstacleMiddleRight = 3;
const int obstacleBackRight = 13;
const int obstacleMiddleLeft = 7;
const int obstacleBackLeft = 11;


const int thresholdEdge = 800;
long  randomNumber; // random number for seconds turning

// Initializing motors.  The library will allow you to initialize as many
// motors as you have memory for.  If you are using functions like forwarding
// that take 2 motors as arguments you can either write new functions or
// call the function more than once.
Motor motor1 = Motor(AIN1, AIN2, PWMA, offsetA, STBY);
Motor motor2 = Motor(BIN1, BIN2, PWMB, offsetB, STBY);

Creating a class

The namespace keyword that we are using groups everything inside the { and } under a name.

The class keyword defines the structure of data and function into an entitity called object.  The public part indicates that the declartion that follows are accecible to everyone.

The class is called robot and will be inside the BeerPongRoboto namespace. It has two public method, initialize() and run() that will be used in our setup() and loop() part of the sketch.

Initialize makes sure that the Beer Pong Robot will move forward when it starts while the run method contains all the functions after that.

namespace BeerpongRobot
{
class Robot
{
  public:

    void initialize()
    {

      move();
    }

Void Run()

You arrived at the void run part of the sketch. This part is executed in the void loop (). Firstly, all the sensors are declared. In total there are 5 Digital IR Sensors and 5 Analog IR Sensors.

We also start the timer to know how many milliseconds have passed since the start of the sketch. This will be used to move the Beer Pong Robot Car a specific amount of seconds into a direction.

void run()
    {
      int distanceForward = analogRead(irForward);
      int distanceLeft = analogRead(irMiddleLeft);
      int distanceRight = analogRead(irMiddleRight);
      int distanceBackLeft = analogRead(irBackLeft);
      int distanceBackRight = analogRead(irBackRight);

      int avoidForward = digitalRead(obstacleForward);
      int avoidBackward = digitalRead(obstacleMiddleRight);
      int avoidLeft = digitalRead(obstacleBackRight);
      int avoidMiddleLeft = digitalRead(obstacleMiddleLeft);
      int avoidBackLeft = digitalRead(obstacleBackLeft);


      unsigned long currentTime = millis(); // begin the timer

Different States

We are using states to determine what the robot is doing and what the next direction should be. I will explain the first state, Moving, the other states act the same way.

We want the robot to do a couple of things: stateMoving, stateTurning, stateBacking, stateright, stateleft

  • Move forward (state = moving)
  • Turning (state = Turning).
  • Turn Right (state = right)
  • Turn Left (state = left)
  • Move backwards (state =  backing)

Furthemore, if the state is active we want to know whether or not there is an edge or an object close to the sensors.

If there is an object in front of the sensor while moving forward the robot will go backwards. If there is an object to the left the robot will turn right. In case there is an edge detected by the analog sensors the robot will also go backwards or turn left/right.

The interesting part is the part where we send the currentTime to the BOOL function donemoving. This enables us to decide how long the robot should stay in the state of Moving. If the BOOL function is true, the robot will go into a turning state.

Now the robot is in a turning state and it will randomly turn in a direction. The same happens in the turning state, after a couple of seconds (randomly defined) the robot will go to another state. In this state, it will randomly move forward or backwards.

Just take a look at the code and the boolean function that are associated with them.

if (moving()) {

        // these are the Digital Sensors on the top of the robot
        if (avoidForward == 0) {
          back(currentTime);
        }
        if ( avoidBackLeft == 0 || avoidMiddleLeft == 0) {
          right(currentTime); 
        }
        if (avoidBackward == 0 || avoidLeft == 0) {
          left(currentTime);
        }

        // now the analog sensors at the bottom of the Robot

        if (edgeForward(distanceForward))
          back(currentTime);

        if (edgeLeft(distanceLeft) || edgeBackRight(distanceBackRight))
          right(currentTime);

        if (edgeRight(distanceRight)  || edgeBackLeft(distanceBackLeft) )
          left(currentTime);

        // When a random amount of seconds is passed in the doneMoving function the robot will turn.
        if (doneMoving(currentTime))
          turn (currentTime);

      }

&nbsp;

Boolean Functions

I will highlight one boolean function to show how it works.  The bool function, turn, will randomly turn right or left. There is a random amount of time added to the currenttime. This is useful for the next bool function. doneTurning returns true if the currentime is greater than the endStateTime. In other words, if the robot is turning and the currentime + random(3000 + 4000) is lower than the currentime the robot will go to another state.

 
    bool turn(unsigned long currentTime)
    {
      if (random(2) == 0) {
        motor2.brake();
        motor1.drive(255);
      }
      else {
        motor1.brake();
        motor2.drive(255);
      }
      state = stateTurning;
      endStateTime = currentTime + random(3000, 4000);
    }

    bool doneTurning(unsigned long currentTime)
    {
      return (currentTime >= endStateTime);
    }

Different States

Before we can execute everything we need to set the states, the function below enables us to have various states in the robot. The last part is the endStateTime function that enables us in our boolean function if the state has ended.

private:
    enum state_t {stateMoving, stateTurning, stateBacking, stateright, stateleft };
    state_t state;
    
    unsigned long endStateTime;

Executing the code

The last part is to set all the sensors to an input mode in the void setup () and to run the code. You can run the code by robot.run() and it will execute everything that is listed under the robot class explained above.

void setup()
{
  Serial.begin(9600);
  pinMode(irBackLeft, INPUT);
  pinMode(irBackRight, INPUT);
  pinMode(irMiddleLeft, INPUT);
  pinMode(irMiddleRight, INPUT);
  pinMode(irForward, INPUT);
  pinMode(obstacleForward, INPUT);
  pinMode(obstacleMiddleRight, INPUT);
  pinMode(obstacleBackRight, INPUT);
  pinMode(obstacleMiddleLeft, INPUT);
  pinMode(obstacleBackLeft, INPUT);  
}

void loop()
{
  robot.run();
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed

Menu