Introduction: Robotic Camera Dolly System



Recently I received the opportunity to try out Servo City's Actobotics build system. While I could have used it to build a great many things, I ultimately decided to make a programmable camera dolly system. This device allows me to create time lapse films with complex movements as well as program repeatable automated real-time camera movements. In other words, this rig allows you to smoothly film complex camera movements in time lapse and real-time.

This project was sponsored by Servo City.

 

Step 1: Features

The Robotic Camera Dolly System has these key features:

  • The ability for real-time filming and stop motion photography.
  • It is ontrollable via an easy-to-use touch screen.
  • The system can pan, tilt and drive at the same time.
  • All actions are repeatable thanks to servo motors and a DC drive motor with a rotary encoder.
  • Thanks to the dynamic Actobotics build system and open source Arduino-based hardware, it is fully customizeable to meet your own needs.

Step 2: Materials

Step 3: Assemble the Idler Wheels

Insert the non friction ball bearings into one of the low friction skate wheels.

Pass the 8mm flanged standoff through the bearing.

Take a 90 degree quad hub mount D and insert center hole adapters on each side.

Pass a bolt through the two center hole adapters and fasten it firmly in place.

Repeat this process 6 more times.

Step 4: Idler Wheel Assemblies

Take two of the idler wheel assemblies and affix them to one of the dolly wheel idler plates.

Also attach a 90 degree quad hub mount B to the same dolly wheel idler plate.

Once all of these are attached, connect the other dolly wheel idler plate to the other side.

Build 3 of these assemblies in total.

Step 5: Attach a Connection Bracket

Attach a 90 degree quad hub mount C centered upon the outside of one of the dolly drive wheel plates on all 3 of the assemblies.

Step 6: Rotary Encoder

The rotary encoder needs to be attached the DC drive motor.

Start by peeling the adhesive cover from the plastic encoder frame and center this on the backside of the motor.

Next, press the encoder disk onto the thin shaft sticking out the back of the motor.

Finally, snap the plastic cover with the encoder attached to the plastic base.

Step 7: Wire the Motor

Attach a red wire to the DC motor's positive terminal and a black wire to the negative terminal.

Step 8: Attach Mount

Affix the motor mount D securely to the motor.

Step 9: Coupler

Attach the 6mm to 1/4” Set Screw Coupler to the motor shaft.

Step 10: Assemble the Drive Wheel

Install the drive wheel adapter kit B into one of the low friction skate wheels along with the 2.5" stainless steel D-shaft.

On the side of the wheel opposite the mounting screw for the adapter kit, slide on a center hole adapter, followed by a quad hub mount D bracket.

Step 11: Connect

Connect the 2.5" D shaft to the motor shaft using the coupling set screw.

Step 12: Attach a Connector

Attach a quad hub mount C centered upon one of the dolly drive wheel plates.

Step 13: Build a Drive Assembly

Attach the motor and drive wheel to the drive wheel plate along with an idler wheel and a quad hub mount B.

Install the other drive wheel plate on the other side once completed.

Step 14: Wheel Channels

Attach two idler wheel assemblies to the outside of a 12" aluminum channel.

Attach an idler wheel assembly and the motor drive assembly to the outside of another 12" aluminum channel.

Step 15: Structural Channels

Take three 12" aluminum channels and install a 90 degree quad mount C connector on each end.

Step 16: Assemble the Base

Connect the two aluminum wheel channels together with the three structural channels.

Make certain that there is 3" between each structural channel.

Step 17: 3.75" Channel

Attach a 90 degree quad hub mount B to the end of a 3.75" aluminum channel.

Step 18: Attach

Center the 3.75" aluminum channel perpendicular to the base and fasten it in place with mounting bolts.

In other words, center it on end upon the center support chanel.

Step 19: Acrylic Base Supports

Connect attachment blocks to the inside of each support channel such that they are all aligned and 7.82" apart legnthwise.

Step 20: Top Panel

Use the attached file as a guide to cut the top panel out of 1/8" acrylic.

Step 21: Install

Install the SPST on/off switch and M-type power jack into the appropriate holes in the acrylic top panel.

Step 22: Wire It Up

Wire the power jack to the DPDT power switch as specified in the schematic.

Step 23: Acrylic Base

Cut the base pieces out of 1/8" acrylic using the attached template.

Step 24: Prep the Battery

Zip tie the battery to the acrylic base with the set of four 0.2" holes in the center.

