Robotic Camera Dolly System

by randofo in Circuits > Cameras

74889 Views, 641 Favorites, 0 Comments

Robotic Camera Dolly System

main1.jpg

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.

Features

main3.jpg
main2.jpg

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.

Materials

You will need:

(x1) Motor with rotary encoder
(x1) HS-785HB winch servo assembly
(x1) HS-5485HB winch servo assembly
(x3) Dolly Free Wheel Idler Plates (pair)
(x12) 90° Quad Hub Mount D
(x10) 90° Quad Hub Mount C
(x5) 90° Quad Hub Mount B
(x8) 2.975” Low Friction Skate Wheels
(x16) 8mm Non-Flanged Ball Bearings
(x4) Center Hole Adaptors (4 pk)
(x7) 8mm Flanged Standoff
(x1) Dolly Drive Wheel Plates (pair)
(x1) Motor Mount D (3mm screws included)
(x1) 6mm to 1/4” Set Screw Coupler
(x1) 2.5” Stainless Steel D-Shaft
(x1) Drive Wheel Adaptor Kit B
(x1) 1/4” ID (1/2” OD) Flanged Ball Bearing
(x5) 12" aluminum channel
(x1) 3.75" aluminum channel
(x1) 4.5" aluminum channel
(x1) 6" aluminum channel
(x8) Attachment blocks (1 pack)
(x1) Dual flat channel bracket
(x1) 0.25" hub spacer
(x1) 90 degree hub to hub mount
(x1) 3" power servo single gearbox arm
(x1) 3" x 3" x 1/8" santoprene rubber
(x2) 24" x 36" x 1/4" plywood
(x2) 3/4" x 8' rods
(x1) Heavy Duty ON/OFF switch
(x1) DC power jack
(x1) DC power plug
(x1) 12V Ni-MH rechargeable battery
(x2) 1.5K resistors
(x1) 1K resistor
(x1) 5V SPST relay
(x1) SPST momentary pushbutton switch
(x1) Mono audio cable
(x1) TIP120 transistor
(x1) 1N4004 diode
(x1) 3/32" mono plug (or appropriate camera trigger plug)
(x2) Arduino Uno
(x1) Arduino 2.8" touch shield
(x2) Arduino proto shield
(x1) 36" x 24" x 1/8" acrylic

(Some of the links on this page contain Amazon affiliate links. This does not change the price of any of the items for sale. However, I earn a small commission if you click on any of those links and buy anything. I reinvest this money into materials and tools for future projects. If you would like an alternate suggestion for a supplier of any of the parts, please let me know.)

Assemble the Idler Wheels

27A.jpg
27B.jpg
27C.jpg
27D.jpg
27E.jpg

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.

Idler Wheel Assemblies

28A.jpg
28B.jpg
28C.jpg
28D.jpg

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.

Attach a Connection Bracket

29B.jpg
29C.jpg
29D.jpg

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.

Rotary Encoder

2A.jpg
2B.jpg
2C.jpg
2D.jpg
2E.jpg
2F.jpg
2G.jpg
2H.jpg

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.

Wire the Motor

20A.jpg

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

Attach Mount

21A.jpg
21B.jpg
21C.jpg

Affix the motor mount D securely to the motor.

Coupler

22A.jpg
22B.jpg
22C.jpg

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

Assemble the Drive Wheel

23A.jpg
23B.jpg
23C.jpg
23D.jpg
23E.jpg

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.

Connect

24A.jpg
24B.jpg
24C.jpg
24D.jpg

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

Attach a Connector

26A.jpg
26B.jpg

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

Build a Drive Assembly

25A.jpg
25B.jpg
25C.jpg
25D.jpg
25E.jpg

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.

Wheel Channels

30A.jpg
30B.jpg
30C.jpg
30D.jpg
30E.jpg

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.

Structural Channels

31A.jpg
31B.jpg
31C.jpg
31D.jpg

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

Assemble the Base

32A.jpg
32B.jpg
32C.jpg
32D.jpg
32E.jpg

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

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

3.75" Channel

33A.jpg
33B.jpg
33C.jpg

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

Attach

34A.jpg
34B.jpg
34C.jpg
34D.jpg

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.

Acrylic Base Supports

35A.jpg
35B.jpg
35C.jpg

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

Top Panel

7A.jpg
7B.jpg
7C.jpg

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

Install

8A.jpg
8B.jpg
8C.jpg

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

Wire It Up

9A.jpg
schematic1.png

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

Acrylic Base

10A.jpg

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

Prep the Battery

11A.jpg
11B.jpg
11C.jpg
11D.jpg
11E.jpg

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.

Connect the Battery

12A.jpg
schematic1.png

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

Solder

4A.jpg
4B.jpg
4C.jpg
4D.jpg
4E.jpg

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.

Attach Wires

5A.jpg
5B.jpg
schematic1.png

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.

Program the Arduinos

50A.jpg

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>

Stack It

6A.jpg
6B.jpg

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

Attach to Base

13A.jpg
13B.jpg
13C.jpg

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

This may require unplugging and replugging the shields.

Reset Switch

14A.jpg
14B.jpg
14C.jpg
14D.jpg

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.

Plug

3A.jpg
3B.jpg

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

Wire the DC Motor

16A.jpg
16B.jpg
16C.jpg

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

Attach the Acrylic

17A.jpg
17B.jpg
17C.jpg
17D.jpg
17E.jpg

Attach the assembled acrylic bases to the aluminum drive assembly.

Put on the Cover

18A.jpg
18B.jpg
18C.jpg
18D.jpg

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.

Plug

36A.jpg

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).

First Servo Assembly

37A.jpg
37C.jpg
19A.jpg
37B.jpg

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.

Make an L-assembly

38A.jpg
38B.jpg
38C.jpg
38D.jpg

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.

Another Servo

39A.jpg
39B.jpg
39C.jpg

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

Affix

40A.jpg
40B.jpg
40C.jpg

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.

Finish Wiring

41A.jpg
41B.jpg
41C.jpg
41D.jpg
41E.jpg
schematic1.png

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.

Camera Assembly Base

42A.jpg
42B.jpg

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.

Angle Bracket

43B.jpg
43A.jpg

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.

Single Servo Arm

44A.jpg
44B.jpg

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

Attach the Camera Plate

45A.jpg
45B.jpg
45C.jpg
45D.jpg
45E.jpg
45F.jpg

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.

Mount the Camera

46A.jpg
46B.jpg
46C.jpg
46D.jpg

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.

Insert Plug

47A.jpg

Insert the trigger plug into the camera remote trigger jack.

Build the Track

48A.jpg
48B.jpg
48C.jpg
48D.jpg
48E.jpg
48F.jpg
48G.jpg
48H.jpg
48I.jpg
48J.jpg
48K.jpg
48L.jpg
48M.jpg

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.

Using the Touch Screen

49A.jpg

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.

Did you find this useful, fun, or entertaining?
Follow @madeineuphoria to see my latest projects.