Building a barn door mount, part 3: drive control software

Part 1 and part 2 of this series considered the electrical circuit construction and mathematics behind the drive mechanism respectively. There was a short demonstration of arduino code to drive the motor in part 1 but this did not attempt to do any kind of proper rate tracking since it was awaiting the mathematics in part 2. This part of the series of blog postings will thus consider the actual code required to do accurate rate tracking. For the impatient, the full working code is published under the GPL version 3 or later license at gitorious.

EDIT 2015/7/22: With gitorious going away, the code now lives at gitlab

Since the writing of part 1, a slight change was made to the usage of the two switches. The ON/ON switch, connected to analogue pin 3, is to be used to switch direction of rotation between forwards (moving with the earth’s rotation) and backwards (moving against the earth’s rotation). The ON/OFF/ON switch, connected to analogue pins 4 and 5, is to be used to control the mode of operation between automatic tracking, stopped and non-tracked high-speed. The idea of the latter mode is to allow the mount to be more quickly reset back to the starting position. For convenience the code defines constants for the analogue input and digital output pins based on their intended function

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

The core design concept for the code is to have a finite state machine associated with the ON/OFF/ON switch. Rather than implement the finite state machine concept from scratch, it reuses the existing FiniteStateMachine module. Thus in order to compile the sample code, the ZIP file for this module must be downloaded and imported into the arduino project. The same is true of the AccelStepper library used for motor control. Since the switch has three positions, there are three states in the FSM declared as global variables. When declared, each state is provided with three callbacks, one to be invoked when the state is entered, one invoked on each tick while the state is active and one to be invoked when the state is left.

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);

The state machine is used to control the operation of the stepper motor object

static AccelStepper motor(AccelStepper::DRIVER,
                          pinOutStep,
                          pinOutDirection);

Off state

Starting off with the simplest, stateOff, there is only one callback that needs any logic in it. When entering the ‘off’ state the motor needs to stop turning:

void state_off_enter(void)
{
    motor.stop();
}

Manual running state

Moving on to the next, stateManual, there is again only one callback that needs any logic in it. On each tick while in this state the motor needs to be told to move a fixed amount in the correct direction. The code arbitrarily uses a speed of 5000 – this can obviously be changed to whatever suits. There is one safety measure built-in, when the mount is running in reverse, it should automatically stop when it gets to the completely closed position. Fortunately the AccelStepper library tracks how far the motor turns, so if we assume the mount was set to the completely closed position when first turned on, it is just a matter of checking whether the motor position is zero. As a future enhancement it would also be desirable to add an upper limit on the position because if the mount opens too far it’ll run off the end of the threaded rod and likely flap open causing any attached camera to bash against the tripod legs.

void state_manual_update(void)
{
    if (analogRead(pinInDirection) < 512) {
        motor.setSpeed(5000);
        motor.runSpeed();
    } else {
        if (motor.currentPosition() == 0) {
            motor.stop();
        } else {
            motor.setSpeed(-5000);
            motor.runSpeed();
        }
    }
}

Automatic tracking state

Finally, stateAuto, requires quite a lot more complicated code in its callbacks. At any given point in the time, the speed at which the threaded rod needs to turn is dependent on how far open the mount is. First of all, the code needs to know the position of the mount when automatic tracking started, bearing in mind there could have been arbitrary movement forwards or backwards when in manual mode. As noted before, the code assumes that the mount is in the completely closed position when the electronics are first powered on, which corresponds to motor step count of 0. Thus when entering the automatic tracking state, the position of the mount can be determined from the total number of motor steps. Given the step count, the mathematics from the previous blog post show how to calculate what this corresponds to in tracking time, so a function is defined to do this conversion:

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);
}

To record the state of the mount at the time automatic tracking begins, three global variables are defined

static long startPositionUSteps;
static long startPositionSecs;
static long startWallClockSecs;

These are initialized by calling another helper function, which also records the current wallclock time reported by the arduino runtime:

void start_tracking(void)
{
    startPositionUSteps = motor.currentPosition();
    startPositionSecs = usteps_to_time(startPositionUSteps);
    startWallClockSecs = millis() / 1000;
}

With the initial position determined, the next job is to determine the speed to move the mount. Since the tangent errors from the mechanical design are quite small, it is not necessary to change the speed on every tick of the arduino code – calculating in 15 seconds blocks is sufficiently accurate. The target for the next 15 second block will be recorded in three more global variables

