Mechatronics Exercises

Workspace Navigation

BeerPong Bot

Project plan

The goal of the project is to build a robot which can aim and throw ping pong balls into cups with a reasonable degree of accuracy. The launcher will have a twin-roller design, where the ping pong ball is pushed in between two spinning rollers, launching the ball into the air. The range of the throw is adjusted by changing the speed of the rollers and the sideways motion is achieved with a linear rail system and a stepper motor. 

Pre-circus update

The first prototype frame of the bot has been built. Sideways linear movements are functional, and the launcher platform is built. The launcher however does not have the desired functionality, as the 12V DC motor is not powerful enough to achieve the necessary launch speeds. Overloading the motor with 24V achieves the desired speeds, but the vibrations from the needlessly complicated gear assembly are not optimal. As a result, a different style of launcher assembly will be attempted, probably with two encoder motors instead of a single, non-encoded motor. This will remove the need for the mechanical linkages, reducing friction while still allowing for relatively accurate speed control of the rollers. Once a new launcher model is designed and built, all that remains is to implement the remaining hardware as well as finalizing the circuitry, programming, as well as doing some testing to catch any unexpected flaws in the design. 


Some pictures of the pre-circus build

Final version

Below is a video of the final working model from the Mechatronic Circus. Based on very limited testing, the success rate of the launcher seemed to be between 50 and 75 percent. I was able to get one sequence of six consecutive hits (ie. a flawless game) during the circus. The robot can't actually sense the distance of the cups nor the position, as it simply trusts that the cups are centered close to the centerline of the robot at a range of approximately 155 cm. I tried to implement an ultrasound range sensor, but was unable to get reliable readings. Upon startup the robot zeroes itself to the limit switch placed at the end of the linear rail, and centers its position. From here, it listens to the serial connection to a laptop running the Arduino IDE software. The target cups are labelled with numbers from 1-6, and once a corresponding number command is sent to the robot it moves to the correct position and ramps up the speed of the motors to achieve the correct launch speed for the given cup. Once the robot is ready to launch, an RC servo pushes the ping pong ball in between the spinning rollers, sending the ball toward the cups.

BPB.mp4


Mechanical parts:

The required 3d-printed parts for the project are pictured below, .stl and .step files will also be provided at the bottom of the page. All parts were printed using an Anycubic Kobra Max with Prusament PLA.



For the tightener nut, press an M8 threaded insert into the base of the part. This threads onto an M8 bolt which in turn is tightened around base of the fork piece with a regular M8 hex nut. 


In addition to the 3d-printed parts, we have some standard components. The two linear rails are standard SBR16 rails with a length of 600 mm, mounted on a flat piece of 22 mm plywood. The gap between the axis of the rails is 70 mm. Driving the launcher's sideways motion is a NEMA17 stepper motor linked to the launcher via GT2 pulley and belt. To attach the DC motors to the launcher, I cut some sheetmetal pieces from 1mm stainless steel with a waterjet cutter. To couple the DC motors to the rollers I machined two 8mm diameter steel shafts with a 2mm hole and M3 set screw at one end and an M8 thread at the other end. Drawings for these provided at the end of the page. I also machined the "rims" of the rollers from POM plastic to attach them to foam wheels, which I looted from old RC-car remotes. I would recommend using standard foam wheels instead, I just had these ones laying around as well as access to a lathe and a mill, so I went with what was easiest for me. The diameter of the wheels I used was approximately 59 millimeters. To stabilize the rotating shafts I used some flanged ball bearings I found from a drawer that happened to have 8 mm bore width. You can freely adjust the 3d-print models to accommodate different sized standard parts with the .step files provided at the bottom of the page.

Sheetmetal part for mounting the DC motors

The steel shaft for coupling the motors with the wheels

Electronics:

The microcontroller used was the Arduino UNO R3.

List of the basic components:

Componentcountconnections
L298N motor driver1ENA1 and ENA2 to any PWM pins, IN1 and IN4 to 5V, IN2 and IN3 to ground, outputs to corresponding motors (swap motor connections to reverse rotation).
DC motor2Motor positive to L298N 12V output, motor negative and encoder negative to ground. One of the encoder outputs to either digital pin 2 or 3. Encoder positive to 5V.
RC-servo of your choosing1Signal to any PWM pin, positive to 5V and negative to ground.
Stepper driver (I used A4988)1STEP and DIR to any digital I/O pins, VMOT to 12V, VDD to 5V, GND to ground, stepper coil connections to stepper motor, reset and sleep connected to each other.
NEMA17 stepper motor1Connected to stepper driver. Coil wiring depends on your motor.
limit switch of your choosing1Common pin to ground, N-O to any digital I/O pin.
1000uF capacitor1Connected parallel to 5V circuit.
100uF capacitor1Connected parallel to stepper driver 12V connection.
12V 3A power supply1(NOTE:Using a more powerful power supply would midigate the need for a capacitor in the 5V circuit)


Software:

The software is written in Arduino C, utilizing the libraries Servo.h, ArduPID.h and ezButton.h.

The source code file of the robot in .ino format:

BPB-finaldocu.ino

Code looks something like this:


#include <Servo.h>
#include <ArduPID.h>
#include <ezButton.h>

// variables to store pin numbers
const int step = 8;
const int dir = 7;
const int ena1 = 5;
const int ena2 = 6;
const int enc1 = 3;         // IMPORTANT: only pins 2 and 3 can be used with interrupts on Arduino UNO R3
const int enc2 = 2;         // IMPORTANT: only pins 2 and 3 can be used with interrupts on Arduino UNO R3

// variables to operate stepper
int counter = 0;
const int delayStep = 800;

// variables for motor control
long previousT = 0;
long previousT2 = 0;
double dT1 = 0;
double dT2 = 0;
double setpoint = 1570;
double setpoint2 = 1570;
double pwm = 0;
double pwm2 = 0;
double p = 0.8;
double i = 0.001;
double d = 0.25;
double rpm = 0;
double rpm2 = 0;

// variables for loop() function
int previousCups = 0;
int cups = 0;

ArduPID myController1;       // initialize PID control for motor 1
ArduPID myController2;       // initialize PID control for motor 2
Servo myservo;               // initialize servo object
ezButton limitSwitch(12);    // attach limitswitch to pin 12

void setup() {
  Serial.begin(9600);        // begin serial connection

  pinMode(step, OUTPUT);     // set pinModes
  pinMode(dir, OUTPUT);

  pinMode(enc1,INPUT);
  pinMode(enc2,INPUT);

  pinMode(ena1,OUTPUT);
  pinMode(ena2,OUTPUT);

  myservo.attach(11);       // attach servo to pin 11 (NOTE: PWM functionality for pins 9 and 10 is disabled while <servo.h> is active)
  myservo.write(0);         // ensure that servo is in the correct position
  limitSwitch.setDebounceTime(50);  // debounce the limitswitch

  initializeStepper();      // find the zero position

  myController1.begin(&rpm, &pwm, &setpoint, p, i, d);                // begin PID control
  myController2.begin(&rpm2, &pwm2, &setpoint2, p, i, d);
  attachInterrupt(digitalPinToInterrupt(enc1), readEncoder, RISING);  // set up interrupts for the encoder outputs (pins 2 and 3)
  attachInterrupt(digitalPinToInterrupt(enc2), readEncoder2, RISING);
  myController1.setOutputLimits(0, 255);                              // limit the PID controller outputs
  myController2.setOutputLimits(0, 255);
  myController1.start();
  myController2.start();
}