Trim away the plastic connector from the battery, leaving as much wire connected to the battery as possible.

Step 25: Connect the Battery

Connect the battery wires to the appropriate terminals on the power jack as specified in the schematic.

Step 26: Solder

Flip the proto shield over and insert the female header pins upwards into the appropriate slots on the shield.

Next, plug the touch screen shield into the female header sockets to ensure the pins are all being held straight.

Finally, quickly solder all of the pins in place. Avoid heating up any of the pins for too long to prevent any damage to the touch shield.

Step 27: Attach Wires

Start building the circuit as specified in the schematic.

Extend wires for the servos, the DC motor and rotary encoder from the proto shields, but do not yet connect them. Additionally, connect a shielded mono cable to the proto shield for the trigger plug, but also, do not yet wire the plug into place.

Step 28: Program the Arduinos

Upload this code to the Arduino controlling the touch shield:

// Camera Rig Touch Screen Controller

#include <Wire.h>

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <TouchScreen.h>

#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

#ifndef USE_ADAFRUIT_SHIELD_PINOUT 
 #error "This sketch is intended for use with the TFT LCD Shield. Make sure that USE_ADAFRUIT_SHIELD_PINOUT is #defined in the Adafruit_TFTLCD.h library file."
#endif

// These are the pins for the shield!
#define YP A1  // must be an analog pin, use "An" notation!
#define XM A2  // must be an analog pin, use "An" notation!
#define YM 7   // can be a digital pin
#define XP 6   // can be a digital pin

#ifdef __SAM3X8E__
  #define TS_MINX 125
  #define TS_MINY 170
  #define TS_MAXX 880
  #define TS_MAXY 940
#else
  #define TS_MINX  150
  #define TS_MINY  120
  #define TS_MAXX  920
  #define TS_MAXY  940
#endif

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0

// Assign human-readable names to some common 16-bit color values:
#define	BLACK   0x0000
#define	BLUE    0x001F
#define	RED     0xF800
#define	GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define LBLUE   0x65FF

Adafruit_TFTLCD tft;

int oldcolor, currentcolor;

bool pan = 1;
int panPosition1 = 20;
int panPosition2 = 20;

bool tilt = 0;
int tiltPosition1 = 20;
int tiltPosition2 = 20;

bool drive = 0;
int drivePosition1 = 20;
int drivePosition2 = 20;

bool time = 0;
int timePosition1 = 20;
int timePosition2 = 20;

int startSend = 0;

int x;

void setup(void) {
  Wire.begin();        // join i2c bus (address optional for master)

  Serial.begin(9600);

  tft.reset();

  uint16_t identifier = tft.readID();

  if(identifier == 0x9325) {
    Serial.println(F("Found ILI9325 LCD driver"));
  } else if(identifier == 0x9328) {
    Serial.println(F("Found ILI9328 LCD driver"));
  } else if(identifier == 0x7575) {
    Serial.println(F("Found HX8347G LCD driver"));
  } else {
    Serial.print(F("Unknown LCD driver chip: "));
    Serial.println(identifier, HEX);
    return;
  }

  tft.begin(identifier);
  tft.setRotation(1);
  tft.fillScreen(BLACK);


  drawMenu();

  tft.drawRect(0, 0, 58, 40, WHITE);


  //set initial slider states
  tft.drawRect(19, 89, 272, 6, WHITE);
  tft.fillRect(20, 90, 270, 4, YELLOW);
    
  tft.drawRect(panPosition1, 71, 17, 42, WHITE);
  tft.fillRect((panPosition1 + 1), 72, 15, 40, MAGENTA);
    
  tft.drawRect(19, 169, 272, 6, WHITE);
  tft.fillRect(20, 170, 270, 4, YELLOW);
    
  tft.drawRect(panPosition2, 151, 17, 42, WHITE);
  tft.fillRect((panPosition2 + 1), 152, 15, 40, MAGENTA);

  currentcolor = RED;
 
  pinMode(13, OUTPUT);
}

#define MINPRESSURE 10
#define MAXPRESSURE 1000