static long targetWallClockSecs;
static long targetPositionSecs;
static long targetPositionUSteps;

The target wall clock time is just updated in increments of 15, but the target position tracking time is updated based on the delta between the start and target wall clock time. It would be possible to just update the target position tracking time in increments of 15 too, but by using the wallclock time delta, the code automatically accounts for any execution time overhead of the tracking code itself. A subtle difference, but worth doing. Once the target position tracking time is decided, the mathematics from part 1 once again show how to convert it back into a motor step count with a small helper function:

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));
}

The three target variables are all updated by yet another helper function

void plan_tracking(void)
{
    targetWallClockSecs = targetWallClockSecs + 15;
    targetPositionSecs = startPositionSecs + (targetWallClockSecs - startWallClockSecs);
    targetPositionUSteps = time_to_usteps(targetPositionSecs);
}

With all these helper functions defined, it is possible to show what action is performed by the callback when entering the automatic tracking state. Specifically it will record the starting position and then plan the first 15 second block of tracking

void state_auto_enter(void)
{
    start_tracking();
    plan_tracking();
}

Now the automatic tracking state is ready to run, it is necessary to actually turn the motor. This is simply a matter of taking the remaining wall clock time for the current 15 second tracking block and the remaining motor steps to achieve the target position and then setting a constant speed on the motor to accomplish that target. The code to do this will recalculate speed on every tick in order to ensure smooth tracking throughout the 15 second block. Once again a helper function is declared to perform this calculation