void loop() {

  if (previousCups != cups) {   //check if a new cup number has been input
    switch(cups) {
      case 1:             //cup 1
        setpoint = 1565;          // motor speed setpoints to first row
        setpoint2 = 1565;
        Stepleft(counter);        // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
      case 2:             //cup 2
        setpoint = 1610;          // motor speed setpoints to second row
        setpoint2 = 1610;
        Stepleft(245 + counter);  // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
      case 3:             //cup 3
        setpoint = 1610;          // motor speed setpoints to second row
        setpoint2 = 1610;
        Stepright(245 - counter); // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
      case 4:             //cup 4
        setpoint = 1650;          // motor speed setpoints to third row
        setpoint2 = 1650;
        Stepright(490 - counter); // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
      case 5:             //cup 5
        setpoint = 1650;          // motor speed setpoints to third row
        setpoint2 = 1650;
        Stepleft(counter);        // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
      case 6:             //cup 6
        setpoint = 1650;          // motor speed setpoints to third row
        setpoint2 = 1650;
        Stepleft(490 + counter);  // move to position
        stabilize();              // stabilize the speed
        myservo.write(45);        // launch the ball
        delay(200);               // wait for servo
        myservo.write(0);         // return servo
        delay(200);
        break;
    }
    previousCups = cups;
    cups = 0;
  }

  if(Serial.available()){      // check for new input from serial
    String a = Serial.readStringUntil('\n'); // read new input from serial
    a.trim();
    cups = a.toInt();          // store value
  }

}

void stabilize() {             // function to stabilize motor speed
  int time = 0;
  int pwmtotal = 0;
  int pwmtotal2 = 0;
  float total = 0.0;
  while (setpoint - rpm > 10) {     // first wait for motor to reach the setpoint
    runPID();
  }
  int prevtime = millis();
  while (time < 3000) {             // calculate the average speed over 3 seconds
    runPID();
    pwmtotal += pwm;
    pwmtotal2 += pwm2;
    total = total + 1.0;
    time += millis()-prevtime;
    prevtime = millis();
  }
  float avg1 = (float)pwmtotal / total;
  float avg2 = (float)pwmtotal2 / total;
  float avg = (avg1 + avg2) / 2;
  analogWrite(ena1, (int)avg);      // set the motors at the calculated pwm value
  analogWrite(ena2, (int)avg);
  Serial.print(rpm);                // print the launch values to serial
  Serial.print(", ");
  Serial.println(rpm2);
}

void runPID() {                 // function to control motor speed
  myController1.compute();          // compute pid values
  myController2.compute();
  if (rpm > 1000) {                 //  check if the current speed is high enough
    analogWrite(ena1, (int)pwm);    
    analogWrite(ena2, (int)pwm2);
  } else {                          // ramp up the speed to avoid drawing too much current
    analogWrite(ena1, 150);        
    analogWrite(ena2, 150);
  }
  Serial.print(rpm);                // print rpm and pwm values to serial
  Serial.print(", ");
  Serial.println(pwm);
}

void initializeStepper() {      // function to find zero point of sideways axis
  digitalWrite(dir, HIGH);          // set direction to left
  limitSwitch.loop();               // start listening to the limitswitch
  int done = 0;
  while(!done) {                    // continuosly poll the limitswitch to find out if contact has been made
    limitSwitch.loop();
    if(limitSwitch.isPressed() || limitSwitch.getState() == LOW) { // check the state of the limitswitch
      done = 1;      
    } else {                        // if limitswitch is not pressed, continue moving left
      digitalWrite(step, HIGH);
      delayMicroseconds(delayStep);
      digitalWrite(step, LOW);
      delayMicroseconds(delayStep);
    }
  }
  Stepright(1280);                  // center the launcher
  counter = 0;                      // reset step counter to middle position
}

void Stepleft(int count) {      // function to move the launcher to the left
  if(counter > -1280) {             // check if left edge has been reached
    if (count < 0) {                // check if input from loop() is negative
      Stepright(-count);            // move right instead if count is negative
    } else {
      digitalWrite(dir, HIGH);      // set direction to left
      for (int i = 0; i < count; i++) { // move launcher <count> steps
        digitalWrite(step, HIGH);
        delayMicroseconds(delayStep);
        digitalWrite(step, LOW);
        delayMicroseconds(delayStep);
        counter--;                  // decrement counter variable
      }
    }
  } else {
    Serial.println("Left edge reached!!!"); // inform user that bad things are happening
  }
 
}