void loop(){
  
  digitalWrite(13, HIGH);
  Point p = ts.getPoint();
  digitalWrite(13, LOW);

  // if sharing pins, you'll need to fix the directions of the touchscreen pins
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);

  // we have some minimum pressure we consider 'valid'
  // pressure of 0 means no pressing!
  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {

    // scale from 0->1023 to tft.width
    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = map(p.y, TS_MINY, TS_MAXY, tft.height(), 0);
    
    
   
    //****************************
    // TOP MENU SELECTION CODE!!!!   
    //****************************
    
    if (p.x > 300) {
       oldcolor = currentcolor;

       if (p.y < 58) { 
         currentcolor = RED; 
         tft.drawRect(0, 0, 58, 40, WHITE);
         pan = 1;
         tilt = 0;
         drive = 0;
         time = 0;
         drawSliders();
      } else if (p.y < 140) {
         currentcolor = LBLUE;

         tft.drawRect(58, 0, 82, 40, WHITE);
         pan = 0;
         tilt = 1;
         drive = 0;
         time = 0;
         drawSliders();
       } else if (p.y < 180) {
         currentcolor = BLUE;

        tft.drawRect(140, 0, 96, 40, WHITE);
         pan = 0;
         tilt = 0;
         drive = 1;
         time = 0;
         drawSliders();
       } else if (p.y < 240) {
         currentcolor = GREEN;
        tft.drawRect(236, 0, 82, 40, WHITE);
         pan = 0;
         tilt = 0;
         drive = 0;
         time = 1;
         drawSliders();
       }
    }  

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//DO I NEED THIS AT ALL????????????????
//CAN IT JUST READ DRAW MENU??????????????

     if (oldcolor != currentcolor) {
        if (oldcolor == RED) drawMenu();
        if (oldcolor == LBLUE) drawMenu();
        if (oldcolor == GREEN) drawMenu();
        if (oldcolor == BLUE) drawMenu();
     }
       
       
     //****************************    
     // START SELECTION CODE!!!!   
     //****************************
       
     if (p.x < 40) {
       if (p.y > 180) {
        tft.drawRect(220, 200, 96, 40, WHITE);
        startSend = 1;
       }
     }
     
     
     //****************
     //SLIDER CODE!!!!!
     //****************

     //TOP SLIDER
     if (p.x > 180 && p.x < 260) {
       if (p.y < 350) {
         if(p.y >= 20){ // or so... for first black square...

         //need bottom remap and top remap!!!                  
         p.y = map(p.y, 0, 240, 0, 300);
         
         tft.fillRect(10, 71, (p.y - 10), 50, BLACK);
         }
         

         
         tft.drawRect((p.y - 1), 71, 17, 42, WHITE);
         tft.fillRect(p.y, 72, 15, 40, MAGENTA);
         tft.fillRect((p.y + 16), 71, (282 - p.y), 50, BLACK);
         
         tft.drawRect((p.y + 15), 89, (272 - p.y), 6, WHITE);
         tft.fillRect((p.y + 16), 90, (270 - p.y), 4, YELLOW);
         
         tft.drawRect(19, 89, (p.y - 19), 6, WHITE);
         tft.fillRect(20, 90, (p.y - 20), 4, YELLOW);
         
         
         if (pan == 1){
           panPosition1 = p.y;
           //  Serial.println(panPosition1);
         } else if(tilt == 1){
           tiltPosition1 = p.y;
         } else if(drive == 1){
           drivePosition1 = p.y;
         } else if(time == 1){
           timePosition1 = p.y;
         }
       }
     }
     
     
     //BOTTOM SLIDER
     if (p.x > 80 && p.x < 160) {
       if (p.y < 350) {
         if(p.y >= 20){ // or so... for first black square...
         
         //need bottom remap and top remap!!!                  
         p.y = map(p.y, 0, 240, 0, 300);
         
         tft.fillRect(10, 151, (p.y - 10), 50, BLACK);
         }
                  
         tft.drawRect((p.y - 1), 151, 17, 42, WHITE);
         tft.fillRect(p.y, 152, 15, 40, MAGENTA);
         tft.fillRect((p.y + 16), 151, (282 - p.y), 50, BLACK);
         
         tft.drawRect((p.y + 15), 169, (272 - p.y), 6, WHITE);
         tft.fillRect((p.y + 16), 170, (270 - p.y), 4, YELLOW);
         
         tft.drawRect(19, 169, (p.y - 19), 6, WHITE);
         tft.fillRect(20, 170, (p.y - 20), 4, YELLOW);
         
         
         if (pan == 1){
           panPosition2 = p.y;
           Serial.println(panPosition2);
         } else if(tilt == 1){
           tiltPosition2 = p.y;
         } else if(drive == 1){
           drivePosition2 = p.y;
         } else if(time == 1){
           timePosition2 = p.y;
         }
       }
     }  
     
     
     //**************************
     //TRANSMITS TO OTHER ARDUINO
     //**************************
     
     if(startSend == 1){
        transmitValues();
        startSend = 0;
        delay(2000);
        drawMenu();
        if (pan == 1) tft.drawRect(0, 0, 58, 40, WHITE);
        if (tilt == 1) tft.drawRect(58, 0, 82, 40, WHITE);
        if (drive == 1) tft.drawRect(140, 0, 96, 40, WHITE);
        if (time == 1) tft.drawRect(236, 0, 82, 40, WHITE);
      }
      
  }
}


