Wesley Eccles
Published © MIT

Automatic Dog Ball Launcher

Let your dog play fetch with themself even while you are away!

IntermediateShowcase (no instructions)1,700
Automatic Dog Ball Launcher

Things used in this project

Hardware components

Teensy 3.1
Teensy 3.1
Micro Controller
×1
0.96" OLED 64x128 Display Module
ElectroPeak 0.96" OLED 64x128 Display Module
Shows how many balls have been launched
×1
DC Motor, 12 V
DC Motor, 12 V
×1
Philips hue
Philips hue
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1
Grove - Relay
Seeed Studio Grove - Relay
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fusion
Autodesk Fusion

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Laser cutter (generic)
Laser cutter (generic)
Soldering iron (generic)
Soldering iron (generic)
CNC Router

Story

Read more

Custom parts and enclosures

STL of Dog Ball Launcher

STL File for enclosure and mechanism

Schematics

Wiring Diagram

Schematic

Code

Code

C/C++
#include <SPI.h>
#include <Ethernet.h>
#include <mac.h>
#include  <Encoder.h>
#include  <OneButton.h>
#include  "wemo.h"
#include <PWMServo.h>
# include <Adafruit_NeoPixel.h>
# include <colors.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <hue.h>

//Display 
  #define SCREEN_WIDTH 128 // OLED display width, in pixels
  #define SCREEN_HEIGHT 64 // OLED display height, in pixels
  
  #define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
  #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
  Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// HUE LIGHTS
int   hueRedGreenBlue[]= {HueRed , HueRed, HueYellow,HueGreen,HueGreen};

//Ethernet 
  bool status;

// RANGE
  enum  State {
    FAR,
    MED,
    SHORT,
    AUTO
  };
 State Mode;

//RELAY
  const int Relay  =17;
  const int MotorDelay=2000;
  int  ballCount;

//NEOPIXEL
 const int LEDSTRIP= 7;
 const int PIXELCOUNT = 30;
  Adafruit_NeoPixel pixelStrip (PIXELCOUNT ,LEDSTRIP ,NEO_GRB + NEO_KHZ800 ); 
  const int colorArray[] ={red, orange, yellow, green,cyan, blue, indigo, violet,   red, orange, yellow, green,cyan, blue, indigo,violet,red, orange, yellow, green,cyan, blue, indigo,violet};



//SERVO
  PWMServo myServo;
  const int SERVOPIN= 20;
    int servoPos, ServoDelay,ServoCloseVal;

//LIGHT SENSOR
  const int LIGHTDIODE  = 23;
  const float ThresholdPercentage= .3; //How Sensitive is the cutoff for triggering the ball 
    int pinNumThreshold[13]; //Setting up Thredshold Values 
    int  diodeVal, currentAve, thresholdVal, j,PinAve;
    float aveDiodeVal;


//ONE BUTTON
  const int BUTTON =8;
  
  OneButton button1 (BUTTON, LOW, LOW);
  int lastPositon, currentPositon,mapPos , finalPosition  ,position; 

//TIMER
  int TimeDelay = 6000;
  unsigned int timerStart;
  bool fanIsRunning;

//OLED

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000100, B00010000,
  B00000100, B00010000,
  B01000100, B10010001,
  B01000101, B01010001,
  B01000010, B00100001,
  B01000100, B00010001,
  B01001000, B00001001,
  B01010000, B00000101,
  B00100000, B00000010,
  B01000000, B00000001,
  B00100000, B00000010,
  B00010000, B00000100,
  B00001000, B00001000,
  B00000100, B00010000,
  B00000010, B00100000,
  B00000001, B10000000, };

  
  
void setup() {
  Serial.begin(9600);
  delay(200);

//SERVO
  myServo.attach(SERVOPIN);
  myServo.write(70);                        //Set the Servo to prevent ball from falling 

//Setup PinModes                            // ensure all SPI devices start off
  pinMode(10, OUTPUT);
  digitalWrite(10, HIGH);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);

//ENCODER LEDS AND SWITCH
  pinMode(BUTTON, INPUT);
  pinMode(Relay, OUTPUT);

//ONE BUTTON
  Mode = 3;
  button1.attachClick(click1);
  button1.setClickTicks (250) ;
  button1.setPressTicks (2000) ;
  
//Ethernet Connection
  status = Ethernet.begin(mac);
  if (!status) {
    Serial.printf("failed to configure Ethernet using DHCP \n");
    //no point in continueing 
   //while(1);
    }
  //print your local IP address
  Serial.print("My IP address:");
  for (byte thisbyte = 0; thisbyte < 4; thisbyte++) {
    //print value of each byte of the IP address
    Serial.print(Ethernet.localIP()[thisbyte], DEC);
    if (thisbyte < 3) Serial.print(".");
    }
  Serial.println();

