// -*- c -*- // // barndoor.ino: arduino code for an astrophotography barndoor mount // // Copyright (C) 2014-2015 Daniel P. Berrange // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // This code is used to drive the circuit described at // // http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-1-arduino-stepper-motor-control/ // // Based on the maths calculations documented at // // http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-2-calculating-mount-movements/ // // The code assumes an **isosceles** drive barn door mount design. // // Other barndoor drive designs will require different mathematical // formulas to correct errors // http://arduino-info.wikispaces.com/HAL-LibrariesUpdates #include // http://www.airspayce.com/mikem/arduino/AccelStepper/ #include // We don't want to send debug over the serial port by default since // it seriously slows down the main loop causing tracking errors //#define DEBUG // Constants to set based on hardware construction specs // // Assuming you followed the blog linked above, these few variables // should be the only things that you need to change in general // static const float STEP_SIZE_DEG = 1.8; // Degrees rotation per step static const float MICRO_STEPS = 8; // Number of microsteps per step static const float THREADS_PER_CM = 8; // Number of threads in rod per cm of length static const float BASE_LEN_CM = 30.5; // Length from hinge to center of rod in cm static const float INITIAL_ANGLE = 0; // Initial angle of barn doors when switched on static const float MAXIMUM_ANGLE = 30; // Maximum angle to allow barn doors to open (30 deg == 2 hours) // Nothing below this line should require changing unless your barndoor // is not an Isoceles mount, or you changed the electrical circuit design // Constants to set based on electronic construction specs static const int pinOutStep = 9; // Arduino digital pin connected to EasyDriver step static const int pinOutDirection = 8; // Arduino digital pin connected to EasyDriver direction static const int pinInSidereal = 4; // Arduino analogue pin connected to sidereal mode switch static const int pinInHighspeed = 5; // Arduino analogue pin connected to highspeed mode switch static const int pinInDirection = 3; // Arduino analogue pin connected to direction switch // Derived constants static const float USTEPS_PER_ROTATION = 360.0 / STEP_SIZE_DEG * MICRO_STEPS; // usteps per rod rotation // Standard constants static const float SIDE_REAL_SECS = 86164.0419; // time in seconds for 1 rotation of earth // Setup motor class with parameters targetting an EasyDriver board static AccelStepper motor(AccelStepper::DRIVER, pinOutStep, pinOutDirection); // A finite state machine with 3 states - sidereal, highspeed and off static State stateSidereal = State(state_sidereal_enter, state_sidereal_update, state_sidereal_exit); static State stateHighspeed = State(state_highspeed_enter, state_highspeed_update, state_highspeed_update); static State stateOff = State(state_off_enter, state_off_update, state_off_exit); static FSM barndoor = FSM(stateOff); // Given time offset from the 100% closed position, figure out // the total number of steps required to achieve that long time_to_usteps(long tsecs) { return (long)(USTEPS_PER_ROTATION * THREADS_PER_CM * 2.0 * BASE_LEN_CM * sin(tsecs * PI / SIDE_REAL_SECS)); } // Given an angle, figure out the usteps required to get to // that point. long angle_to_usteps(float angle) { return time_to_usteps(SIDE_REAL_SECS / 360.0 * angle); } // Given total number of steps from 100% closed position, figure out // the corresponding total tracking time in seconds long usteps_to_time(long usteps) { return (long)(asin(usteps / (USTEPS_PER_ROTATION * THREADS_PER_CM * 2.0 * BASE_LEN_CM)) * SIDE_REAL_SECS / PI); } // These variables are initialized when the motor switches // from stopped to running, so we know our starting conditions // If the barn door doesn't go to 100% closed, this records // the inital offset we started from for INITIAL_ANGLE static long offsetPositionUSteps; // The maximum we're willing to open the mount to avoid the // doors falling open and smashing the camera. Safety first :-) static long maximumPositionUSteps; // Total motor steps since 100% closed, at the time the // motor started running static long startPositionUSteps; // Total tracking time associated with total motor steps static long startPositionSecs; // Wall clock time at the point the motor switched from // stopped to running. static long startWallClockSecs; // These variables are used while running to calculate our // constantly changing targets // The wall clock time where we need to next calculate tracking // rate / target static long targetWallClockSecs; // Total tracking time associated with our target point static long targetPositionSecs; // Total motor steps associated with target point static long targetPositionUSteps; // Global initialization when first turned off void setup(void) { pinMode(pinInSidereal, OUTPUT); pinMode(pinInHighspeed, OUTPUT); pinMode(pinInDirection, OUTPUT); motor.setPinsInverted(true, false, false); motor.setMaxSpeed(3000); offsetPositionUSteps = angle_to_usteps(INITIAL_ANGLE); maximumPositionUSteps = angle_to_usteps(MAXIMUM_ANGLE); #ifdef DEBUG Serial.begin(9600); #endif } // The logical motor position which takes into account the // fact that we have an initial opening angle long motor_position() { return motor.currentPosition() + offsetPositionUSteps; } // This is called whenever the motor is switch from stopped to running. // // It reads the current motor position which lets us see how many steps // have run since the device was first turned on in the 100% closed // position. From that we then figure out the total tracking time that // corresponds to our open angle. This is then used by plan_tracking() // to figure out subsequent deltas void start_tracking(void) { startPositionUSteps = motor_position(); startPositionSecs = usteps_to_time(startPositionUSteps); startWallClockSecs = millis() / 1000; targetWallClockSecs = startWallClockSecs; #ifdef DEBUG Serial.print("Enter sidereal\n"); Serial.print("offset pos usteps: "); Serial.print(offsetPositionUSteps); Serial.print(", start pos usteps: "); Serial.print(startPositionUSteps); Serial.print(", start pos secs: "); Serial.print(startPositionSecs); Serial.print(", start wclk secs: "); Serial.print(startWallClockSecs); Serial.print("\n\n"); #endif } // This is called when we need to figure out a new target position // // The tangent errors are small enough that over a short period, // we can assume constant linear motion will give constant angular // motion. // // So we set our target values to what we expect them all to be // 15 seconds in the future void plan_tracking(void) { targetWallClockSecs = targetWallClockSecs + 15; targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs); targetPositionUSteps = time_to_usteps(targetPositionSecs); #ifdef DEBUG Serial.print("target pos usteps: "); Serial.print(targetPositionUSteps); Serial.print(", target pos secs: "); Serial.print(targetPositionSecs); Serial.print(", target wclk secs: "); Serial.print(targetWallClockSecs); Serial.print("\n"); #endif } // This is called on every iteration of the main loop // // It looks at our target steps and target wall clock time and // figures out the rate of steps required to get to the target // in the remaining wall clock time. This applies the constant // linear motion expected by plan_tracking() // // By re-calculating rate of steps on every iteration, we are // self-correcting if we are not invoked by the arduino at a // constant rate void apply_tracking(long currentWallClockSecs) { long timeLeft = targetWallClockSecs - currentWallClockSecs; long stepsLeft = targetPositionUSteps - motor_position(); float stepsPerSec = (float)stepsLeft / (float)timeLeft; #ifdef DEBUG32 Serial.print("Target "); Serial.print(targetPositionUSteps); Serial.print(" curr "); Serial.print(motor_position()); Serial.print(" left"); Serial.print(stepsLeft); Serial.print("\n"); #endif if (motor_position() >= maximumPositionUSteps) { motor.stop(); } else { motor.setSpeed(stepsPerSec); motor.runSpeed(); } } // Called when switching from stopped to running // in sidereal tracking mode void state_sidereal_enter(void) { start_tracking(); plan_tracking(); } // Called on every tick, when running in sidereal // tracking mode // // XXX we don't currently use the direction switch // in sidereal mode. Could use it for sidereal vs lunar // tracking rate perhaps ? void state_sidereal_update(void) { long currentWallClockSecs = millis() / 1000; if (currentWallClockSecs >= targetWallClockSecs) { plan_tracking(); } apply_tracking(currentWallClockSecs); } void state_sidereal_exit(void) { // nada } void state_highspeed_enter(void) { #ifdef DEBUG Serial.print("Enter highspeed\n"); #endif } // Called on every iteration when in non-tracking highspeed // forward/back mode. Will automatically step when it // hits the 100% closed position to avoid straining // the motor void state_highspeed_update(void) { // pinInDirection is a 2-position switch for choosing direction // of motion if (analogRead(pinInDirection) < 512) { if (motor_position() >= maximumPositionUSteps) { motor.stop(); } else { motor.setSpeed(5000); motor.runSpeed(); } } else { if (motor.currentPosition() <= 0) { motor.stop(); } else { motor.setSpeed(-5000); motor.runSpeed(); } } } void state_highspeed_exit(void) { // nada } void state_off_enter(void) { #ifdef DEBUG Serial.print("Enter off\n"); #endif motor.stop(); } void state_off_update(void) { // nada } void state_off_exit(void) { // nada } void loop(void) { // pinInSidereal/pinInHighspeed are two poles of a 3-position // switch, that let us choose between sidereal tracking, // stopped and highspeed mode if (analogRead(pinInSidereal) < 512) { barndoor.transitionTo(stateSidereal); } else if (analogRead(pinInHighspeed) < 512) { barndoor.transitionTo(stateHighspeed); } else { barndoor.transitionTo(stateOff); } barndoor.update(); } // // Local variables: // c-indent-level: 4 // c-basic-offset: 4 // indent-tabs-mode: nil // End: //