void transmitValues(){
Wire.beginTransmission(2); // transmit to device #2
  x = panPosition1;
  Wire.write("a");        // sends five bytes
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting
  
  delay(100);
  
  Wire.beginTransmission(2); // transmit to device #2
  x = panPosition2;
  Wire.write("b");        // sends five bytes
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting

  delay(100);

  Wire.beginTransmission(2); // transmit to device #2
  x = tiltPosition1;
  Wire.write("c");
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting
  
  delay(100);
  
  Wire.beginTransmission(2); // transmit to device #2
  x = tiltPosition2;
  Wire.write("d");
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting

  delay(100);
  
   Wire.beginTransmission(2); // transmit to device #2
  x = drivePosition1;
  Wire.write("e");        // sends five bytes
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting
  
  delay(100);
  
  Wire.beginTransmission(2); // transmit to device #2
  x = drivePosition2;
  Wire.write("f");        // sends five bytes
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting

  delay(100);

  Wire.beginTransmission(2); // transmit to device #2
  x = timePosition1;
  Wire.write("g");
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting
  
  delay(100);
  
  Wire.beginTransmission(2); // transmit to device #2
  x = timePosition2;
  Wire.write("h");
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting

  delay(100);
  
  Wire.beginTransmission(2); // transmit to device #2
  x = startSend;
  Wire.write("i");
  Wire.write(x);
  Wire.endTransmission();    // stop transmitting
}



void drawSliders(){
  
  if (oldcolor != currentcolor) {
    tft.fillRect(10, 60, 300, 200, BLACK);
  }
  
  if(pan == 1){
    tft.drawRect(19, 89, 272, 6, WHITE);
    tft.fillRect(20, 90, 270, 4, YELLOW);
    
    tft.drawRect(panPosition1, 71, 17, 42, WHITE);
    tft.fillRect((panPosition1 + 1), 72, 15, 40, MAGENTA);
    
    tft.drawRect(19, 169, 272, 6, WHITE);
    tft.fillRect(20, 170, 270, 4, YELLOW);
    
    tft.drawRect(panPosition2, 151, 17, 42, WHITE);
    tft.fillRect((panPosition2 + 1), 152, 15, 40, MAGENTA);
  } else if(tilt == 1){
    tft.drawRect(19, 89, 272, 6, WHITE);
    tft.fillRect(20, 90, 270, 4, YELLOW);
    
    tft.drawRect(tiltPosition1, 71, 17, 42, WHITE);
    tft.fillRect((tiltPosition1 + 1), 72, 15, 40, MAGENTA);
    
    tft.drawRect(19, 169, 272, 6, WHITE);
    tft.fillRect(20, 170, 270, 4, YELLOW);
    
    tft.drawRect(tiltPosition2, 151, 17, 42, WHITE);
    tft.fillRect((tiltPosition2 + 1), 152, 15, 40, MAGENTA);
  } else if(drive == 1){
    tft.drawRect(19, 89, 272, 6, WHITE);
    tft.fillRect(20, 90, 270, 4, YELLOW);
    
    tft.drawRect(drivePosition1, 71, 17, 42, WHITE);
    tft.fillRect((drivePosition1 + 1), 72, 15, 40, MAGENTA);
    
    tft.drawRect(19, 169, 272, 6, WHITE);
    tft.fillRect(20, 170, 270, 4, YELLOW);
    
    tft.drawRect(drivePosition2, 151, 17, 42, WHITE);
    tft.fillRect((drivePosition2 + 1), 152, 15, 40, MAGENTA);
  } else if(time == 1){
    tft.drawRect(19, 89, 272, 6, WHITE);
    tft.fillRect(20, 90, 270, 4, YELLOW);
    
    tft.drawRect(timePosition1, 71, 17, 42, WHITE);
    tft.fillRect((timePosition1 + 1), 72, 15, 40, MAGENTA);
    
    tft.drawRect(19, 169, 272, 6, WHITE);
    tft.fillRect(20, 170, 270, 4, YELLOW);
    
    tft.drawRect(timePosition2, 151, 17, 42, WHITE);
    tft.fillRect((timePosition2 + 1), 152, 15, 40, MAGENTA);
  }
  
}