//OLED DISPLAY
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    
  }
    display.clearDisplay();
    display.display();
    display.setRotation(0);
    
    //SHOW THAT ITS CALIBRATING
    display.setTextSize(2);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0,5);  
    display.printf("SETTING UP");
    display.display();
    delay(1000);
    
    testdrawbitmap();
    
        


    
  
//Light Sensor
 pinMode(LIGHTDIODE, INPUT);
  thresholdVal = averageSetup(LIGHTDIODE,300);      //Take threshold Average For the Light sensor
     Serial.print(" Threadshold Set with a val of: ");
     Serial.println(thresholdVal);
    
 
//LED Lights
  pixelStrip.begin ();
  pixelStrip.clear ();
  pixelStrip.show ();
  pixelStrip.setBrightness (100);

  for (int i=0; i<3; i++){                        // Flash Green to show that it is calibrated and ready to go! 
    pixelStrip.fill (lime,0,30);
    pixelStrip.show ();
      delay(250);
    pixelStrip.clear ();
    pixelStrip.show ();
      delay(200);
   }

//Ball Count 
  ballCount=0;
  SETUPPrintOLED() ;
  PrintBallCountToOLED(ballCount, Mode);

//TIMER
    switchOFF(1); //TURNS OFF WEMO FAN
    switchOFF(2); //TURNS OFF WEMO FAN 
    fanIsRunning=false;                  
}

void loop() {
 // delay(2);
  button1.tick ();                                //Check Button for pressed
  
  PinAve=average(LIGHTDIODE, 5);                  //Average the light sensor 5 times

  //Serial.println (fanIsRunning);

  
  if (isTimerReady()){                            //Check the timer to see if its done to turn off fans 
    switchOFF(1); //TURNS OFF WEMO FAN
    switchOFF(2); //TURNS OFF WEMO FAN 
    fanIsRunning=false;   
  }
  
  //Serial.println (Mode); 
  
  
  if (CompareToThreshhold(PinAve, LIGHTDIODE)){ //Check the current average against the threshhold for TRUE 
    
    for (int k=1; k<=5; k++){                         //Turn ON and OFF hue lights Red > Yellow> Green 
        setHue(k,true,hueRedGreenBlue[k-1],255,255);
        delay (75);
        setHue(k,false,hueRedGreenBlue[k-1],0,0);
          Serial.println ("Lights turning off");
     }
     
        turnOnMotor();
        MoveServo(ServoDelay);  
        PrintBallCountToOLED(ballCount,Mode);  
        Rainbow();
  }
  
   switch (Mode) {
      case FAR: 
            ServoDelay= 750;
            ServoCloseVal=80;
              pixelStrip.fill (red,0,30);
              pixelStrip.show ();
              //Serial.println ("FAR RANGE MODE"); 
         break;
          
      case MED:
            ServoDelay= 250;
            ServoCloseVal=80;
              pixelStrip.fill (yellow,0,30);
              pixelStrip.show ();   
              //Serial.println ("MED RANGE MODE");                     
         break;
          
      case SHORT: 
            myServo.write(180);
            ServoDelay= 0;
            ServoCloseVal=180;
              pixelStrip.fill (lime,0,30);
              pixelStrip.show ();       
              //Serial.println ("SHORT RANGE MODE");       
         break;
          
      default:
            
            pixelStrip.fill (blue,0,30);
            pixelStrip.show (); 
            //Serial.println ("AUTOMATIC MODE");     
          if (ballCount <=3){
            myServo.write(80);
              ServoDelay= 750;
              ServoCloseVal=80;
                display.setTextSize(1);             // Normal 1:1 pixel scale
                display.setTextColor(SSD1306_WHITE);        // Draw white text
                display.setCursor(90,55);  
                display.printf("FAR" );
                display.display();
              
          }
          if (ballCount >3 && ballCount<6){
              ServoDelay= 250;
              ServoCloseVal=80;
                display.setTextSize(1);             // Normal 1:1 pixel scale
                display.setTextColor(SSD1306_WHITE);        // Draw white text
                display.setCursor(90,55);  
                display.printf("MED" );
                display.display();             
         
          }
                    
          if (ballCount ==6 ){
              ServoDelay= 0;
              ServoCloseVal=180;

               if (!fanIsRunning){
                 timerStart=millis();
                 switchON(1); //TURNS ON WEMO FAN
                 switchON(2); //TURNS ON WEMO FAN 
                 fanIsRunning=true;  
                display.setTextSize(1);             // Normal 1:1 pixel scale
                display.setTextColor(SSD1306_WHITE);        // Draw white text
                display.setCursor(90,55);  
                display.printf("SHORT" );
                display.display();  
               } 
          }          
         
          if (ballCount >6 ){
              ServoDelay= 0;
              ServoCloseVal=180; 
                display.setTextSize(1);             // Normal 1:1 pixel scale
                display.setTextColor(SSD1306_WHITE);        // Draw white text
                display.setCursor(90,55);  
                display.printf("SHORT" );
                display.display();  
          }          
          break;
      }
  }


 



