//
// barndoor.ino: arduino code for an astrophotography barndoor mount
//
// Copyright (C) 2014 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/
//
// 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
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
// 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 pinInAutomatic = 4; // Arduino analogue pin connected to automatic mode switch
static const int pinInManual = 5; // Arduino analogue pin connected to manual 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 - auto, manual and off
static State stateAuto = State(state_auto_enter, state_auto_update, state_auto_exit);
static State stateManual = State(state_manual_enter, state_manual_update, state_manual_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 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);
}
void setup(void)
{
pinMode(pinInAutomatic, OUTPUT);
pinMode(pinInManual, OUTPUT);
pinMode(pinInDirection, OUTPUT);
motor.setPinsInverted(true, false, false);
motor.setMaxSpeed(3000);
#ifdef DEBUG
Serial.begin(9600);
#endif
}
// These variables are initialized when the motor switches
// from stopped to running, so we know our starting conditions
// 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;
// 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.currentPosition();
startPositionSecs = usteps_to_time(startPositionUSteps);
startWallClockSecs = millis() / 1000;
targetWallClockSecs = startWallClockSecs;
#ifdef DEBUG
Serial.print("Enter auto\n");
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.currentPosition();
float stepsPerSec = (float)stepsLeft / (float)timeLeft;
#ifdef DEBUG32
Serial.print("Target ");
Serial.print(targetPositionUSteps);
Serial.print(" curr ");
Serial.print(motor.currentPosition());
Serial.print(" left");
Serial.print(stepsLeft);
Serial.print("\n");
#endif
motor.setSpeed(stepsPerSec);
motor.runSpeed();
}
// Called when switching from stopped to running
// in sidereal tracking mode
void state_auto_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 auto mode. Could use it for sidereal vs lunar
// tracking rate
void state_auto_update(void)
{
long currentWallClockSecs = millis() / 1000;
if (currentWallClockSecs >= targetWallClockSecs) {
plan_tracking();
}
apply_tracking(currentWallClockSecs);
}
void state_auto_exit(void)
{
// nada
}
void state_manual_enter(void)
{
#ifdef DEBUG
Serial.print("Enter manual\n");
#endif
}
// Called on every iteration when in manul highspeed
// forward/back mode. Will automatically step when it
// hits the 100% closed position to avoid straining
// the motor
void state_manual_update(void)
{
// pinInDirection is a 2-position switch for choosing direction
// of motion
if (analogRead(pinInDirection) < 512) {
motor.setSpeed(5000);
motor.runSpeed();
} else {
if (motor.currentPosition() == 0) {
motor.stop();
} else {
motor.setSpeed(-5000);
motor.runSpeed();
}
}
}
void state_manual_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)
{
// pinInAutomatic is a 3-position switch, that lets us
// choose between automatic sidereal tracking, stopped
// and manual fast mode
if (analogRead(pinInAutomatic) < 512) {
barndoor.transitionTo(stateAuto);
} else if (analogRead(pinInManual) < 512) {
barndoor.transitionTo(stateManual);
} else {
barndoor.transitionTo(stateOff);
}
barndoor.update();
}