void drawMenu(){
  
  tft.fillRect(236, 0, 82, 40, GREEN);
  tft.fillRect(140, 0, 96, 40, BLUE);
  tft.fillRect(58, 0, 82, 40, LBLUE);
  tft.fillRect(0, 0, 58, 40, RED);

  tft.drawChar(2, 10, 'P', WHITE, RED, 3);
  tft.drawChar(20, 10, 'A', WHITE, RED, 3);
  tft.drawChar(38, 10, 'N', WHITE, RED, 3);
  
  tft.drawChar(64, 10, 'T', WHITE, LBLUE, 3);
  tft.drawChar(82, 10, 'I', WHITE, LBLUE, 3);
  tft.drawChar(100, 10, 'L', WHITE, LBLUE, 3);
  tft.drawChar(118, 10, 'T', WHITE, LBLUE, 3);
  
  tft.drawChar(144, 10, 'D', WHITE, BLUE, 3);
  tft.drawChar(162, 10, 'R', WHITE, BLUE, 3);
  tft.drawChar(180, 10, 'I', WHITE, BLUE, 3);
  tft.drawChar(198, 10, 'V', WHITE, BLUE, 3);
  tft.drawChar(216, 10, 'E', WHITE, BLUE, 3);

  tft.drawChar(242, 10, 'T', WHITE, GREEN, 3);
  tft.drawChar(260, 10, 'I', WHITE, GREEN, 3);
  tft.drawChar(278, 10, 'M', WHITE, GREEN, 3);
  tft.drawChar(296, 10, 'E', WHITE, GREEN, 3);
  
  tft.fillRect(220, 200, 96, 40, BLUE);
  tft.drawChar(224, 210, 'S', WHITE, BLUE, 3);
  tft.drawChar(242, 210, 'T', WHITE, BLUE, 3);
  tft.drawChar(260, 210, 'A', WHITE, BLUE, 3);
  tft.drawChar(278, 210, 'R', WHITE, BLUE, 3);
  tft.drawChar(296, 210, 'T', WHITE, BLUE, 3);
  
}
<br>

Upload this code to the Arduino that will be controlling the motor:

// Camera Rig Motor Controller


#include <Servo.h> 

#define ENC_A 14
#define ENC_B 15
#define ENC_PORT PINC

Servo panServo;
Servo tiltServo;

int distance = 0;

bool stateChange = 0;

int motorPin = 3;    // LED connected to digital pin 9

float driveX = 0;
float tiltX = 0;
float panX = 0;
float timeX = 0;

float driveZ = 0;
float tiltZ = 0;
float panZ = 0;
float picZ = 0;


int distanceToGo = 0;

//for program
int progStart = 0;

//for testing
//int progStart = 1;


int motorSpeed;

float totalTilt;
float totalPan;
float totalDrive;

float panIncrement;
float tiltIncrement;
float driveIncrement;

float picsPerPanIncrement = 1;
float picsPerTiltIncrement = 1;
float picsPerDriveIncrement = 1;

int panSteps = 0;
int tiltSteps = 0;
int driveSteps = 0;

char dataRx[20];

#include <Wire.h>

void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);  // start serial for output
    
  
  panServo.attach(4); 
  tiltServo.attach(5);  
  tiltServo.writeMicroseconds(1100);
  panServo.writeMicroseconds(1500);
  delay(10000);  

  pinMode(8, OUTPUT);     
    
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);

}

