Free DIY Controller

This is a Arduino-based DIY controller designed for Eclíck — an easy-to-build solution for hobbyists and makers.
This controller provides a reliable and customizable way to operate Eclíck, offering essential functionality with the flexibility to expand based on your needs. The design is straightforward, using widely available components, making it perfect for both beginners and experienced makers.
Or, if you prefer a fully built, ready-to-use controller with advanced functionality and wireless control via Wifi check out the Eclíck WiFi Pro Controller

Download the Eagle schematic & board files
Code Functionality:
-
Start Button Press Handling :
- When the
startButtonPress
flag is set, the system starts the shooting process. - Debug message confirms the start action:
"DEBUG: Shooting process started!"
.
- When the
-
Startup Delay :
- The system applies a startup delay (
startupDelaySec
) before beginning the shooting process. - Debug message logs the delay duration:
"DEBUG: Waiting for startup delay of X seconds..."
.
- The system applies a startup delay (
-
Main Shooting Loop :
- Iterates through each elevation in the
shootingPositions
array. - For each elevation, calculates the adjusted number of pan positions and step size based on the panorama width and direction mode.
- Iterates through each elevation in the
-
Pause/Resume Handling :
- Before each critical action (moving motors, applying delays, taking shots), the system calls
waitWhilePaused()
to check for and handle pauses. - Debug messages confirm pausing and resuming actions:
-
"DEBUG: Pause Button Pressed"
. -
"DEBUG: Resume Button Pressed"
.
-
- Before each critical action (moving motors, applying delays, taking shots), the system calls
-
Bracketing Logic :
- For each pan position, takes multiple shots (
bracketing
) with appropriate delays (delayAfterMs
). - Focus is triggered only before the first shot in the bracketing sequence if
autoFocusEnabled
istrue
. - Debug messages confirm the start and completion of bracketing:
-
"DEBUG: Starting bracketing..."
. -
"DEBUG: Finished bracketing."
.
-
- For each pan position, takes multiple shots (
-
Return to Start :
- After completing all shots, the system moves both motors back to their starting positions if
returnToStartEnabled
istrue
. - Debug message confirms this action:
"DEBUG: Returning to starting position..."
.
- After completing all shots, the system moves both motors back to their starting positions if
-
Completion :
- Once the shooting process is complete, the system updates the LEDs and logs a debug message:
"DEBUG: Shooting process completed!"
.
- Once the shooting process is complete, the system updates the LEDs and logs a debug message:
/* Programmable Robotic Head for: - Panoramas, - Gigapixel, - Motion Timelapse, - Static Timelapse/Intervalometer Website: www.eclick.org */ // Include the AccelStepper library for stepper motor control #include// Step 1: Pin Declarations // Input Pins const int PUSH_BUTTON = 2; // Push button connected to digital pin D2 (interrupt-capable pin) const int INPUT_PORT = 3; // Input port (2.5mm jack) connected to digital pin D3 // Output Pins for Pan Motor const int PAN_DIR_PIN = 4; // Direction pin for the pan motor connected to digital pin D4 const int PAN_STEP_PIN = 5; // Step pin for the pan motor connected to digital pin D5 // Output Pins for Tilt Motor const int TILT_DIR_PIN = 7; // Direction pin for the tilt motor connected to digital pin D7 const int TILT_STEP_PIN = 8; // Step pin for the tilt motor connected to digital pin D8 // Output Pins for LEDs const int GREEN_LED = A0; // Green LED connected to analog pin A0 const int RED_LED = A1; // Red LED connected to analog pin A1 // Output Pins for Camera Control const int CAMERA_SHUTTER = A3; // Camera shutter pin connected to analog pin A3 const int CAMERA_FOCUS = A4; // Camera focus pin connected to analog pin A4 // Step 2: Stepper Motor Configuration (Declarations) // Define the stepper motor objects using AccelStepper library AccelStepper panMotor(1, PAN_STEP_PIN, PAN_DIR_PIN); // Create pan motor object AccelStepper tiltMotor(1, TILT_STEP_PIN, TILT_DIR_PIN); // Create tilt motor object // Step 3: Motor Parameters (Steps, Microstepping, Gear Ratio) // Pan Motor Parameters const int PAN_MOTOR_STEPS = 200; // Number of steps per revolution for the pan motor const int PAN_MICROSTEPS = 16; // Microstepping value for the pan motor const float PAN_GEAR_RATIO = 3.0; // Gear ratio for the pan motor // Tilt Motor Parameters const int TILT_MOTOR_STEPS = 200; // Number of steps per revolution for the tilt motor const int TILT_MICROSTEPS = 4; // Microstepping value for the tilt motor const float TILT_GEAR_RATIO = 50.0; // Gear ratio for the tilt motor // Function to calculate steps per second from RPM float rpmToStepsPerSecond(int motorSteps, int microsteps, float gearRatio, float rpm) { return (rpm * motorSteps * microsteps * gearRatio) / 60.0; } // Step 4: Shooting Profile Parameters // Timing Parameters (in milliseconds unless specified otherwise) const int focusSignalDurationMs = 100; // Duration of the focus signal in milliseconds const int focusDelayAfterMs = 2000; // Delay after focus signal in milliseconds const int shutterSignalDurationMs = 100; // Duration of the shutter signal in milliseconds const int delayBeforeMs = 500; // Delay before taking a shot in a sequence const int delayAfterMs = 1000; // Delay after each shot in a sequence const int startupDelaySec = 2; // Startup delay in seconds const int flashIntervalMs = 500; // Interval for flashing the red LED when paused const int debounceDelayMs = 2000; // Debounce delay (2 seconds) // Panorama Parameters const int panoramaWidthDegrees = 360; // Total width of the panorama in degrees const String tiltArmInitialization = "Zenith"; // Tilt arm initialization: "Horizontal" or "Zenith" const int bracketing = 1; // Number of shots (bracketing) at each shooting position const String directionMode = "Normal"; // Direction mode: "Normal" or "Reverse" // Array of shooting positions [elevation, number of pan positions] const int shootingPositions[][2] = { {0, 6}, // Elevation 0°, 6 pan positions {45, 4}, // Elevation +45°, 4 pan positions {90, 1}, // Elevation +90°, 1 pan position {-45, 4}, // Elevation -45°, 4 pan positions {-90, 1} // Elevation -90°, 1 pan position }; const int numElevations = sizeof(shootingPositions) / sizeof(shootingPositions[0]); // Number of elevations // Shooting Mode Parameters const bool autoFocusEnabled = false; // Enable or disable auto-focus const bool returnToStartEnabled = true; // Return to start position after shooting // Motor Speeds and Acceleration const float panSpeedRPM = 12; // Desired speed for the pan motor in RPM const float panAccelerationStepsPerSec2 = 2000; // Acceleration for the pan motor in steps/sec^2 const float tiltSpeedRPM = 4; // Desired speed for the tilt motor in RPM const float tiltAccelerationStepsPerSec2 = 2000; // Acceleration for the tilt motor in steps/sec^2 // Global Variables for Steps Per Degree float panTotalSteps; // Total steps for the pan motor float panStepsPerDegree; // Steps per degree for the pan motor float tiltTotalSteps; // Total steps for the tilt motor float tiltStepsPerDegree; // Steps per degree for the tilt motor // Variables for pause/resume functionality volatile bool buttonPressedFlag = false; // Flag set by interrupt bool isPaused = false; // Tracks if the system is paused unsigned long lastButtonPressTime = 0; // Timestamp for the last button press unsigned long lastFlashTime = 0; // Timestamp for flashing the red LED volatile bool startButtonPress = false; // Variables to track starting position float startingPanAngle = 0; // Starting pan angle (always 0°) float startingTiltAngle = 0; // Starting tilt angle (determined by tiltArmInitialization) // Function to move the pan motor to a specific angle void movePanMotorToAngle(float targetAngle) { long targetSteps = round(targetAngle * panStepsPerDegree); panMotor.moveTo(targetSteps); Serial.print("DEBUG: Moving pan motor to position "); Serial.println(targetAngle); while (panMotor.distanceToGo() != 0 && !isPaused) { panMotor.run(); } Serial.print("DEBUG: Arrived at pan position "); Serial.println(targetAngle); } // Function to move the tilt motor to a specific angle void moveTiltMotorToAngle(float targetAngle) { long targetSteps = round(targetAngle * tiltStepsPerDegree); tiltMotor.moveTo(targetSteps); Serial.print("DEBUG: Moving tilt motor to position "); Serial.println(targetAngle); while (tiltMotor.distanceToGo() != 0 && !isPaused) { tiltMotor.run(); } Serial.print("DEBUG: Arrived at tilt position "); Serial.println(targetAngle); } // Function to trigger the camera focus void triggerFocus() { digitalWrite(CAMERA_FOCUS, HIGH); delay(focusSignalDurationMs); digitalWrite(CAMERA_FOCUS, LOW); delay(focusDelayAfterMs); Serial.println("DEBUG: Focus triggered."); } // Function to trigger the camera shutter void triggerShutter() { digitalWrite(CAMERA_SHUTTER, HIGH); delay(shutterSignalDurationMs); digitalWrite(CAMERA_SHUTTER, LOW); Serial.println("DEBUG: Shutter triggered."); } // Function to set LED states void setLEDs(bool greenOn, bool redOn) { if (greenOn) { digitalWrite(GREEN_LED, HIGH); } else { digitalWrite(GREEN_LED, LOW); } if (redOn) { digitalWrite(RED_LED, HIGH); } else { digitalWrite(RED_LED, LOW); } } // Function to handle LED flashing when paused void flashRedLED() { unsigned long currentTime = millis(); if (currentTime - lastFlashTime >= flashIntervalMs) { lastFlashTime = currentTime; if (digitalRead(RED_LED) == HIGH) { digitalWrite(RED_LED, LOW); // Turn off the red LED } else { digitalWrite(RED_LED, HIGH); // Turn on the red LED } } } // ISR to detect button presses with debouncing void button_ISR() { unsigned long currentTime = millis(); if (currentTime - lastButtonPressTime > debounceDelayMs) { lastButtonPressTime = currentTime; // Update debounce time bool buttonState = digitalRead(GREEN_LED); if (!isPaused && buttonState == HIGH) { // Start button pressed startButtonPress = true; isPaused = false; Serial.println("DEBUG: Start Button Pressed."); } else if (!isPaused && buttonState == LOW) { // Pause button pressed startButtonPress = false; isPaused = true; Serial.println("DEBUG: Pause Button Pressed."); } else if (isPaused) { // Resume button pressed startButtonPress = false; isPaused = false; Serial.println("DEBUG: Resume Button Pressed."); } } } // Function to wait while the system is paused void waitWhilePaused() { while (isPaused) { flashRedLED(); // Flash the red LED while paused } setLEDs(false, true); // Green LED OFF, Red LED ON Serial.println("DEBUG: Resumed shooting process."); } void setup() { Serial.begin(9600); // Initialize input pins with internal pull-up resistors pinMode(PUSH_BUTTON, INPUT_PULLUP); // Set push button as an input with internal pull-up attachInterrupt(digitalPinToInterrupt(PUSH_BUTTON), button_ISR, FALLING); // Attach interrupt to button pin // Initialize output pins pinMode(PAN_DIR_PIN, OUTPUT); // Set pan motor direction pin as an output pinMode(PAN_STEP_PIN, OUTPUT); // Set pan motor step pin as an output pinMode(TILT_DIR_PIN, OUTPUT); // Set tilt motor direction pin as an output pinMode(TILT_STEP_PIN, OUTPUT); // Set tilt motor step pin as an output pinMode(GREEN_LED, OUTPUT); // Set green LED pin as an output pinMode(RED_LED, OUTPUT); // Set red LED pin as an output pinMode(CAMERA_SHUTTER, OUTPUT); // Set camera shutter pin as an output pinMode(CAMERA_FOCUS, OUTPUT); // Set camera focus pin as an output // Convert RPM to steps per second float panSpeedStepsPerSec = rpmToStepsPerSecond(PAN_MOTOR_STEPS, PAN_MICROSTEPS, PAN_GEAR_RATIO, panSpeedRPM); float tiltSpeedStepsPerSec = rpmToStepsPerSecond(TILT_MOTOR_STEPS, TILT_MICROSTEPS, TILT_GEAR_RATIO, tiltSpeedRPM); // Set maximum speeds for the motors panMotor.setMaxSpeed(panSpeedStepsPerSec); tiltMotor.setMaxSpeed(tiltSpeedStepsPerSec); // Set acceleration for smoother motion panMotor.setAcceleration(panAccelerationStepsPerSec2); tiltMotor.setAcceleration(tiltAccelerationStepsPerSec2); // Pre-calculate total steps and steps per degree for pan and tilt motors panTotalSteps = PAN_MOTOR_STEPS * PAN_MICROSTEPS * PAN_GEAR_RATIO; panStepsPerDegree = panTotalSteps / 360.0; tiltTotalSteps = TILT_MOTOR_STEPS * TILT_MICROSTEPS * TILT_GEAR_RATIO; tiltStepsPerDegree = tiltTotalSteps / 360.0; // Tilt Arm Initialization if (tiltArmInitialization == "Horizontal") { startingTiltAngle = 0; // Assume the tilt arm is at 0° (horizontal) tiltMotor.setCurrentPosition(0); Serial.println("DEBUG: Tilt arm initialized to Horizontal (0°)."); } else if (tiltArmInitialization == "Zenith") { startingTiltAngle = 90; // Assume the tilt arm is at +90° (zenith) tiltMotor.setCurrentPosition(tiltTotalSteps / 4); // Set current position to +90° Serial.println("DEBUG: Tilt arm initialized to Zenith (+90°)."); } else { Serial.println("DEBUG: Invalid tiltArmInitialization parameter. Defaulting to Zenith (+90°)."); startingTiltAngle = 90; // Default to horizontal if invalid parameter tiltMotor.setCurrentPosition(tiltTotalSteps / 4); } // Store the starting pan angle (assumed to be 0° at the beginning) startingPanAngle = 0; // Set LEDs to indicate readiness setLEDs(true, false); // Green LED ON, Red LED OFF Serial.println("DEBUG: System ready. Press the button to start the shooting process..."); } void loop() { // Check if the Start button is pressed to start the process if (startButtonPress) { startButtonPress = false; Serial.println("DEBUG: Shooting process started!"); setLEDs(false, true); // Green LED OFF, Red LED ON // Apply startup delay Serial.print("DEBUG: Waiting for startup delay of "); Serial.print(startupDelaySec); Serial.println(" seconds..."); delay(startupDelaySec * 1000); // Convert seconds to milliseconds // Main shooting loop for (int k = 0; k < numElevations; k++) { int elevation = shootingPositions[k][0]; // Current elevation angle int numPanPositions = shootingPositions[k][1]; // Number of pan positions for this elevation // Adjusted number of pan positions for panoramas less than 360° int adjustedNumPanPositions = numPanPositions; if (panoramaWidthDegrees < 360) { adjustedNumPanPositions = numPanPositions + 1; // Add one extra position } // Calculate step size between pan positions using numPanPositions float stepSize = panoramaWidthDegrees / numPanPositions; // Wait for any pending pause state before proceeding waitWhilePaused(); // Move the tilt motor to the current elevation Serial.print("DEBUG: Moving to elevation "); Serial.println(elevation); moveTiltMotorToAngle(elevation); // Inner loop for pan positions for (int m = 0; m < adjustedNumPanPositions; m++) { // Wait for any pending pause state before proceeding waitWhilePaused(); // Calculate the current pan position float panPosition = 0; if (directionMode == "Normal") { panPosition = m * stepSize; } else if (directionMode == "Reverse") { panPosition = panoramaWidthDegrees - m * stepSize; } // Move the pan motor to the current pan position Serial.print("DEBUG: Moving to pan position "); Serial.println(panPosition); movePanMotorToAngle(panPosition); // Wait for any pending pause state before proceeding waitWhilePaused(); // Wait for microvibrations to dissipate (before the first shot) Serial.println("DEBUG: Applying delayBeforeMs..."); delay(delayBeforeMs); Serial.println("DEBUG: Finished applying delayBeforeMs."); // Take shots with bracketing Serial.println("DEBUG: Starting bracketing..."); for (int b = 0; b < bracketing; b++) { if (b == 0) { // Trigger focus only before the first shot in the bracketing sequence if (autoFocusEnabled) { triggerFocus(); // Trigger focus if enabled } } // Trigger the camera shutter triggerShutter(); // Wait for the delay after the shot if (b < bracketing - 1) { delay(delayAfterMs); } } Serial.println("DEBUG: Finished bracketing."); // Wait for any pending pause state before proceeding waitWhilePaused(); } Serial.println("DEBUG: Moving to next elevation..."); } // Return to the starting position if enabled if (returnToStartEnabled) { Serial.println("DEBUG: Returning to starting position..."); // Move the pan motor back to the starting pan angle movePanMotorToAngle(startingPanAngle); // Move the tilt motor back to the starting tilt angle moveTiltMotorToAngle(startingTiltAngle); } // Update LEDs to indicate readiness setLEDs(true, false); // Green LED ON, Red LED OFF Serial.println("DEBUG: Shooting process completed!"); } }
×
❮
❯