Move setup method further down
[fstop138:barndoor.git] / barndoor.ino
1 // -*- c -*-
2 //
3 // barndoor.ino: arduino code for an astrophotography barndoor mount
4 //
5 // Copyright (C) 2014-2015 Daniel P. Berrange
6 //
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.
11 //
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.
16 //
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/>.
19 //
20 // This code is used to drive the circuit described at
21 //
22 //  http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-1-arduino-stepper-motor-control/
23 //
24 // Based on the maths calculations documented at
25 //
26 //  http://fstop138.berrange.com/2014/01/building-an-barn-door-mount-part-2-calculating-mount-movements/
27 //
28 // The code assumes an **isosceles** drive barn door mount design.
29 //
30 // Other barndoor drive designs will require different mathematical
31 // formulas to correct errors
32
33 // http://arduino-info.wikispaces.com/HAL-LibrariesUpdates
34 #include <FiniteStateMachine.h>
35
36 // http://www.airspayce.com/mikem/arduino/AccelStepper/
37 #include <AccelStepper.h>
38
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
41 //#define DEBUG
42
43 // Constants to set based on hardware construction specs
44 //
45 // Assuming you followed the blog linked above, these few variables
46 // should be the only things that you need to change in general
47 //
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
52
53 // Nothing below this line should require changing unless your barndoor
54 // is not an Isoceles mount, or you changed the electrical circuit design
55
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
59
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
63
64
65 // Derived constants
66 static const float USTEPS_PER_ROTATION = 360.0 / STEP_SIZE_DEG * MICRO_STEPS; // usteps per rod rotation
67
68
69 // Standard constants
70 static const float SIDE_REAL_SECS = 86164.0419; // time in seconds for 1 rotation of earth
71
72
73 // Setup motor class with parameters targetting an EasyDriver board
74 static AccelStepper motor(AccelStepper::DRIVER,
75                           pinOutStep,
76                           pinOutDirection);
77
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);
83
84
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)
88 {
89     return (long)(USTEPS_PER_ROTATION *
90                   THREADS_PER_CM * 2.0 * BASE_LEN_CM *
91                   sin(tsecs * PI / SIDE_REAL_SECS));
92 }
93
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)
97 {
98     return (long)(asin(usteps /
99                        (USTEPS_PER_ROTATION * THREADS_PER_CM * 2.0 * BASE_LEN_CM)) *
100                   SIDE_REAL_SECS / PI);
101 }
102
103 // These variables are initialized when the motor switches
104 // from stopped to running, so we know our starting conditions
105
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;
114
115
116 // These variables are used while running to calculate our
117 // constantly changing targets
118
119 // The wall clock time where we need to next calculate tracking
120 // rate / target
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;
126
127
128 // Global initialization when first turned off
129 void setup(void)
130 {
131     pinMode(pinInSidereal, OUTPUT);
132     pinMode(pinInHighspeed, OUTPUT);
133     pinMode(pinInDirection, OUTPUT);
134
135     motor.setPinsInverted(true, false, false);
136     motor.setMaxSpeed(3000);
137
138 #ifdef DEBUG
139     Serial.begin(9600);
140 #endif
141 }
142
143
144 // This is called whenever the motor is switch from stopped to running.
145 //
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)
152 {
153     startPositionUSteps = motor.currentPosition();
154     startPositionSecs = usteps_to_time(startPositionUSteps);
155     startWallClockSecs = millis() / 1000;
156     targetWallClockSecs = startWallClockSecs;
157
158 #ifdef DEBUG
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");
167 #endif
168 }
169
170
171 // This is called when we need to figure out a new target position
172 //
173 // The tangent errors are small enough that over a short period,
174 // we can assume constant linear motion will give constant angular
175 // motion.
176 //
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)
180 {
181     targetWallClockSecs = targetWallClockSecs + 15;
182     targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs);
183     targetPositionUSteps = time_to_usteps(targetPositionSecs);
184
185 #ifdef DEBUG
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);
192     Serial.print("\n");
193 #endif
194 }
195
196
197 // This is called on every iteration of the main loop
198 //
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()
203 //
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
206 // constant rate
207 void apply_tracking(long currentWallClockSecs)
208 {
209     long timeLeft = targetWallClockSecs - currentWallClockSecs;
210     long stepsLeft = targetPositionUSteps - motor.currentPosition();
211     float stepsPerSec = (float)stepsLeft / (float)timeLeft;
212
213 #ifdef DEBUG32
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);
220     Serial.print("\n");
221 #endif
222
223     motor.setSpeed(stepsPerSec);
224     motor.runSpeed();
225 }
226
227
228 // Called when switching from stopped to running
229 // in sidereal tracking mode
230 void state_sidereal_enter(void)
231 {
232     start_tracking();
233     plan_tracking();
234 }
235
236
237 // Called on every tick, when running in sidereal
238 // tracking mode
239 //
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)
244 {
245     long currentWallClockSecs = millis() / 1000;
246
247     if (currentWallClockSecs >= targetWallClockSecs) {
248         plan_tracking();
249     }
250
251     apply_tracking(currentWallClockSecs);
252 }
253
254 void state_sidereal_exit(void)
255 {
256     // nada
257 }
258
259 void state_highspeed_enter(void)
260 {
261 #ifdef DEBUG
262     Serial.print("Enter highspeed\n");
263 #endif
264 }
265
266
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
270 // the motor
271 void state_highspeed_update(void)
272 {
273     // pinInDirection is a 2-position switch for choosing direction
274     // of motion
275     if (analogRead(pinInDirection) < 512) {
276         motor.setSpeed(5000);
277         motor.runSpeed();
278     } else {
279         if (motor.currentPosition() == 0) {
280             motor.stop();
281         } else {
282             motor.setSpeed(-5000);
283             motor.runSpeed();
284         }
285     }
286 }
287
288 void state_highspeed_exit(void)
289 {
290     // nada
291 }
292
293 void state_off_enter(void)
294 {
295 #ifdef DEBUG
296     Serial.print("Enter off\n");
297 #endif
298     motor.stop();
299 }
300
301 void state_off_update(void)
302 {
303     // nada
304 }
305
306 void state_off_exit(void)
307 {
308     // nada
309 }
310
311
312 void loop(void)
313 {
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);
321     } else {
322         barndoor.transitionTo(stateOff);
323     }
324     barndoor.update();
325 }
326
327 //
328 // Local variables:
329 //  c-indent-level: 4
330 //  c-basic-offset: 4
331 //  indent-tabs-mode: nil
332 // End:
333 //