void loop(){
  
  if(progStart == 1){
     
    tiltX = map(tiltX, 0, 255, 750, 1450);
    tiltZ = map(tiltZ, 0, 255, 750, 1450);
    
    panX = map(panX, 0, 255, 600, 2200);
    panZ = map(panZ, 0, 255, 600, 2200);
    
    Serial.println(panX);
    Serial.println(panZ);
    
    if(timeX < 5){
      motorSpeed = 150;
      driveX = (driveX, 0, 255, 0, 180);
      timeX = 0;
    } else{
      motorSpeed = 75;
      driveX = (driveX, 0, 255, 0, 360);
    }
    
    totalPan = (panX - panZ);
    totalPan = abs(totalPan);
    totalTilt = tiltX - tiltZ;
    totalTilt = abs(totalTilt);
    totalDrive = driveX;
 
     Serial.println(totalPan);
   
    
    //***************************************************
    //figure out how far to advance before taking pics
    //alternately, how many pics to take before advancing
    //***************************************************
    
    //! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 
    //THESE NEED TO BE FUCKING FLOATS AND SHIT!
    //! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
    
    panIncrement = totalPan / picZ;
    if(totalPan < picZ){
      picsPerPanIncrement = 1 / panIncrement;
      panIncrement = 1;
    }
     
    tiltIncrement = totalTilt / picZ;
    if(totalTilt < picZ){
      picsPerTiltIncrement = 1 / tiltIncrement;
      tiltIncrement = 1;
    }
    
    driveIncrement = totalDrive / picZ;
    if(totalDrive < picZ){
      picsPerDriveIncrement = 1 / driveIncrement;
      driveIncrement = 1;
    }
    
    
    //**************************************
    //move servos into the initial positions
    //**************************************
    
    tiltServo.writeMicroseconds(tiltX);
    panServo.writeMicroseconds(panX);
    delay(10000);
    
    //**************************************
    //take some pictures and move some stuff
    //**************************************
    
    
    for (int takingPictures = 0; takingPictures < picZ; takingPictures++) { 
      
      
      if(panSteps >= picsPerPanIncrement){
        if(panX > panZ){
         panServo.writeMicroseconds(panX - panIncrement);
         panX = panX - panIncrement;
        }else if(panX < panZ){
          panServo.writeMicroseconds(panX + panIncrement);
          panX = panX + panIncrement;
        }else{
          panServo.writeMicroseconds(panX);
        }
         panSteps = 0;
      }
      
      
      
      if(tiltSteps >= picsPerTiltIncrement){
        if(tiltX > tiltZ){
         tiltServo.writeMicroseconds(tiltX - tiltIncrement);
         tiltX = tiltX - tiltIncrement;
        }else if(tiltX < tiltZ){
          tiltServo.writeMicroseconds(tiltX + tiltIncrement);
          tiltX = tiltX + tiltIncrement;
        }else{
          tiltServo.writeMicroseconds(tiltX);
        }
         tiltSteps = 0;
      }
            
      
      
      if(driveSteps >= picsPerDriveIncrement){
        analogWrite(motorPin, motorSpeed);
        while(distance < distanceToGo){
          callEncoder();
        }  
        analogWrite(motorPin, 0);
        driveSteps = 0;
      }
      
       distanceToGo = distanceToGo + driveIncrement;

      
      
      delay(timeX * 500);
      Serial.println("SHOOT!!!");
      
      //take picture
      digitalWrite(8, HIGH);   // connect the camera trigger
      delay(500);               // wait for a second
      digitalWrite(8, LOW);    // disconnect the camera trigger
      delay(500); 
      
      panSteps = panSteps + 1;
      tiltSteps = tiltSteps + 1;
      driveSteps = driveSteps + 1;
      
      progStart = 0;
    }
  } 
}




// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void receiveEvent(int holder)
{
  
  int index = 0;
  while(1 < Wire.available()) // loop through all but the last
  {
    char c = Wire.read(); // receive byte as a character
 //   Serial.print(c);         // print the character
    
    dataRx[index++] = c;
    dataRx[index] = '\0';
    
  }
  int x = Wire.read();    // receive byte as an integer
 // Serial.println(x);         // print the integer
  
  if(*dataRx == 'a'){
    panX = x;
    
    Serial.print("OMG PARSING PANX: ");
    Serial.println(panX);
  } else if(*dataRx == 'b'){
    panZ = x;
    
    Serial.print("OMG PARSING PANZ: ");
    Serial.println(panZ);
  } else if(*dataRx == 'c'){
    tiltX = x;
    
    Serial.print("OMG PARSING TILTX: ");
    Serial.println(tiltX);
  } else if(*dataRx == 'd'){
    tiltZ = x;
    
    Serial.print("OMG PARSING TILTZ: ");
    Serial.println(tiltZ);
  } else if(*dataRx == 'e'){
    driveX = x;
    
    Serial.print("OMG PARSING DRIVEX: ");
    Serial.println(driveX);
  } else if(*dataRx == 'f'){
    driveZ = x;
    
    Serial.print("OMG PARSING DRIVEZ: ");
    Serial.println(driveZ);
  } else if(*dataRx == 'g'){
    timeX = x;
    
    Serial.print("OMG PARSING TIMEX: ");
    Serial.println(timeX);
  } else if(*dataRx == 'h'){
    picZ = x;
    
    Serial.print("OMG PARSING PICZ: ");
    Serial.println(picZ);
  } else if(*dataRx == 'i'){
    progStart = x;
    
    Serial.print("OMG PARSING START: ");
    Serial.println(progStart);
  } 
  
  
}


