3 // barndoor.ino: arduino code for an astrophotography barndoor mount
5 // Copyright (C) 2014-2015 Daniel P. Berrange
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation, either version 3 of the License, or
10 // (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // This code is used to drive the circuit described at
22 // http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-1-arduino-stepper-motor-control/
24 // Based on the maths calculations documented at
26 // http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-2-calculating-mount-movements/
28 // The code assumes an **isosceles** drive barn door mount design.
30 // Other barndoor drive designs will require different mathematical
31 // formulas to correct errors
33 // http://arduino-info.wikispaces.com/HAL-LibrariesUpdates
34 #include <FiniteStateMachine.h>
36 // http://www.airspayce.com/mikem/arduino/AccelStepper/
37 #include <AccelStepper.h>
39 // We don't want to send debug over the serial port by default since
40 // it seriously slows down the main loop causing tracking errors
43 // Constants to set based on hardware construction specs
45 // Assuming you followed the blog linked above, these few variables
46 // should be the only things that you need to change in general
48 static const float STEP_SIZE_DEG = 1.8; // Degrees rotation per step
49 static const float MICRO_STEPS = 8; // Number of microsteps per step
50 static const float THREADS_PER_CM = 8; // Number of threads in rod per cm of length
51 static const float BASE_LEN_CM = 30.5; // Length from hinge to center of rod in cm
53 // Nothing below this line should require changing unless your barndoor
54 // is not an Isoceles mount, or you changed the electrical circuit design
56 // Constants to set based on electronic construction specs
57 static const int pinOutStep = 9; // Arduino digital pin connected to EasyDriver step
58 static const int pinOutDirection = 8; // Arduino digital pin connected to EasyDriver direction
60 static const int pinInSidereal = 4; // Arduino analogue pin connected to sidereal mode switch
61 static const int pinInHighspeed = 5; // Arduino analogue pin connected to highspeed mode switch
62 static const int pinInDirection = 3; // Arduino analogue pin connected to direction switch
66 static const float USTEPS_PER_ROTATION = 360.0 / STEP_SIZE_DEG * MICRO_STEPS; // usteps per rod rotation
70 static const float SIDE_REAL_SECS = 86164.0419; // time in seconds for 1 rotation of earth
73 // Setup motor class with parameters targetting an EasyDriver board
74 static AccelStepper motor(AccelStepper::DRIVER,
78 // A finite state machine with 3 states - sidereal, highspeed and off
79 static State stateSidereal = State(state_sidereal_enter, state_sidereal_update, state_sidereal_exit);
80 static State stateHighspeed = State(state_highspeed_enter, state_highspeed_update, state_highspeed_update);
81 static State stateOff = State(state_off_enter, state_off_update, state_off_exit);
82 static FSM barndoor = FSM(stateOff);
85 // Given time offset from the 100% closed position, figure out
86 // the total number of steps required to achieve that
87 long time_to_usteps(long tsecs)
89 return (long)(USTEPS_PER_ROTATION *
90 THREADS_PER_CM * 2.0 * BASE_LEN_CM *
91 sin(tsecs * PI / SIDE_REAL_SECS));
94 // Given total number of steps from 100% closed position, figure out
95 // the corresponding total tracking time in seconds
96 long usteps_to_time(long usteps)
98 return (long)(asin(usteps /
99 (USTEPS_PER_ROTATION * THREADS_PER_CM * 2.0 * BASE_LEN_CM)) *
100 SIDE_REAL_SECS / PI);
103 // These variables are initialized when the motor switches
104 // from stopped to running, so we know our starting conditions
106 // Total motor steps since 100% closed, at the time the
107 // motor started running
108 static long startPositionUSteps;
109 // Total tracking time associated with total motor steps
110 static long startPositionSecs;
111 // Wall clock time at the point the motor switched from
112 // stopped to running.
113 static long startWallClockSecs;
116 // These variables are used while running to calculate our
117 // constantly changing targets
119 // The wall clock time where we need to next calculate tracking
121 static long targetWallClockSecs;
122 // Total tracking time associated with our target point
123 static long targetPositionSecs;
124 // Total motor steps associated with target point
125 static long targetPositionUSteps;
128 // Global initialization when first turned off
131 pinMode(pinInSidereal, OUTPUT);
132 pinMode(pinInHighspeed, OUTPUT);
133 pinMode(pinInDirection, OUTPUT);
135 motor.setPinsInverted(true, false, false);
136 motor.setMaxSpeed(3000);
144 // This is called whenever the motor is switch from stopped to running.
146 // It reads the current motor position which lets us see how many steps
147 // have run since the device was first turned on in the 100% closed
148 // position. From that we then figure out the total tracking time that
149 // corresponds to our open angle. This is then used by plan_tracking()
150 // to figure out subsequent deltas
151 void start_tracking(void)
153 startPositionUSteps = motor.currentPosition();
154 startPositionSecs = usteps_to_time(startPositionUSteps);
155 startWallClockSecs = millis() / 1000;
156 targetWallClockSecs = startWallClockSecs;
159 Serial.print("Enter sidereal\n");
160 Serial.print("start pos usteps: ");
161 Serial.print(startPositionUSteps);
162 Serial.print(", start pos secs: ");
163 Serial.print(startPositionSecs);
164 Serial.print(", start wclk secs: ");
165 Serial.print(startWallClockSecs);
166 Serial.print("\n\n");
171 // This is called when we need to figure out a new target position
173 // The tangent errors are small enough that over a short period,
174 // we can assume constant linear motion will give constant angular
177 // So we set our target values to what we expect them all to be
178 // 15 seconds in the future
179 void plan_tracking(void)
181 targetWallClockSecs = targetWallClockSecs + 15;
182 targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs);
183 targetPositionUSteps = time_to_usteps(targetPositionSecs);
186 Serial.print("target pos usteps: ");
187 Serial.print(targetPositionUSteps);
188 Serial.print(", target pos secs: ");
189 Serial.print(targetPositionSecs);
190 Serial.print(", target wclk secs: ");
191 Serial.print(targetWallClockSecs);
197 // This is called on every iteration of the main loop
199 // It looks at our target steps and target wall clock time and
200 // figures out the rate of steps required to get to the target
201 // in the remaining wall clock time. This applies the constant
202 // linear motion expected by plan_tracking()
204 // By re-calculating rate of steps on every iteration, we are
205 // self-correcting if we are not invoked by the arduino at a
207 void apply_tracking(long currentWallClockSecs)
209 long timeLeft = targetWallClockSecs - currentWallClockSecs;
210 long stepsLeft = targetPositionUSteps - motor.currentPosition();
211 float stepsPerSec = (float)stepsLeft / (float)timeLeft;
214 Serial.print("Target ");
215 Serial.print(targetPositionUSteps);
216 Serial.print(" curr ");
217 Serial.print(motor.currentPosition());
218 Serial.print(" left");
219 Serial.print(stepsLeft);
223 motor.setSpeed(stepsPerSec);
228 // Called when switching from stopped to running
229 // in sidereal tracking mode
230 void state_sidereal_enter(void)
237 // Called on every tick, when running in sidereal
240 // XXX we don't currently use the direction switch
241 // in sidereal mode. Could use it for sidereal vs lunar
242 // tracking rate perhaps ?
243 void state_sidereal_update(void)
245 long currentWallClockSecs = millis() / 1000;
247 if (currentWallClockSecs >= targetWallClockSecs) {
251 apply_tracking(currentWallClockSecs);
254 void state_sidereal_exit(void)
259 void state_highspeed_enter(void)
262 Serial.print("Enter highspeed\n");
267 // Called on every iteration when in non-tracking highspeed
268 // forward/back mode. Will automatically step when it
269 // hits the 100% closed position to avoid straining
271 void state_highspeed_update(void)
273 // pinInDirection is a 2-position switch for choosing direction
275 if (analogRead(pinInDirection) < 512) {
276 motor.setSpeed(5000);
279 if (motor.currentPosition() == 0) {
282 motor.setSpeed(-5000);
288 void state_highspeed_exit(void)
293 void state_off_enter(void)
296 Serial.print("Enter off\n");
301 void state_off_update(void)
306 void state_off_exit(void)
314 // pinInSidereal/pinInHighspeed are two poles of a 3-position
315 // switch, that let us choose between sidereal tracking,
316 // stopped and highspeed mode
317 if (analogRead(pinInSidereal) < 512) {
318 barndoor.transitionTo(stateSidereal);
319 } else if (analogRead(pinInHighspeed) < 512) {
320 barndoor.transitionTo(stateHighspeed);
322 barndoor.transitionTo(stateOff);
331 // indent-tabs-mode: nil