├── README.md └── CameraSlider └── CameraSlider.ino /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoCameraSlider 2 | An Arduino Pro Mini based 2-axis camera slider using TMC2208 drivers 3 | 4 | License: CC BY-NC-SA 5 | 6 | The original project guide can be found here - https://www.the-diy-life.com/diy-motorised-camera-slider-with-object-tracking/ 7 | 8 | The project uses a 5V Arduino Pro Mini to control two TMC2208 stepper motor drivers, one to pan the camera and one to rotate it. Inputs are made through a rotary pushbutton and the menus and parameter inputs are shown on a 128x64 I2C OLED display. The project guide includes a download for the PCB manufacturing files, this allows the components to be soldered onto a single PCB for a more robust and reliable build. 9 | 10 | Please ask any project related questions or questions relating to the hardware and build process on the project guide. 11 | -------------------------------------------------------------------------------- /CameraSlider/CameraSlider.ino: -------------------------------------------------------------------------------- 1 | //The DIY Life 2 | //Michael Klements 3 | //13 January 2021 4 | //License: CC BY-NC-SA 5 | 6 | #include //Import libraries to control the OLED display 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 13 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels 14 | 15 | #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 16 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) 17 | 18 | // 'The DIY Life', 128x64px 19 | const unsigned char logo [] PROGMEM = { 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x01, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xfc, 0x07, 0xfc, 0x00, 0x00, 0x7f, 0xc0, 0x00, 29 | 0x00, 0x02, 0x03, 0x41, 0xfe, 0x00, 0x00, 0x03, 0xfc, 0x07, 0xfe, 0x00, 0x00, 0x5f, 0xc0, 0x00, 30 | 0x00, 0x03, 0x33, 0x47, 0xff, 0x80, 0x00, 0x03, 0xfc, 0x03, 0xfe, 0x00, 0x00, 0x9f, 0xc0, 0x00, 31 | 0x00, 0x03, 0x30, 0x43, 0xff, 0xe0, 0x00, 0x03, 0xfc, 0x03, 0xff, 0x00, 0x00, 0x37, 0x80, 0x00, 32 | 0x00, 0x03, 0x30, 0x43, 0xff, 0xf8, 0x00, 0x03, 0xfc, 0x01, 0xff, 0x00, 0x01, 0x04, 0x00, 0x00, 33 | 0x00, 0x03, 0x33, 0x47, 0xff, 0xfc, 0x00, 0x03, 0xfc, 0x01, 0xff, 0x80, 0x01, 0x80, 0x00, 0x00, 34 | 0x00, 0x03, 0x33, 0x41, 0xff, 0xfe, 0x00, 0x03, 0xfc, 0x00, 0xff, 0x80, 0x02, 0x61, 0x00, 0x00, 35 | 0x00, 0x03, 0xfe, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xfc, 0x00, 0xff, 0xc0, 0x02, 0xd8, 0x00, 0x00, 36 | 0x00, 0x03, 0xfe, 0x00, 0x1f, 0xff, 0x80, 0x03, 0xfc, 0x00, 0x7f, 0xc0, 0x04, 0x1e, 0x00, 0x00, 37 | 0x00, 0x03, 0xfe, 0x00, 0x07, 0xff, 0x80, 0x03, 0xfc, 0x00, 0x7f, 0xe0, 0x04, 0x1c, 0x00, 0x00, 38 | 0x00, 0x03, 0xfe, 0x00, 0x01, 0xff, 0xc0, 0x03, 0xfc, 0x00, 0x3f, 0xe0, 0x03, 0x04, 0x00, 0x00, 39 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0xff, 0xc0, 0x03, 0xfc, 0x00, 0x3f, 0xf0, 0x00, 0xc0, 0x00, 0x00, 40 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x7f, 0xe0, 0x03, 0xfc, 0x00, 0x1f, 0xf0, 0x0c, 0x38, 0x00, 0x00, 41 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x7f, 0xe0, 0x03, 0xfc, 0x00, 0x1f, 0xf0, 0x1f, 0x10, 0x00, 0x00, 42 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x0f, 0xf8, 0x0f, 0x90, 0x00, 0x00, 43 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x0f, 0xf8, 0x23, 0x20, 0x00, 0x00, 44 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x07, 0xfc, 0x30, 0x20, 0x00, 0x00, 45 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x07, 0xfc, 0x3c, 0x00, 0x00, 0x00, 46 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x07, 0xfe, 0x7f, 0x40, 0x00, 0x00, 47 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x03, 0xfe, 0x7f, 0x80, 0x00, 0x00, 48 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 49 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 50 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 51 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 52 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 53 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 54 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 55 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x00, 56 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 57 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x1f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 58 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 59 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 60 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 61 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 62 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0x7f, 0xe0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 63 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0xff, 0xe0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 64 | 0x00, 0x03, 0xfe, 0x00, 0x00, 0xff, 0xc0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 65 | 0x00, 0x03, 0xfe, 0x00, 0x03, 0xff, 0xc0, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 66 | 0x00, 0x03, 0xfe, 0x00, 0x07, 0xff, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 67 | 0x00, 0x03, 0xfe, 0x00, 0x1f, 0xff, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 68 | 0x00, 0x03, 0xfe, 0x01, 0xff, 0xff, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 69 | 0x00, 0x03, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 70 | 0x00, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 71 | 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 72 | 0x00, 0x03, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 73 | 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 74 | 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 75 | 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 84 | }; 85 | 86 | #define minTravDist 25 //Define travel distance initial setting, minimum, maximum and increment 87 | #define maxTravDist 550 88 | #define travDistInc 25 89 | #define initialDur 120 //Define duration initial setting, minimum, maximum and increment 90 | #define minDur 10 91 | #define maxDur 1800 92 | #define durInc 5 93 | #define initialRotAng 180 //Define rotation initial setting, minimum, maximum and increment 94 | #define minRotAng 20 95 | #define maxRotAng 360 96 | #define rotAngInc 10 97 | #define initialObjDist 200 //Define object distance initial setting, minimum, maximum and increment 98 | #define minObjDist 150 99 | #define maxObjDist 5000 100 | #define objInc 50 101 | #define minInterval 400 //Minimum interval time between pulses in microseconds 102 | 103 | static int pinA = 2; //Hardware interrupt digital pin 2 104 | static int pinB = 3; //Hardware interrupt digital pin 3 105 | volatile byte aFlag = 0; //Rising edge on pinA to signal that the encoder has arrived at a detent 106 | volatile byte bFlag = 0; //Rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set) 107 | volatile int encoderPos = 0; //Current value of encoder position, constained between limits below 108 | volatile int prevEncoderPos = 0; //To track whether the encoder has been turned and the display needs to update 109 | volatile byte reading = 0; //Stores direct value from interrupt pin 110 | #define encButton 4 //Define encoder pushbutton pin 111 | byte oldButtonState = HIGH; //First button state is open because of pull-up resistor 112 | const unsigned long debounceTime = 10; //Debounce delay time 113 | unsigned long buttonPressTime; //Time button has been pressed for debounce 114 | 115 | int encLowLim = 0; //Variables to store the encoder limits and increment 116 | int encHighLim = 3; 117 | int encIncrement = 1; 118 | int dataInputNo = 0; 119 | int modeSelected = 0; //Current operating mode (Pan, Rotate, Pan & Rotate, Track Object) 120 | 121 | #define enablePin 5 //Define motor enable pin 122 | #define travDirPin 6 //Define travel & rotation stepper motor pins 123 | #define travStepPin 7 124 | #define rotDirPin 8 125 | #define rotStepPin 9 126 | 127 | float travDist = maxTravDist; //Distance to travel across slider in millimeters 128 | float travTime = initialDur; //Travel time to cover the required distance in seconds 129 | float objDist = initialObjDist; //Distance of tracked object from slider in millimeters 130 | int travelDir = 0; //Deifne initial travel and rotation directions 131 | int rotDir = 0; 132 | int rotAngle = 180; //Angle to rotate camera around axis 133 | 134 | float pulsesPerMM = 50; //Number of motor pulses for 1mm travel 135 | float pulsesPerDeg = 4.4444; //Number of motor pulses for 1 degree of rotation 136 | float currentDist = 0; 137 | float currentAngle = 0; 138 | 139 | void setup() 140 | { 141 | Serial.begin(9600); //Start serial communication for debugging 142 | if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) //Connect to the OLED display 143 | { 144 | Serial.println(F("SSD1306 allocation failed")); //If connection fails 145 | for(;;); //Don't proceed, loop forever 146 | } 147 | pinMode(pinA, INPUT_PULLUP); //Set pinA as an input, pulled HIGH to the logic voltage 148 | pinMode(pinB, INPUT_PULLUP); //Set pinB as an input, pulled HIGH to the logic voltage 149 | attachInterrupt(0,PinA,RISING); //Set an interrupt on PinA 150 | attachInterrupt(1,PinB,RISING); //Set an interrupt on PinB 151 | pinMode (encButton, INPUT_PULLUP); //Set the encoder button as an input, pulled HIGH to the logic voltage 152 | pinMode(enablePin, INPUT); //Open circuit enable pin, disables motors 153 | pinMode(travDirPin, OUTPUT); //Define the travel stepper motor pins 154 | pinMode(travStepPin, OUTPUT); 155 | pinMode(rotDirPin, OUTPUT); //Define the rotation stepper motor pins 156 | pinMode(rotStepPin, OUTPUT); 157 | digitalWrite(travDirPin, HIGH); //Set the initial direction of motion for both motors 158 | digitalWrite(rotDirPin, HIGH); 159 | display.clearDisplay(); //Clear the display 160 | display.setTextColor(SSD1306_WHITE); //Set the text colour to white 161 | display.drawBitmap(0, 0, logo, 128, 64, WHITE); //Display bitmap from array 162 | display.display(); 163 | delay(2000); 164 | display.clearDisplay(); //Clear display 165 | Serial.println("Setup complete"); //Write to serial monitor to indicate the setup function is complete 166 | } 167 | 168 | void loop() 169 | { 170 | encLowLim = 0; //Mode selection menu, 4 modes 171 | encHighLim = 3; 172 | encIncrement = 1; 173 | updateMainMenu(); 174 | 175 | boolean confirmed = false; //Both used to confirm button push to select mode 176 | boolean pressed = false; 177 | encoderPos = 0; //Encoder starts from 0, first menu option 178 | while(!confirmed) //While the user has not confirmed the selection 179 | { 180 | byte buttonState = digitalRead (encButton); 181 | if (buttonState != oldButtonState) 182 | { 183 | if (millis () - buttonPressTime >= debounceTime) //Debounce button 184 | { 185 | buttonPressTime = millis (); //Time when button was pushed 186 | oldButtonState = buttonState; //Remember button state for next time 187 | if (buttonState == LOW) 188 | { 189 | modeSelected = encoderPos; //If the button is pressed, accept the current digit into the guessed code 190 | pressed = true; 191 | Serial.println("Button Pushed"); 192 | } 193 | else 194 | { 195 | if (pressed == true) //Confirm the input once the button is released again 196 | { 197 | confirmed = true; 198 | Serial.println("Mode confirmed"); 199 | } 200 | } 201 | } 202 | } 203 | if(encoderPos!=prevEncoderPos) //Update the display if the encoder position has changed 204 | { 205 | updateMainMenu(); 206 | prevEncoderPos=encoderPos; 207 | } 208 | } 209 | Serial.println("Mode selected"); 210 | if (modeSelected == 0) //Run required mode function depending on selection 211 | runPan(); 212 | else if (modeSelected == 1) 213 | runRotate (); 214 | else if (modeSelected == 2) 215 | runPanAndRotate (); 216 | else 217 | runTrack (); 218 | } 219 | 220 | void PinA() //Rotary encoder interrupt service routine for one encoder pin 221 | { 222 | cli(); //Stop interrupts happening before we read pin values 223 | reading = PIND & 0xC; //Read all eight pin values then strip away all but pinA and pinB's values 224 | if(reading == B00001100 && aFlag) //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge 225 | { 226 | if(encoderPos<=(encHighLim-encIncrement)) 227 | encoderPos = encoderPos+encIncrement; //Increment the encoder's position count , only when below upper limit 228 | else 229 | encoderPos = encHighLim; //Stop at maximum, being upper limit 230 | bFlag = 0; //Reset flags for the next turn 231 | aFlag = 0; //Reset flags for the next turn 232 | } 233 | else if (reading == B00000100) //Signal that we're expecting pinB to signal the transition to detent from free rotation 234 | bFlag = 1; 235 | sei(); //Restart interrupts 236 | } 237 | 238 | void PinB() //Rotary encoder interrupt service routine for the other encoder pin 239 | { 240 | cli(); //Stop interrupts happening before we read pin values 241 | reading = PIND & 0xC; //Read all eight pin values then strip away all but pinA and pinB's values 242 | if (reading == B00001100 && bFlag) //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge 243 | { 244 | if(encoderPos>=(encLowLim+encIncrement)) 245 | encoderPos = encoderPos-encIncrement; //Decrement the encoder's position count, only when above lower limit 246 | else 247 | encoderPos = encLowLim; //Stop at minimum, being lower limit 248 | bFlag = 0; //Reset flags for the next turn 249 | aFlag = 0; //Reset flags for the next turn 250 | } 251 | else if (reading == B00001000) //Signal that we're expecting pinA to signal the transition to detent from free rotation 252 | aFlag = 1; 253 | sei(); //Restart interrupts 254 | } 255 | 256 | void updateMainMenu() //Updates the display data for the main menu 257 | { 258 | display.clearDisplay(); //Clear display 259 | display.setTextSize(1); //Set the text size 260 | display.setCursor(28,4); 261 | display.print(F("Camera Slider")); 262 | display.setCursor(25,20); //Set the display cursor position 263 | display.print(F("Pan")); //Set the display text 264 | display.setCursor(25,30); 265 | display.print(F("Rotate")); 266 | display.setCursor(25,40); 267 | display.print(F("Pan & Rotate")); 268 | display.setCursor(25,50); 269 | display.print(F("Track Object")); 270 | int selected = 0; //Stores cursor vertical position to show selected item 271 | if (encoderPos == 0) 272 | selected = 20; 273 | else if (encoderPos == 1) 274 | selected = 30; 275 | else if (encoderPos == 2) 276 | selected = 40; 277 | else 278 | selected = 50; 279 | display.setCursor(14,selected); //Set the display cursor position 280 | display.print(F(">")); 281 | display.display(); //Output the display text 282 | } 283 | 284 | void runPan () //Runs the pan mode sequence 285 | { 286 | inputPanData (); //Get user inputs for pan movement 287 | displayStart (); //Display startup sequence and enable motors 288 | display.setCursor(55,30); 289 | display.print(F("Pan")); 290 | display.display(); 291 | if (travelDir == 0) //Set motor travel direction 292 | digitalWrite(travDirPin, LOW); 293 | else 294 | digitalWrite(travDirPin, HIGH); 295 | int travelPulses = calcTravelPulses (); //Calculate the number of motor pulses required to move the travel distance 296 | Serial.print("Travel pulses: "); 297 | Serial.println(travelPulses); 298 | float interval = calcInterval (travelPulses); //Calculate the pulse interval required to move the required distance in the required time 299 | Serial.print("Interval: "); 300 | Serial.println(interval); 301 | for (int i=1; i<=travelPulses; i++) //Pulse the motor to move the required distance in the required time 302 | { 303 | digitalWrite(travStepPin, HIGH); 304 | delayMicroseconds(interval/2); 305 | digitalWrite(travStepPin, LOW); 306 | delayMicroseconds(interval/2); 307 | } 308 | displayEnd(); //Display the end sequence and disable motors 309 | } 310 | 311 | void runRotate () //Runs the rotate mode sequence 312 | { 313 | inputRotateData (); //Get user inputs for pan movement 314 | displayStart (); //Display startup sequence and enable motors 315 | display.setCursor(49,30); 316 | display.print(F("Rotate")); 317 | display.display(); 318 | if (rotDir == 0) //Set motor travel direction 319 | digitalWrite(rotDirPin, HIGH); 320 | else 321 | digitalWrite(rotDirPin, LOW); 322 | int rotationPulses = calcRotationPulses (); //Calculate the number of motor pulses required to rotate the required angle 323 | Serial.print("Rotation pulses: "); 324 | Serial.println(rotationPulses); 325 | Serial.print("Travel Time: "); 326 | Serial.println(travTime); 327 | float interval = calcRotInterval (rotationPulses); //Calculate the pulse interval required to rotate in the required time 328 | Serial.print("Interval: "); 329 | Serial.println(interval); 330 | for (int i=1; i<=rotationPulses; i++) //Pulse the motor to rotate the required angle in the required time 331 | { 332 | digitalWrite(rotStepPin, HIGH); 333 | delay(interval/2); 334 | digitalWrite(rotStepPin, LOW); 335 | delay(interval/2); 336 | } 337 | displayEnd(); //Display the end sequence and disable motors 338 | } 339 | 340 | void runPanAndRotate () //Runs the pan and rotate mode sequence 341 | { 342 | inputPanAndRotateData (); //Get user inputs for pan movement 343 | displayStart (); //Display startup sequence and enable motors 344 | display.setCursor(30,30); 345 | display.print(F("Pan & Rotate")); 346 | display.display(); 347 | if (travelDir == 0) //Set motor travel direction 348 | digitalWrite(travDirPin, LOW); 349 | else 350 | digitalWrite(travDirPin, HIGH); 351 | if (rotDir == 0) //Set motor travel direction 352 | digitalWrite(rotDirPin, HIGH); 353 | else 354 | digitalWrite(rotDirPin, LOW); 355 | int travelPulses = calcTravelPulses (); //Calculate the number of motor pulses required to move the travel distance 356 | Serial.print("Travel pulses: "); 357 | Serial.println(travelPulses); 358 | float interval = calcInterval (travelPulses); //Calculate the pulse interval required to move the required distance in the required time 359 | Serial.print("Interval: "); 360 | Serial.println(interval); 361 | int rotationPulses = calcRotationPulses (); //Calculate the number of motor pulses required to rotate the required angle 362 | Serial.print("Rotation pulses: "); 363 | Serial.println(rotationPulses); 364 | int travelPerRotation = travelPulses/rotationPulses; //Calculate how much the camera should pan for each rotation step 365 | for (int i=1; i<=travelPulses; i++) 366 | { 367 | digitalWrite(travStepPin, HIGH); 368 | int checkRotate = i % travelPerRotation; //Check if a rotation step must be made 369 | if (checkRotate == 0) 370 | digitalWrite(rotStepPin, HIGH); 371 | delayMicroseconds(interval/2); 372 | digitalWrite(travStepPin, LOW); 373 | if (checkRotate == 0) 374 | digitalWrite(rotStepPin, LOW); 375 | delayMicroseconds(interval/2); 376 | /*currentDist = i/pulsesPerMM; 377 | currentAngle = i/pulsesPerDeg; 378 | Serial.print("Dist: "); 379 | Serial.println(currentDist); 380 | Serial.print("Angle: "); 381 | Serial.println(currentAngle);*/ 382 | } 383 | displayEnd(); //Display the end sequence and disable motors 384 | } 385 | 386 | void runTrack () //Runs the object tracking mode sequence 387 | { 388 | inputTrackData (); //Get user inputs for tracking movement 389 | displayStart (); 390 | display.setCursor(22,30); 391 | display.print(F("Object Tracking")); 392 | display.display(); 393 | if (travelDir == 0) //Set motor travel and rotate directions 394 | { 395 | digitalWrite(travDirPin, LOW); 396 | digitalWrite(rotDirPin, LOW); 397 | } 398 | else 399 | { 400 | digitalWrite(travDirPin, HIGH); 401 | digitalWrite(rotDirPin, HIGH); 402 | } 403 | int travelPulses = calcTravelPulses (); //Calculates the number of pulses required to move the travel distance 404 | Serial.print("Travel pulses: "); 405 | Serial.println(travelPulses); 406 | float interval = calcInterval (travelPulses); //Calculates the interval required to achieve the required duration 407 | Serial.print("Interval: "); 408 | Serial.println(interval); 409 | currentAngle = atan((objDist)/(travDist/2))*180/M_PI; //Calculates the initial camera to object angle 410 | Serial.print("Current Angle: "); 411 | Serial.println(currentAngle); 412 | for (int i=1; i<=(travelPulses/2); i++) //Runs through movement sequence to move motors 413 | { 414 | digitalWrite(travStepPin, HIGH); 415 | boolean rotatePulse = checkRot (i); 416 | if (rotatePulse == true) 417 | digitalWrite(rotStepPin, HIGH); 418 | delayMicroseconds(interval/2); 419 | digitalWrite(travStepPin, LOW); 420 | if (rotatePulse == true) 421 | digitalWrite(rotStepPin, LOW); 422 | delayMicroseconds(interval/2); 423 | currentDist = i/pulsesPerMM; 424 | /*Serial.print("Dist: "); 425 | Serial.println(currentDist); 426 | Serial.print("Angle: "); 427 | Serial.println(currentAngle);*/ 428 | } 429 | currentAngle = 90; 430 | for (int i=((travelPulses/2)+1); i<=travelPulses; i++) //Runs through movement sequence to move motors 431 | { 432 | digitalWrite(travStepPin, HIGH); 433 | boolean rotatePulse = checkRot (i); 434 | if (rotatePulse == true) 435 | digitalWrite(rotStepPin, HIGH); 436 | delayMicroseconds(interval/2); 437 | digitalWrite(travStepPin, LOW); 438 | if (rotatePulse == true) 439 | digitalWrite(rotStepPin, LOW); 440 | delayMicroseconds(interval/2); 441 | currentDist = i/pulsesPerMM; 442 | /*Serial.print("Dist: "); 443 | Serial.println(currentDist); 444 | Serial.print("Angle: "); 445 | Serial.println(currentAngle);*/ 446 | } 447 | displayEnd(); //Display the end sequence and disable motors 448 | } 449 | 450 | void displayStart() 451 | { 452 | display.clearDisplay(); //Clear display 453 | display.setTextSize(1); //Set the text size 454 | display.setCursor(45,20); //Set the display cursor position 455 | display.print(F("Push To")); //Set the display text 456 | display.setCursor(50,32); 457 | display.print(F("Start")); 458 | display.display(); //Output the display text 459 | boolean confirmed = false; //Both used to confirm button push to select mode 460 | boolean pressed = false; 461 | while(!confirmed) //While the user has not started the panning routine 462 | { 463 | byte buttonState = digitalRead (encButton); 464 | if (buttonState != oldButtonState) 465 | { 466 | if (millis () - buttonPressTime >= debounceTime) //Debounce button 467 | { 468 | buttonPressTime = millis (); //Time when button was pushed 469 | oldButtonState = buttonState; //Remember button state for next time 470 | if (buttonState == LOW) 471 | { 472 | pressed = true; 473 | } 474 | else 475 | { 476 | if (pressed == true) //Confirm the input once the button is released again 477 | { 478 | confirmed = true; 479 | } 480 | } 481 | } 482 | } 483 | } 484 | pinMode(enablePin, OUTPUT); //Enable the motors 485 | for(int i=3 ; i> 0 ; i--) //Countdown to start 486 | { 487 | display.clearDisplay(); //Clear display 488 | display.setTextSize(2); //Set the text size 489 | display.setCursor(60,20); //Set the display cursor position 490 | display.print(i); //Set the display text 491 | display.display(); 492 | delay(1000); 493 | } 494 | display.clearDisplay(); //Clear display 495 | display.setTextSize(1); //Set the text size 496 | display.setCursor(45,15); //Set the display cursor position 497 | display.print(F("Running")); //Set the display text 498 | display.display(); 499 | } 500 | 501 | void displayEnd() 502 | { 503 | display.clearDisplay(); //Clear display 504 | display.setTextSize(1); //Set the text size 505 | display.setCursor(40,15); //Set the display cursor position 506 | display.print(F("Complete")); //Set the display text 507 | display.setCursor(44,35); 508 | display.print(F("Push To")); 509 | display.setCursor(27,47); 510 | display.print(F("Release Motors")); 511 | display.display(); //Output the display text 512 | boolean confirmed = false; //Both used to confirm button push to select mode 513 | boolean pressed = false; 514 | while(!confirmed) //While the user has not started the routine 515 | { 516 | byte buttonState = digitalRead (encButton); 517 | if (buttonState != oldButtonState) 518 | { 519 | if (millis () - buttonPressTime >= debounceTime) //Debounce button 520 | { 521 | buttonPressTime = millis (); //Time when button was pushed 522 | oldButtonState = buttonState; //Remember button state for next time 523 | if (buttonState == LOW) 524 | { 525 | pressed = true; 526 | } 527 | else 528 | { 529 | if (pressed == true) //Confirm the input once the button is released again 530 | { 531 | confirmed = true; 532 | } 533 | } 534 | } 535 | } 536 | } 537 | pinMode(enablePin, INPUT); //Open circuit enable pin, disables motors 538 | resetVariables (); 539 | } 540 | 541 | void inputPanData () //Input required data for pan mode 542 | { 543 | dataInputNo = 0; //Input travel distance 544 | inputField (maxTravDist, minTravDist, maxTravDist, travDistInc); 545 | dataInputNo = 1; //Input travel direction 546 | inputField (0, 0, 1, 1); 547 | dataInputNo = 2; //Input travel duration 548 | inputField (initialDur, minDur, maxDur, durInc); 549 | } 550 | 551 | void inputRotateData () //Input required data for rotate mode 552 | { 553 | dataInputNo = 0; //Input rotation angle 554 | inputField (initialRotAng, minRotAng, maxRotAng, rotAngInc); 555 | dataInputNo = 1; //Input rotation direction 556 | inputField (0, 0, 1, 1); 557 | dataInputNo = 2; //Input rotation duration 558 | inputField (initialDur, minDur, maxDur, durInc); 559 | } 560 | 561 | void inputPanAndRotateData () //Input required data for pan and rotate mode 562 | { 563 | dataInputNo = 0; //Input pan distance 564 | inputField (maxTravDist, minTravDist, maxTravDist, travDistInc); 565 | dataInputNo = 1; //Input pan direction 566 | inputField (0, 0, 1, 1); 567 | dataInputNo = 2; //Input rotation angle 568 | inputField (initialRotAng, minRotAng, maxRotAng, rotAngInc); 569 | dataInputNo = 3; //Input rotation direction 570 | inputField (0, 0, 1, 1); 571 | dataInputNo = 4; //Input total duration 572 | inputField (initialDur, minDur, maxDur, durInc); 573 | } 574 | 575 | void inputTrackData () //Input required data for object tracking mode 576 | { 577 | dataInputNo = 0; //Input pan distance 578 | inputField (maxTravDist, minTravDist, maxTravDist, travDistInc); 579 | dataInputNo = 1; //Input pan direction 580 | inputField (0, 0, 1, 1); 581 | dataInputNo = 2; //Input rotation angle 582 | inputField (initialObjDist, minObjDist, maxObjDist, objInc); 583 | dataInputNo = 3; //Input total duration 584 | inputField (initialDur, minDur, maxDur, durInc); 585 | } 586 | 587 | void updatePanDataDisplay () 588 | { 589 | display.clearDisplay(); //Clear display 590 | display.setTextSize(1); //Set the text size 591 | display.setCursor(2,10); //Set the display cursor position 592 | display.print(F("Distance: ")); //Set the display text 593 | display.setCursor(2,20); 594 | display.print(F("Direction: ")); 595 | display.setCursor(2,30); 596 | display.print(F("Duration: ")); 597 | int selected = 0; 598 | if (dataInputNo == 0) //Get the cursor position & update changing variable 599 | { 600 | selected = 10; 601 | travDist = encoderPos; 602 | } 603 | else if (dataInputNo == 1) 604 | { 605 | selected = 20; 606 | travelDir = encoderPos; 607 | } 608 | else 609 | { 610 | selected = 30; 611 | travTime = encoderPos; 612 | if(calcInterval (calcTravelPulses ()) < minInterval) //Flags movement too fast 613 | { 614 | display.setCursor(40,55); //Set the display cursor position 615 | display.print(F("Too Fast")); //Set the display text 616 | } 617 | } 618 | display.setCursor(65,selected); //Set the display cursor position 619 | display.print(F(">")); 620 | display.setCursor(75,10); //Display the field data 621 | display.print(travDist); 622 | display.print(F("mm")); 623 | display.setCursor(75,20); 624 | if (travelDir == 0) 625 | display.print(F("Forward")); 626 | else 627 | display.print(F("Reverse")); 628 | display.setCursor(75,30); 629 | display.print(travTime); 630 | display.print(F("s")); 631 | display.display(); //Output the display text 632 | } 633 | 634 | void updateRotDataDisplay () 635 | { 636 | display.clearDisplay(); //Clear display 637 | display.setTextSize(1); //Set the text size 638 | display.setCursor(2,10); //Set the display cursor position 639 | display.print(F("Rot. Ang.: ")); //Set the display text 640 | display.setCursor(2,20); 641 | display.print(F("Direction: ")); 642 | display.setCursor(2,30); 643 | display.print(F("Duration: ")); 644 | int selected = 0; 645 | if (dataInputNo == 0) //Get the cursor position & update changing variable 646 | { 647 | selected = 10; 648 | rotAngle = encoderPos; 649 | } 650 | else if (dataInputNo == 1) 651 | { 652 | selected = 20; 653 | rotDir = encoderPos; 654 | } 655 | else 656 | { 657 | selected = 30; 658 | travTime = encoderPos; 659 | if(calcRotInterval (calcRotationPulses ()) < minInterval) //Flags movement too fast 660 | { 661 | display.setCursor(40,55); //Set the display cursor position 662 | display.print(F("Too Fast")); //Set the display text 663 | } 664 | } 665 | display.setCursor(65,selected); //Set the display cursor position 666 | display.print(F(">")); 667 | display.setCursor(75,10); //Display the field data 668 | display.print(rotAngle); 669 | display.print(F("deg")); 670 | display.setCursor(75,20); 671 | if (rotDir == 0) 672 | display.print(F("Forward")); 673 | else 674 | display.print(F("Reverse")); 675 | display.setCursor(75,30); 676 | display.print(travTime); 677 | display.print(F("s")); 678 | display.display(); //Output the display text 679 | } 680 | 681 | void updatePanAndRotateDataDisplay () 682 | { 683 | display.clearDisplay(); //Clear display 684 | display.setTextSize(1); //Set the text size 685 | display.setCursor(2,2); //Set the display cursor position 686 | display.print(F("Distance: ")); //Set the display text 687 | display.setCursor(2,12); 688 | display.print(F("Trav. Dir: ")); 689 | display.setCursor(2,22); 690 | display.print(F("Rot. Ang: ")); 691 | display.setCursor(2,32); 692 | display.print(F("Rot. Dir: ")); 693 | display.setCursor(2,42); 694 | display.print(F("Duration: ")); 695 | int selected = 0; 696 | if (dataInputNo == 0) //Get the cursor position & update changing variable 697 | { 698 | selected = 2; 699 | travDist = encoderPos; 700 | } 701 | else if (dataInputNo == 1) 702 | { 703 | selected = 12; 704 | travelDir = encoderPos; 705 | } 706 | else if (dataInputNo == 2) 707 | { 708 | selected = 22; 709 | rotAngle = encoderPos; 710 | } 711 | else if (dataInputNo == 3) 712 | { 713 | selected = 32; 714 | rotDir = encoderPos; 715 | } 716 | else 717 | { 718 | selected = 42; 719 | travTime = encoderPos; 720 | if(calcInterval (calcTravelPulses ()) < minInterval) //Flags movement too fast 721 | { 722 | display.setCursor(40,55); //Set the display cursor position 723 | display.print(F("Too Fast")); //Set the display text 724 | } 725 | } 726 | display.setCursor(65,selected); //Set the display cursor position 727 | display.print(F(">")); 728 | display.setCursor(75,2); //Display the field data 729 | display.print(travDist); 730 | display.print(F("mm")); 731 | display.setCursor(75,12); 732 | if (travelDir == 0) 733 | display.print(F("Forward")); 734 | else 735 | display.print(F("Reverse")); 736 | display.setCursor(75,22); 737 | display.print(rotAngle); 738 | display.print(F("deg")); 739 | display.setCursor(75,32); 740 | if (rotDir == 0) 741 | display.print(F("Forward")); 742 | else 743 | display.print(F("Reverse")); 744 | display.setCursor(75,42); 745 | display.print(travTime); 746 | display.print(F("s")); 747 | display.display(); //Output the display text 748 | } 749 | 750 | void updateTrackDataDisplay () 751 | { 752 | display.clearDisplay(); //Clear display 753 | display.setTextSize(1); //Set the text size 754 | display.setCursor(2,10); //Set the display cursor position 755 | display.print(F("Distance: ")); //Set the display text 756 | display.setCursor(2,20); 757 | display.print(F("Trav. Dir: ")); 758 | display.setCursor(2,30); 759 | display.print(F("Obj. Dist: ")); 760 | display.setCursor(2,40); 761 | display.print(F("Duration: ")); 762 | int selected = 0; 763 | if (dataInputNo == 0) //Get the cursor position & update changing variable 764 | { 765 | selected = 10; 766 | travDist = encoderPos; 767 | } 768 | else if (dataInputNo == 1) 769 | { 770 | selected = 20; 771 | travelDir = encoderPos; 772 | } 773 | else if (dataInputNo == 2) 774 | { 775 | selected = 30; 776 | objDist = encoderPos; 777 | } 778 | else 779 | { 780 | selected = 40; 781 | travTime = encoderPos; 782 | if(calcInterval (calcTravelPulses ()) < minInterval) //Flags movement too fast 783 | { 784 | display.setCursor(40,55); //Set the display cursor position 785 | display.print(F("Too Fast")); //Set the display text 786 | } 787 | } 788 | display.setCursor(65,selected); //Set the display cursor position 789 | display.print(F(">")); 790 | display.setCursor(75,10); //Display the field data 791 | display.print(travDist); 792 | display.print(F("mm")); 793 | display.setCursor(75,20); 794 | if (travelDir == 0) 795 | display.print(F("Forward")); 796 | else 797 | display.print(F("Reverse")); 798 | display.setCursor(75,30); 799 | display.print(objDist); 800 | display.print(F("mm")); 801 | display.setCursor(75,40); 802 | display.print(travTime); 803 | display.print(F("s")); 804 | display.display(); //Output the display text 805 | } 806 | 807 | void inputField (int initialSetting, int lowerLimit, int upperLimit, int increment) 808 | { 809 | encLowLim = lowerLimit; 810 | encHighLim = upperLimit; 811 | encIncrement = increment; 812 | encoderPos = initialSetting; //Encoder starts from initial setting 813 | prevEncoderPos = encoderPos+1; //Set different so that display is updated on first cycle 814 | boolean confirmed = false; //Both used to confirm button push to select mode 815 | boolean pressed = false; 816 | while(!confirmed) //While the user has not confirmed the input 817 | { 818 | byte buttonState = digitalRead (encButton); 819 | if (buttonState != oldButtonState) 820 | { 821 | if (millis () - buttonPressTime >= debounceTime) //Debounce button 822 | { 823 | buttonPressTime = millis (); //Time when button was pushed 824 | oldButtonState = buttonState; //Remember button state for next time 825 | if (buttonState == LOW) 826 | { 827 | pressed = true; 828 | } 829 | else 830 | { 831 | if (pressed == true) //Confirm the input once the button is released again 832 | { 833 | confirmed = true; 834 | Serial.println("Input Confirmed"); 835 | } 836 | } 837 | } 838 | } 839 | if(encoderPos!=prevEncoderPos) //Update the display if the encoder position has changed 840 | { 841 | if (modeSelected == 0) 842 | updatePanDataDisplay (); 843 | else if (modeSelected == 1) 844 | updateRotDataDisplay (); 845 | else if (modeSelected == 2) 846 | updatePanAndRotateDataDisplay (); 847 | else 848 | updateTrackDataDisplay (); 849 | prevEncoderPos=encoderPos; 850 | } 851 | } 852 | } 853 | 854 | void resetVariables () //Reset variables back to initial values after run 855 | { 856 | travDist = maxTravDist; 857 | travTime = initialDur; 858 | objDist = initialObjDist; 859 | travelDir = 0; 860 | rotDir = 0; 861 | rotAngle = initialRotAng; 862 | } 863 | 864 | int calcTravelPulses () //Calculates the number of pulses required to move a certain distance 865 | { 866 | int travP = travDist*pulsesPerMM; 867 | return travP; 868 | } 869 | 870 | int calcRotationPulses () //Calculate the number of pulses required to rotate a certain angle 871 | { 872 | int rotP = rotAngle*pulsesPerDeg; 873 | return rotP; 874 | } 875 | 876 | boolean checkRot (int i) //Used in tracking to calculate the angle required to track object 877 | { 878 | boolean rotP = false; 879 | float deltaAngle; 880 | if(((travDist/2)-(i/pulsesPerMM)) > 0) 881 | { 882 | if (currentAngle < 90-(1/pulsesPerDeg)) 883 | { 884 | float newAngle = atan((objDist)/((travDist/2)-(i/pulsesPerMM)))*180/M_PI; 885 | deltaAngle = newAngle-currentAngle; 886 | } 887 | } 888 | else if (((travDist/2)-(i/pulsesPerMM)) < 0) 889 | { 890 | if (currentAngle > 0) 891 | { 892 | float newAngle = atan((objDist)/((i/pulsesPerMM)-(travDist/2)))*180/M_PI; 893 | deltaAngle = currentAngle-newAngle; 894 | } 895 | } 896 | if(deltaAngle >= (1/pulsesPerDeg)) 897 | { 898 | rotP = true; 899 | if ((travDist/2)-(i/pulsesPerMM) > 0) 900 | currentAngle=currentAngle+(1/pulsesPerDeg); 901 | else 902 | currentAngle=currentAngle-(1/pulsesPerDeg); 903 | } 904 | return rotP; 905 | } 906 | 907 | float calcInterval (int numPulses) //Calculate the interval required between pulses to achieve duration 908 | { 909 | float inter = travTime*1000000/numPulses; 910 | return inter; 911 | } 912 | 913 | float calcRotInterval (int numPulses) //Calculate the interval required between pulses to achieve duration 914 | { 915 | float inter = travTime*1000/numPulses; 916 | return inter; 917 | } 918 | --------------------------------------------------------------------------------