/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
  static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  /**/
  old_AB <<= 2;                   //remember previous state
  old_AB |= ( ENC_PORT & 0x03 );  //add current state
  return ( enc_states[( old_AB & 0x0f )]);
}


void callEncoder(){
  //         analogWrite(motorPin, 0);
//delay(1000);      
//
// //30Dst @ 150Sp
// //60Dst @ 75Sp
// //60Dst @ 50Sp
//
// while(distance < 240){
//       analogWrite(motorPin, 75);         
//}
   
 static uint8_t counter = 0;      //this variable will be changed by encoder input
 int8_t tmpdata;
 /**/
  tmpdata = read_encoder();
  if( tmpdata ) {
  //  Serial.print("Counter value: ");
  //  Serial.println(counter, DEC);
    counter += tmpdata;
    
    if(counter == 255){
      stateChange = 1;
    }
    
    if(counter == 0){
      if(stateChange == 1){
        stateChange = 0;
        distance += 1;
        
        Serial.print("increments: ");
        Serial.println(distance, DEC);
      }
    }    
  }
}
<br>

Step 29: Stack It

Stack the proto shield atop the Arduino, and then the touch screen atop the proto shield.

Step 30: Attach to Base

Bolt the Arduinos to the mounting holes on the acrylic base.

This may require unplugging and replugging the shields.

Step 31: Reset Switch

Insert the reset switch through one of the holes in the outer support channel and fasten it in place with its mounting hardware.

Attach two wire to the switch that make electrical contact when the switch is closed.

Step 32: Plug

Plug the 4-wire harness into the connector on the rotary encoder.

Step 33: Wire the DC Motor

Connect the power from the DC motor to wires coming off of the TIP120 transistor.

Connect the rotary encoder to the motor control Arduino as follows:

  • Red to 5V power
  • Black to Ground
  • Yellow to Pin A0
  • Green to Pin A1

Step 34: Attach the Acrylic

Attach the assembled acrylic bases to the aluminum drive assembly.

Step 35: Put on the Cover

Pass the shielded mono wire and the Arduino servo wires up through the center hole of the central 90 degree quad hub mount B, if you have not done so already.

Once the wires are passed through, slide the acrylic top plate down atop the drive assembly and fasten it into place with nuts and bolts.

Step 36: Plug

Once the shielded mono wire is passed up through the base assembly, attach a 3/32" mono plug on the end (or an adapter appropriate for your DSLR trigger).

Step 37: First Servo Assembly

Remove the gear from the HS-5485HB winch servo assembly.

Attach the servo assembly to one end of a 4.5" aluminum channel.

Install a 90 degree quad hub B mount into the other end of the channel.

Step 38: Make an L-assembly

Affix a 6" aluminum channel to the 90 degree quad hub B-mount affixed to the aluminum channel with the servo attached to form an L-shape.

Step 39: Another Servo

Attach the HS-785HB servo assembly to the end of the 6" aluminum extrusion opposite the connection on the L-bracket.

Step 40: Affix

Attach a 90 degree quad hub mount D bracket to the center of the gear on the HS-785B winch servo assembly.

Next, affix the 90 degree quad hub mount D bracket to the inner top of the aluminum extrusion protroduing perpendicular from the base.

Step 41: Finish Wiring

Connect together the servo wires to the extension servo wires from the Arduino.

Make any other loose connections as specified in the schematic.

Bundle and zip tie all of the wires neatly together.

Step 42: Camera Assembly Base