void Stepright(int count) {      // function to move the launcher to the right
  if (counter < 1280) {             // check if right edge has been reached
    if (count < 0) {                // check if input from loop() is negative
      Stepleft(-count);             // move left instead if count is negative
    } else {
      digitalWrite(dir, LOW);       // set direction to right
      for (int i = 0; i < count; i++) { // move launcher <count> steps
        digitalWrite(step, HIGH);
        delayMicroseconds(delayStep);
        digitalWrite(step, LOW);
        delayMicroseconds(delayStep);
        counter++;                  // increment counter variable
      }
    }
  } else {
    Serial.println("Right edge reached!!!"); // inform user that bad things are happening
  }  
}

void readEncoder(){           // read values from motor encoder
  dT1 =  micros() - previousT;        // calculate the time between pulses
  previousT = micros();              
  rpm = 60/(11*(dT1/1000000));    // calculate rpm
}

void readEncoder2(){          // read values from motor encoder
  dT2 =  micros() - previousT2;       // calculate the time between pulses
  previousT2 = micros();              
  rpm2 = 60/(11*(dT2/1000000));   // calculate rpm
}

Files:

.STEP:

BPB LEVER v2.step

BPB_tightener v1.step

BPB_rollerfork v1.step

BPB_rollermount v2.step

BPB_steppermount v2.step

BPB_sled_test_2 v7.step

.stl:

BPB LEVER v2.stl

BPB_tightener v1.stl

BPB_rollerfork v1.stl

BPB_rollermount v2.stl

BPB_steppermount v2.stl

BPB_sled_test_2 v7.stl

  • No labels
  File Modified
JPEG File 20230314_131008.jpg Mar 19, 2023 by Aaro Uitto
JPEG File 20230314_131022.jpg Mar 19, 2023 by Aaro Uitto
File BPB_rollerfork v1.step Apr 23, 2023 by Aaro Uitto
File BPB_rollerfork v1.stl Apr 23, 2023 by Aaro Uitto
File BPB_rollermount v2.step Apr 23, 2023 by Aaro Uitto
File BPB_rollermount v2.stl Apr 23, 2023 by Aaro Uitto
File BPB_sled_test_2 v7.step Apr 23, 2023 by Aaro Uitto
File BPB_sled_test_2 v7.stl Apr 23, 2023 by Aaro Uitto
File BPB_steppermount v2.step Apr 23, 2023 by Aaro Uitto
File BPB_steppermount v2.stl Apr 23, 2023 by Aaro Uitto
File BPB_tightener v1.step Apr 23, 2023 by Aaro Uitto
File BPB_tightener v1.stl Apr 23, 2023 by Aaro Uitto
Multimedia File BPB.mp4 Apr 23, 2023 by Aaro Uitto
JPEG File bpb-2.jpg Mar 19, 2023 by Aaro Uitto
PNG File bpb-akseli.PNG Apr 23, 2023 by Aaro Uitto
File BPB-finaldocu.ino Apr 23, 2023 by Aaro Uitto
PNG File bpb-fork.PNG Apr 23, 2023 by Aaro Uitto
PNG File bpb-left.PNG Apr 23, 2023 by Aaro Uitto
PNG File bpb-lever.PNG Apr 23, 2023 by Aaro Uitto
File BPB LEVER v2.step Apr 23, 2023 by Aaro Uitto
File BPB LEVER v2.stl Apr 23, 2023 by Aaro Uitto
PNG File bpb-motordisk.PNG Apr 23, 2023 by Aaro Uitto
PNG File bpb-right.PNG Apr 23, 2023 by Aaro Uitto
PNG File bpb-sled.PNG Apr 23, 2023 by Aaro Uitto
PNG File bpb-tightener.PNG Apr 23, 2023 by Aaro Uitto