Initial commit
[fstop138:barndoor.git] / barndoor.ino
1 //
2 // barndoor.ino: arduino code for an astrophotography barndoor mount
3 //
4 // Copyright (C) 2014 Daniel P. Berrange
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 //
19 // This code is used to drive the circuit described at
20 //
21 //  http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-1-arduino-stepper-motor-control/
22 //
23 // Based on the maths calculations documented at
24 //
25 //  http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-2-calculating-mount-movements/
26 //
27
28 // http://arduino-info.wikispaces.com/HAL-LibrariesUpdates
29 #include <FiniteStateMachine.h>
30
31 // http://www.airspayce.com/mikem/arduino/AccelStepper/
32 #include <AccelStepper.h>
33
34 // We don't want to send debug over the serial port by default since
35 // it seriously slows down the main loop causing tracking errors
36 //#define DEBUG
37
38 // Constants to set based on hardware construction specs
39 static const float STEP_SIZE_DEG = 1.8;  // degrees rotation per step
40 static const float MICRO_STEPS = 8;     // number of microsteps per step
41 static const float THREADS_PER_CM = 8;  // number of threads in rod per cm of length
42 static const float BASE_LEN_CM = 30.5;     // length from hinge to center of rod in cm
43
44 // Constants to set based on electronic construction specs
45 static const int pinOutStep = 9;      // Arduino digital pin connected to EasyDriver step
46 static const int pinOutDirection = 8; // Arduino digital pin connected to EasyDriver direction
47
48 static const int pinInAutomatic = 4; // Arduino analogue pin connected to automatic mode switch
49 static const int pinInManual = 5;    // Arduino analogue pin connected to manual mode switch
50 static const int pinInDirection = 3; // Arduino analogue pin connected to direction switch
51
52
53 // Derived constants
54 static const float USTEPS_PER_ROTATION = 360.0 / STEP_SIZE_DEG * MICRO_STEPS; // usteps per rod rotation
55
56
57 // Standard constants
58 static const float SIDE_REAL_SECS = 86164.0419; // time in seconds for 1 rotation of earth
59
60
61 // Setup motor class with parameters targetting an EasyDriver board
62 static AccelStepper motor(AccelStepper::DRIVER,
63                           pinOutStep,
64                           pinOutDirection);
65
66 // A finite state machine with 3 states - auto, manual and off
67 static State stateAuto = State(state_auto_enter, state_auto_update, state_auto_exit);
68 static State stateManual = State(state_manual_enter, state_manual_update, state_manual_update);
69 static State stateOff = State(state_off_enter, state_off_update, state_off_exit);
70 static FSM barndoor = FSM(stateOff);
71
72
73 // Given time offset from the 100% closed position, figure out
74 // the total number of steps required to achieve that
75 long time_to_usteps(long tsecs)
76 {
77   return (long)(USTEPS_PER_ROTATION *
78                 THREADS_PER_CM * 2.0 * BASE_LEN_CM *
79                 sin(tsecs * PI / SIDE_REAL_SECS));
80 }
81
82 // Given total number of steps from 100% closed position, figure out
83 // the corresponding total tracking time in seconds
84 long usteps_to_time(long usteps)
85 {
86   return (long)(asin(usteps /
87                      (USTEPS_PER_ROTATION * THREADS_PER_CM * 2.0 * BASE_LEN_CM)) *
88                 SIDE_REAL_SECS / PI);
89 }
90
91 void setup(void)
92 {
93   pinMode(pinInAutomatic, OUTPUT);
94   pinMode(pinInManual, OUTPUT);
95   pinMode(pinInDirection, OUTPUT);
96
97   motor.setPinsInverted(true, false, false);
98   motor.setMaxSpeed(3000);
99
100 #ifdef DEBUG
101   Serial.begin(9600);
102 #endif
103 }
104
105 // These variables are initialized when the motor switches
106 // from stopped to running, so we know our starting conditions
107
108 // Total motor steps since 100% closed, at the time the
109 // motor started running
110 static long startPositionUSteps;
111 // Total tracking time associated with total motor steps
112 static long startPositionSecs;
113 // Wall clock time at the point the motor switched from
114 // stopped to running.
115 static long startWallClockSecs;
116
117
118 // These variables are used while running to calculate our
119 // constantly changing targets
120
121 // The wall clock time where we need to next calculate tracking
122 // rate / target
123 static long targetWallClockSecs;
124 // Total tracking time associated with our target point
125 static long targetPositionSecs;
126 // Total motor steps associated with target point
127 static long targetPositionUSteps;
128
129
130 // This is called whenever the motor is switch from stopped to running.
131 //
132 // It reads the current motor position which lets us see how many steps
133 // have run since the device was first turned on in the 100% closed
134 // position. From that we then figure out the total tracking time that
135 // corresponds to our open angle. This is then used by plan_tracking()
136 // to figure out subsequent deltas
137 void start_tracking(void)
138 {
139   startPositionUSteps = motor.currentPosition();
140   startPositionSecs = usteps_to_time(startPositionUSteps);
141   startWallClockSecs = millis() / 1000;
142   targetWallClockSecs = startWallClockSecs;
143
144 #ifdef DEBUG
145   Serial.print("Enter auto\n");
146   Serial.print("start pos usteps: ");
147   Serial.print(startPositionUSteps);
148   Serial.print(", start pos secs: ");
149   Serial.print(startPositionSecs);
150   Serial.print(", start wclk secs: ");
151   Serial.print(startWallClockSecs);
152   Serial.print("\n\n");
153 #endif
154 }
155
156
157 // This is called when we need to figure out a new target position
158 //
159 // The tangent errors are small enough that over a short period,
160 // we can assume constant linear motion will give constant angular
161 // motion.
162 //
163 // So we set our target values to what we expect them all to be
164 // 15 seconds  in the future
165 void plan_tracking(void)
166 {
167   targetWallClockSecs = targetWallClockSecs + 15;
168   targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs);
169   targetPositionUSteps = time_to_usteps(targetPositionSecs);
170
171 #ifdef DEBUG
172     Serial.print("target pos usteps: ");
173     Serial.print(targetPositionUSteps);
174     Serial.print(", target pos secs: ");
175     Serial.print(targetPositionSecs);
176     Serial.print(", target wclk secs: ");
177     Serial.print(targetWallClockSecs);
178     Serial.print("\n");
179 #endif
180 }
181
182
183 // This is called on every iteration of the main loop
184 //
185 // It looks at our target steps and target wall clock time and
186 // figures out the rate of steps required to get to the target
187 // in the remaining wall clock time. This applies the constant
188 // linear motion expected by  plan_tracking()
189 //
190 // By re-calculating rate of steps on every iteration, we are
191 // self-correcting if we are not invoked by the arduino at a
192 // constant rate
193 void apply_tracking(long currentWallClockSecs)
194 {
195   long timeLeft = targetWallClockSecs - currentWallClockSecs;
196   long stepsLeft = targetPositionUSteps - motor.currentPosition();
197   float stepsPerSec = (float)stepsLeft / (float)timeLeft;
198
199 #ifdef DEBUG32
200   Serial.print("Target ");
201   Serial.print(targetPositionUSteps);
202   Serial.print("  curr ");
203   Serial.print(motor.currentPosition());
204   Serial.print("  left");
205   Serial.print(stepsLeft);
206   Serial.print("\n");
207 #endif
208
209   motor.setSpeed(stepsPerSec);
210   motor.runSpeed();
211 }
212
213
214 // Called when switching from stopped to running
215 // in sidereal tracking mode
216 void state_auto_enter(void)
217 {
218   start_tracking();
219   plan_tracking();
220 }
221
222
223 // Called on every tick, when running in sidereal
224 // tracking mode
225 //
226 // XXX we don't currently use the direction switch
227 // in auto mode. Could use it for sidereal vs lunar
228 // tracking rate
229 void state_auto_update(void)
230 {
231   long currentWallClockSecs = millis() / 1000;
232
233   if (currentWallClockSecs >= targetWallClockSecs) {
234     plan_tracking();
235   }
236
237   apply_tracking(currentWallClockSecs);
238 }
239
240 void state_auto_exit(void)
241 {
242   // nada
243 }
244
245 void state_manual_enter(void)
246 {
247 #ifdef DEBUG
248   Serial.print("Enter manual\n");
249 #endif
250 }
251
252
253 // Called on every iteration when in manul highspeed
254 // forward/back mode. Will automatically step when it
255 // hits the 100% closed position to avoid straining
256 // the motor
257 void state_manual_update(void)
258 {
259   // pinInDirection is a 2-position switch for choosing direction
260   // of motion
261   if (analogRead(pinInDirection) < 512) {
262     motor.setSpeed(5000);
263     motor.runSpeed();
264   } else {
265     if (motor.currentPosition() == 0) {
266       motor.stop();
267     } else {
268       motor.setSpeed(-5000);
269       motor.runSpeed();
270     }
271   }
272 }
273
274 void state_manual_exit(void)
275 {
276   // nada
277 }
278
279 void state_off_enter(void)
280 {
281 #ifdef DEBUG
282   Serial.print("Enter off\n");
283 #endif
284   motor.stop();
285 }
286
287 void state_off_update(void)
288 {
289   // nada
290 }
291
292 void state_off_exit(void)
293 {
294   // nada
295 }
296
297
298 void loop(void)
299 {
300   // pinInAutomatic is a 3-position switch, that lets us
301   // choose between automatic sidereal tracking, stopped
302   // and manual fast mode
303   if (analogRead(pinInAutomatic) < 512) {
304     barndoor.transitionTo(stateAuto);
305   } else if (analogRead(pinInManual) < 512) {
306     barndoor.transitionTo(stateManual);
307   } else {
308     barndoor.transitionTo(stateOff);
309   }
310   barndoor.update();
311 }
312