void apply_tracking(long currentWallClockSecs)
{
    long timeLeft = targetWallClockSecs - currentWallClockSecs;
    long stepsLeft = targetPositionUSteps - motor.currentPosition();
    float stepsPerSec = (float)stepsLeft / (float)timeLeft;

    motor.setSpeed(stepsPerSec);
    motor.runSpeed();

With this final helper function it is possible to implement the callback for running the automatic tracking state. It simply has to apply the current tracking information and occasionally update the tracking target.

void state_auto_update(void)
{
    long currentWallClockSecs = millis() / 1000;

    if (currentWallClockSecs >= targetWallClockSecs) {
        plan_tracking();
    }

    apply_tracking(currentWallClockSecs);
}

Switching FSM states

With the code for operating each state defined, all that remains is to actually switch between the different states. This is done by writing the arduino main loop function to check the input pins which are connected to the operation switch.

void loop(void)
{
    if (analogRead(pinInAutomatic) < 512) {
        barndoor.transitionTo(stateAuto);
    } else if (analogRead(pinInManual) < 512) {
        barndoor.transitionTo(stateManual);
    } else {
        barndoor.transitionTo(stateOff);
    }

    barndoor.update();
}

Hardware specific constants

The actual speed of tracking depends on various constants that match the physical construction of the barn door mount. There are only 4 parameters that need to be figured out. The stepper motor will have a number of discrete steps to achieve one complete rotation. The code records this as the number of degrees per step, so if only the step count is known, simply divide that into 360, eg 360/200 == 1.8. The arduino EasyDriver board for controlling the stepper motor is very clever and can actually move the motors in smaller increments than they are officially designed for, currently 8 micro-steps. The next important parameter is the pitch of the threaded rod, which can be determined by just putting a ruler alongside the rod and counting the number of threads in one centimetre. The final piece of information is the distance between the centre of the hinge joining the two pieces of the mount, and the center of the threaded rod. This should be measured once the actual mount is constructed. All these values are defined in constants at the start of the code

static const float STEP_SIZE_DEG = 1.8;
static const float MICRO_STEPS = 8;
static const float THREADS_PER_CM = 8;
static const float BASE_LEN_CM = 30.5;

As mentioned earlier, all the code snippets shown in this blog post are taken from the complete functioning code available under the GPL version 3 or later license at gitorious. With that code compiled and uploaded to the arduino, the electronics are completed. The final part(s) of this blog series will thus focus on the mechanical construction of the mount.

Now read: part 4, construction diagrams.

21 thoughts on “Building a barn door mount, part 3: drive control software

  1. Harald

    Hi Daniel,
    first of all let me thank you for this great job.
    I built a barndoor (a little bit different to yous) and now i’d like to use your Arduino sketch to controle the stepper motor. Theoretically it should work, but now I realized a problem, where I need your help. I think you the sketch better than I do.
    My problem is that I can not close the barndoor completly. As the angle between is about 10 or so degrees at the beginning of the tracking I hae to reduce the tracking speed. How can I do this in your sketch. Is it easy to manage? I tried to give a default in startPositionSecs but I realized that there must be some other changes to do. Can you give me a hint how I can manage this without building a new barndoor?
    Thank you very much
    Harald

    Reply
    1. admin Post author

      It should be possible to alter the code so that is assumes an initial starting position of 10 degrees by just setting the initial time variables to a particular value. One complete rotation of the Earth is 86164.0419 seconds, which is 360 degrees. So 10 degrees initial angle corresponds to 86164.0419 divided by 360 multiplied by 10. So at the very least you have to set the startPositionSecs variable to the value 2393.44560833333333333333. I have a nasty feeling it might also be necessary to set startPositionUsteps to a corresponding value, but I would have to go and check the algorithm again to be sure.

      Reply
  2. Marty

    Hi Daniel,

    Thanks for the inspiration. I’ve made a slightly different barn door setup that requires slightly different calcs, but it’s been pretty simple to make the changes. I’ve taken your code and simplified it for my device, but it’s been very helpful. However, I’m struggling with the usteps_to_time function.

    In excel, if I run the time_to_usteps formula and the put the output into the usteps_to_time function, I don’t get the original number back out. I’ve done this with your functions and also mine (which use tan and inverse tan). I’m not sure if I’ve made a mistake or it’s a limitation of these inverse functions (my geometry days are long past). The time_to_usteps formulas do seem to be working fine, it’s the reverse that seems to be the problem. This isn’t an issue on the first run, but will be if you don’t start from a motor_position of 0. Any ideas?

    Reply
    1. admin Post author

      I don’t have the hardware handy to check right now, but the time_to_usteps and usteps_to_time certainly should be symmetric and able to get the same number in & out if you chain them. If that’s not happening then its definitely a bug, but from code inspection I don’t see an obvious reason why it wouldn’t be working.

      Reply
      1. Marty

        I’m wondering if it is because the inverse trigonometric functions are only partial inverse functions, hence outside certain bounds they aren’t inverse of one another. For the tracker I’m making I’ll probably just fix the initial conditions so the usteps_to_time function isn’t needed. I’ll add functionality later.

        Reply
  3. John

    Hi,

    I have recreated your deisgn but with a different stepper motor, I used a NEMA-17. For some reson the stepper motor is going the wrong direction when in sidereal mode.

    I also spotted a difference between your circuit diagram and the code pin allocations on the arduino, had to switch pins A3 and A5 around on the arduino.

    For some reason the speed that the sidereal increases at increases in speed very quickly, after about 30 seconds it is moving at just about the same speed as the fast mode.

    Any help would be appreciated.

    Reply
    1. John

      Its ok, I worked out the problem, I reversed the motor connections and it started turning the right direction and right speed.

      Reply
  4. Raduen

    Hello,

    I can’t download the fulltime code.

    Is it possible to recieve it via a PM?

    Kind Regards,
    raduen

    Reply
  5. Rob

    Hello

    I like your project & I’m currently trying to build a barn door mount using your Arduino code. Can you point me in the right direction to download the code. I looked at the link, but can’t seem to see anything that looks like it relates to this project there, & they’realso migrating all the stuff stored there.

    One other thing I was churning over in my head was the positioning bearings for mounting the motor & the nut that the leadscrew is mounted in. Looking at your calculations it looks like the pivot points should be on the inside edges of the barn door (i.e where the 2 bits of wood touch) for highest accuracy. Do you think that will add a slight inaccuracy to the barn doors tracking ability ?

    Best regards

    Rob

    Reply
    1. admin Post author

      I’ve updated the article with the link to the new GIT repository for the code.

      WRT to lengths, I measured distances from the center point of the hinge joint and/or pivot point. Getting this off by a mm or two will obviously cause inaccuracy, but IME, this will usually be dwarfed by inaccuracy most people will have in achieving polar alignment, so probably not too worth worrying about unless you are an absolute perfectionist.

      Reply
  6. Raduen

    Thank you for the link.

    I can’t seems to make it work.
    My values does not match the code.
    In debug mode the valForward =0 but the valBackward is filled with random numbers and sometimes with 0 and the motor sounds like it’s stotters. The valSpeed does not get under 134 at all. I wend and change the < x to match the values read form de analogpins but due that sometimes both valForward and valBackward are 0 the motor does not run smootly.

    If i upload the full code to the arduino the switches have no effect.

    Help

    Kind Regards,
    Raduen

    Reply
  7. Pinak

    Hello Daniel, Thank you for such a nice and detailed article on building the barn door. But when I try copy paste and compile the code on genuine arduino uno r3, it gives the following errors… Kindly guide what wrong am I doing.

    Thanks,
    Pinak

    Arduino: 1.6.7 Hourly Build 2015/12/17 04:47 (Windows 7), Board: “Arduino/Genuino Uno”

    barndoor:81: error: ‘state_sidereal_enter’ was not declared in this scope

    static State stateSidereal = State(state_sidereal_enter, state_sidereal_update, state_sidereal_exit);

    ^

    barndoor:81: error: ‘state_sidereal_update’ was not declared in this scope

    static State stateSidereal = State(state_sidereal_enter, state_sidereal_update, state_sidereal_exit);

    ^

    barndoor:81: error: ‘state_sidereal_exit’ was not declared in this scope

    static State stateSidereal = State(state_sidereal_enter, state_sidereal_update, state_sidereal_exit);

    ^

    barndoor:82: error: ‘state_highspeed_enter’ was not declared in this scope

    static State stateHighspeed = State(state_highspeed_enter, state_highspeed_update, state_highspeed_update);

    ^

    barndoor:82: error: ‘state_highspeed_update’ was not declared in this scope

    static State stateHighspeed = State(state_highspeed_enter, state_highspeed_update, state_highspeed_update);

    ^

    barndoor:82: error: ‘state_highspeed_update’ was not declared in this scope

    static State stateHighspeed = State(state_highspeed_enter, state_highspeed_update, state_highspeed_update);

    ^

    barndoor:83: error: ‘state_off_enter’ was not declared in this scope

    static State stateOff = State(state_off_enter, state_off_update, state_off_exit);

    ^

    barndoor:83: error: ‘state_off_update’ was not declared in this scope

    static State stateOff = State(state_off_enter, state_off_update, state_off_exit);

    ^

    barndoor:83: error: ‘state_off_exit’ was not declared in this scope

    static State stateOff = State(state_off_enter, state_off_update, state_off_exit);

    ^

    exit status 1
    ‘state_sidereal_enter’ was not declared in this scope

    Invalid library found in E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\AccelStepper-1.49: E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\AccelStepper-1.49
    Invalid library found in E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\FSM: E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\FSM
    Invalid library found in E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\AccelStepper-1.49: E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\AccelStepper-1.49
    Invalid library found in E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\FSM: E:\Arduino-Drivers-Utility\arduino-nightly-windows\arduino-nightly\libraries\FSM

    This report would have more information with
    “Show verbose output during compilation”
    enabled in File > Preferences.

    Reply
  8. Cameron Thomson

    Hi, I’m confused between the files contained in gitorius and gitlab, can you please confirm that the file in gitlab is the master containing all of the amendments from 2015. I’m getting heaps of error messages about “not declared in this scope”.

    Reply
  9. admin Post author

    The newer versions of Arduino force functions to be declared before use. I’ve pushed a change to gitlab which moves the FSM variables to hopefully fix these error messages.

    Reply
    1. zübeyir

      Hi, I’m too getting error messages related with newer versions of arduino software. I tried to modify the library but this time I get what Pinak says. Will it work if I use the same old ide software as you used compiling this code?

      Reply
  10. sinardet Jean-Luc

    First, congratulation for your Barn Door…
    I’m really impressif by your caculations and it would be impossible for me to do it.
    On the other hand, I’ve got some knowledges in Arduino board and photography.
    But perhaps not enough in Arduino because when I used your arduino sketch and got this message below:

    K:\Arduino\libraries\FSM/FiniteStateMachine.h:33:22: fatal error: WProgram.h: No such file or directory

    #include
    ^
    compilation terminated.
    exit status 1
    Error compiling for board Arduino Duemilanove or Diecimila.

    Hepl me, could you give me a solution for that.

    Kind Regards

    Jean-Luc

    Reply
  11. Kevin J Wren

    Daniel

    First of all, bravo a well thought out design, I have successfully wired the arduino, easy driver and stepper but when setting the current I noticed something odd when my ear was close to the stepper motor. When listening I noticed the stepper motor stutter repeatedly about every 15 seconds or so. I’m not sure how critical this is as I think it’s probably to do with the recalculation of the step rate. Have you noticed this and if so did it actually affect anything negatively? My worry is the stuttering is a slowing down of the motor and it kinda looses it’s grip on the time causing tracking inaccuracies over time.

    thanks

    Kevin

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*