Update some comments
[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 void setup(void)
104 {
105     pinMode(pinInSidereal, OUTPUT);
106     pinMode(pinInHighspeed, OUTPUT);
107     pinMode(pinInDirection, OUTPUT);
108
109     motor.setPinsInverted(true, false, false);
110     motor.setMaxSpeed(3000);
111
112 #ifdef DEBUG
113     Serial.begin(9600);
114 #endif
115 }
116
117 // These variables are initialized when the motor switches
118 // from stopped to running, so we know our starting conditions
119
120 // Total motor steps since 100% closed, at the time the
121 // motor started running
122 static long startPositionUSteps;
123 // Total tracking time associated with total motor steps
124 static long startPositionSecs;
125 // Wall clock time at the point the motor switched from
126 // stopped to running.
127 static long startWallClockSecs;
128
129
130 // These variables are used while running to calculate our
131 // constantly changing targets
132
133 // The wall clock time where we need to next calculate tracking
134 // rate / target
135 static long targetWallClockSecs;
136 // Total tracking time associated with our target point
137 static long targetPositionSecs;
138 // Total motor steps associated with target point
139 static long targetPositionUSteps;
140
141
142 // This is called whenever the motor is switch from stopped to running.
143 //
144 // It reads the current motor position which lets us see how many steps
145 // have run since the device was first turned on in the 100% closed
146 // position. From that we then figure out the total tracking time that
147 // corresponds to our open angle. This is then used by plan_tracking()
148 // to figure out subsequent deltas
149 void start_tracking(void)
150 {
151     startPositionUSteps = motor.currentPosition();
152     startPositionSecs = usteps_to_time(startPositionUSteps);
153     startWallClockSecs = millis() / 1000;
154     targetWallClockSecs = startWallClockSecs;
155
156 #ifdef DEBUG
157     Serial.print("Enter sidereal\n");
158     Serial.print("start pos usteps: ");
159     Serial.print(startPositionUSteps);
160     Serial.print(", start pos secs: ");
161     Serial.print(startPositionSecs);
162     Serial.print(", start wclk secs: ");
163     Serial.print(startWallClockSecs);
164     Serial.print("\n\n");
165 #endif
166 }
167
168
169 // This is called when we need to figure out a new target position
170 //
171 // The tangent errors are small enough that over a short period,
172 // we can assume constant linear motion will give constant angular
173 // motion.
174 //
175 // So we set our target values to what we expect them all to be
176 // 15 seconds  in the future
177 void plan_tracking(void)
178 {
179     targetWallClockSecs = targetWallClockSecs + 15;
180     targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs);
181     targetPositionUSteps = time_to_usteps(targetPositionSecs);
182
183 #ifdef DEBUG
184     Serial.print("target pos usteps: ");
185     Serial.print(targetPositionUSteps);
186     Serial.print(", target pos secs: ");
187     Serial.print(targetPositionSecs);
188     Serial.print(", target wclk secs: ");
189     Serial.print(targetWallClockSecs);
190     Serial.print("\n");
191 #endif
192 }
193
194
195 // This is called on every iteration of the main loop
196 //
197 // It looks at our target steps and target wall clock time and
198 // figures out the rate of steps required to get to the target
199 // in the remaining wall clock time. This applies the constant
200 // linear motion expected by  plan_tracking()
201 //
202 // By re-calculating rate of steps on every iteration, we are
203 // self-correcting if we are not invoked by the arduino at a
204 // constant rate
205 void apply_tracking(long currentWallClockSecs)
206 {
207     long timeLeft = targetWallClockSecs - currentWallClockSecs;
208     long stepsLeft = targetPositionUSteps - motor.currentPosition();
209     float stepsPerSec = (float)stepsLeft / (float)timeLeft;
210
211 #ifdef DEBUG32
212     Serial.print("Target ");
213     Serial.print(targetPositionUSteps);
214     Serial.print("  curr ");
215     Serial.print(motor.currentPosition());
216     Serial.print("  left");
217     Serial.print(stepsLeft);
218     Serial.print("\n");
219 #endif
220
221     motor.setSpeed(stepsPerSec);
222     motor.runSpeed();
223 }
224
225
226 // Called when switching from stopped to running
227 // in sidereal tracking mode
228 void state_sidereal_enter(void)
229 {
230     start_tracking();
231     plan_tracking();
232 }
233
234
235 // Called on every tick, when running in sidereal
236 // tracking mode
237 //
238 // XXX we don't currently use the direction switch
239 // in sidereal mode. Could use it for sidereal vs lunar
240 // tracking rate perhaps ?
241 void state_sidereal_update(void)
242 {
243     long currentWallClockSecs = millis() / 1000;
244
245     if (currentWallClockSecs >= targetWallClockSecs) {
246         plan_tracking();
247     }
248
249     apply_tracking(currentWallClockSecs);
250 }
251
252 void state_sidereal_exit(void)
253 {
254     // nada
255 }
256
257 void state_highspeed_enter(void)
258 {
259 #ifdef DEBUG
260     Serial.print("Enter highspeed\n");
261 #endif
262 }
263
264
265 // Called on every iteration when in non-tracking highspeed
266 // forward/back mode. Will automatically step when it
267 // hits the 100% closed position to avoid straining
268 // the motor
269 void state_highspeed_update(void)
270 {
271     // pinInDirection is a 2-position switch for choosing direction
272     // of motion
273     if (analogRead(pinInDirection) < 512) {
274         motor.setSpeed(5000);
275         motor.runSpeed();
276     } else {
277         if (motor.currentPosition() == 0) {
278             motor.stop();
279         } else {
280             motor.setSpeed(-5000);
281             motor.runSpeed();
282         }
283     }
284 }
285
286 void state_highspeed_exit(void)
287 {
288     // nada
289 }
290
291 void state_off_enter(void)
292 {
293 #ifdef DEBUG
294     Serial.print("Enter off\n");
295 #endif
296     motor.stop();
297 }
298
299 void state_off_update(void)
300 {
301     // nada
302 }
303
304 void state_off_exit(void)
305 {
306     // nada
307 }
308
309
310 void loop(void)
311 {
312     // pinInSidereal/pinInHighspeed are two poles of a 3-position
313     // switch, that let us choose between sidereal tracking,
314     // stopped and highspeed mode
315     if (analogRead(pinInSidereal) < 512) {
316         barndoor.transitionTo(stateSidereal);
317     } else if (analogRead(pinInHighspeed) < 512) {
318         barndoor.transitionTo(stateHighspeed);
319     } else {
320         barndoor.transitionTo(stateOff);
321     }
322     barndoor.update();
323 }
324
325 //
326 // Local variables:
327 //  c-indent-level: 4
328 //  c-basic-offset: 4
329 //  indent-tabs-mode: nil
330 // End:
331 //