//ONEBUTTON 
void click1 () {
  
  Mode= Mode+1 ;
  Serial.println(Mode);
  if (Mode==4){
       Mode=0;  
   }
  if (Mode==3){
      ballCount=0; 
   }
    PrintBallCountToOLED(ballCount,Mode);
    pixelStrip.clear (); 
 }

void turnOnMotor(){
   for (int i=0;i<5; i++){                 //Pulse the motor on off quicky to turn it on
    digitalWrite(Relay, HIGH);
    delay(15);
    digitalWrite(Relay, LOW);
    delay(10);
   }
    digitalWrite(Relay, HIGH);              //Keep motor on until MoveServo turns it off
    
}

int averageSetup (int _pinNum , int _iterations) {
    unsigned int sum;
    sum=0; 
 for (int i = 0 ; i < _iterations ; i++){
    sum=sum + analogRead(LIGHTDIODE);   
    delay(1);
 }

  return  (sum / _iterations) ;  
}

int average (int pinNum , int iterations) {
  unsigned int sum, ave;
  sum=0; 
    for (int i = 0 ; i < iterations ; i++){
       sum = sum + analogRead(LIGHTDIODE);
       delay(1);
    }
   ave= (sum / iterations);
  return ave ;  
}

bool CompareToThreshhold(int _currentAve,int _ThresholdPos){
  if (_currentAve<(thresholdVal * ThresholdPercentage)){
      Serial.print(" Ball is present with current Average = ");
      Serial.println (_currentAve);
    return true;
  }
    
  else {
    return false;
  }
}

void MoveServo(int _servoDelay){ 
  delay (_servoDelay);                    //Determines how far the ball will launch
  myServo.write(180);
  delay (500);                            //Wait to turn off motor until ball has shot
  digitalWrite(Relay, LOW);               //Turn off motor
  delay(500);     
  myServo.write(ServoCloseVal);           //Close the Servo to reset 
  ballCount++;
}

void PrintBallCountToOLED(int _ballCount,int _Mode) {
  display.clearDisplay();

  display.setTextSize(2);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,5);  

  display.printf("Ball Count" );
  display.display();
 
  display.setTextSize(3);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(50,30);  

  display.printf("%i",_ballCount );
  display.display();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(10,55);  
  
  if (_Mode==0){                      //Show which mode it's i
    display.printf("FAR" );
    display.display();
    }
  if (_Mode==1){
    display.printf("MED" );
    display.display();
    } 
  if (_Mode==2){
    display.printf("SHORT" );
    display.display();
    } 
  if (_Mode==3){
    display.printf("AUTO" );
    display.display();
    } 
    
  delay(1);
}

void SETUPPrintOLED() {

  display.clearDisplay();

  display.setTextSize(2);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(10,5);  

  display.printf("READY TO" );
   
  display.setTextSize(3);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(40,30);  

  display.printf("GO!" );
  display.display();
  delay(1000);
}

bool isTimerReady() {
      //Serial.println("checking Timer");
       
      if ((millis() -  timerStart) > TimeDelay){
        return true;
      }
      else {
        return false;
      }
    }

bool isFanRunning(){
  if (fanIsRunning){
    return true;
  }
  else{
    return false;
  }
  
}

void Rainbow(){
 for (int l=0; l<5;l++){
  for (int j=0; j<25;j++){
    for (int i=0; i<25; i++){
      pixelStrip.setPixelColor(i,colorArray[(i+j)/3]);
    }
      pixelStrip.show();
      delay(10);
  }
 }
}



void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

Automatic Dog Ball Launcher

Arduino IDE Code for Teensy 3.2

Credits

Wesley Eccles

Wesley Eccles

3 projects • 22 followers
Growing up I have always considered myself to be a maker. Over the years, I've built countless inventions from go-karts to a CNC Router.

Comments