Take a dual flat channel bracket and connect a 90 degree quad hub mount D bracket to one side, and a 0.25" hub spacer to the other side.

Step 43: Angle Bracket

On the other side of the dual flat channel bracket and on the same face as the 0.25" hub spacer, connect a 90 degree hub to hub mount.

Step 44: Single Servo Arm

Attach the 3" power servo single gearbox arm to the free side of the hub to hub mount.

Step 45: Attach the Camera Plate

Place the gear back onto the shaft of the HS-5485HB winch servo assembly. Also place on the 0.77" hub adapter.

Finally, attach the top of the power servo arm to the 0.77" hub.

Step 46: Mount the Camera

Use the attached file as a template for cutting a 1/8" rubber ring to place atop the hub spacer.

Insert a 3/8" bolt up through the quad hub mount and hub spacer to lock the camera in place.

Fasten the camera to the assembly.

Step 47: Insert Plug

Insert the trigger plug into the camera remote trigger jack.

Step 48: Build the Track

The track system can be made of any legnth of 3/4" pipe that you would like. It can also be built in most any way so long as the track is 13.5" apart and nothing collides with the wheels.

I used aluminum rods that I found at the hardware store, but you can also use PVC or wooden dowel rods.

To create the frame for the rods, I used the attached 3D model to print connector brackets, and the 2D layout file to create wooden spacers.

Once I had these parts fabricated, I then drilled 1/8" holes in the pipe every 11.5" and used these to build the frame.

Step 49: Using the Touch Screen

The touch screen interface has four menu options:

Pan - Sliders for setting in and out points

Tilt - Sliders for setting in and out points.

Drive - The top slider sets only the stopping point

Start - The top slider sets time interval and the bottom sets number of pictures to take.

Comments

author
SMH_Cinema (author)2016-05-04

Hola Randy! I am building this with my Cinema students in collaboration with our Robotics Club. We can't find the attached template to cut the acrylic for the top and base. Help!

:)

Thanks so much!!!

@SMH_CINEMA

author
alcurb (author)2015-08-06

Great job, man.

What do you think the project would cost for us mere mortals?

author
ASCAS (author)2014-07-18

Kudos to Randy! And man I need one of those, anyone willing to sponsor me? LOLjk :D

author
shellalbert (author)2014-07-17

so great robot!

author
shellalbert (author)2014-07-17

so great robot!

author
shellalbert (author)2014-07-17

so great robot!

author
hunter999 (author)2014-04-27

This is an excellent, well documented instructable. Awesome job Randy! :D

author
Jan_Henrik (author)hunter9992014-05-09

Agree!!!

author
awawawaw (author)2014-04-23

Excellent Instructable! Very detailed and well ordered steps that make it easy to follow. Nice, well lit photos. I do have one question. I don't see anything in your code that indicates why you need 2 Arduinos to run this instead of just one. Is it code size or easier having them separate since you are using off the shelf code?

author
randofo (author)awawawaw2014-04-24

The touch shield uses just about all of the pins of the first Arduino. It just made like easier to seperate them, rather than trying to work around the pin shortage.

author
stevenarango (author)2014-04-21

This is by far the most interesting instructable i have seen. well done

great pictures and instructions.

You should put " As a KIT" i Buy one......

author
sensoryhouse (author)2014-04-19

Killer! Here is mine. It uses the Servocity PT-2100

openmoco.jpg
author
randofo (author)sensoryhouse2014-04-19

Wow! That is a really nice setup. I like how it can travel on an incline. That's pretty cool.

author
SteveDonie (author)2014-04-18

The Actobotics components look very nice. What is the approximate total retail price of the components you used?

author
TraciAG (author)2014-04-16

This is excellent! If I had the money I'd definitely make this, but I spend money faster like it's nothing

*sigh* Oh well..a girl can dream ;_;

author
giocad (author)2014-04-15

fantastic! but how many money is cost?

author
randofo (author)giocad2014-04-16

Have not calculated the total cost. At the very least, much cheaper than buying a professional model that does this.

author
amandaghassaei (author)2014-04-14

wow, nice work randy. and I made it in the video! good choice.

About This Instructable

68,054views

615favorites

License:

Bio: My name is Randy and I founded the Instructables Design Studio. I'm also the author of the books 'Simple Bots,' and '62 Projects to ... More »
More by randofo:Adjustable BookshelfSponge BotSingle Motor Bot
Add instructable to: