Co-ordinated stepper movements using AccelStepper

Some time ago I wrote a small Arduino program to control 3 stepper motors on my small lathe in a coordinated way. By coordinated I mean that if the three motors are required to move, say, 500, 322 and 18 steps respectively then there would be significant differences in the intervals between steps so that each motor completes its move in exactly the same time. For example if the total time for the move is 500 millisecs then the first motor will make a step every millisec; the second motor will make a step every 1.552 millisecs; and the third motor will make a step every 27 millisecs.

That Arduino program operated all the motors at constant speed without any acceleration.

One shortcoming with the arrangement is that some of the range of movement of the lathe cross-slide must be excluded because the friction in that area causes the motor to miss steps. I proved to my satisfaction that acceleration would get around this problem by writing a short program just using AccelStepper on the cross-slide axis.

However the AccelStepper library is not designed for coordinated movements. And while the MultiStepper variant of the library is designed for coordinated movements, it does not use acceleration.

While wondering how I could manage acceleration on all three axes (possibly with my own acceleration code) it occurred to me that one axis will always have the fastest (or equal fastest) step rate and it would be sufficient to apply acceleration to that axis if the movements of the other axes could be “slaved” from the movements of the fastest (or master) axis. For example, using the above figures, the second motor needs to take a step after every 1.552 steps of the first motor, and the third motor needs to take a step after every 27 steps of the first motor.

If I only need to calculate acceleration for one axis then I can use the AccelStepper library.

To make the maths easy for the Arduino I decided to calculate the lowest common multiple for the moves. Using the above figures this gives 500 * 322 * 18 or 288,000 and, from that calculate an increment-per-step for each motor. The values are 576, 894 and 16000.

I can use the AccelStepper distanceToGo() function to identify whenever a step is taken by the master motor and when that happens I increment a master counter by (in this case) 576. By checking the value of the master counter I can tell when to make the other motors step. For example when it exceeds 894 it is time to make the second motor step and when it exceeds 1788 it will be time to make the second motor take another step.

The code in the next Reply is a simple demonstration of this which works very nicely for me.
(Please keep in mind that this is my rough development code with a minimum of tidying up).

Comments etc are welcome

…R

Demo program to illustrate the use of AccelStepper as a master for controlling acceleration of coordinated movements on all three axes.

#include <AccelStepper.h>

    // define the pins
byte stepPinX = 12;
byte  dirPinX = 11;

byte stepPinY = 8;
byte  dirPinY = 7;

byte stepPinZ = 10;
byte  dirPinZ = 9;

    // create an AccelStepper instance for each axis
AccelStepper xSlide(AccelStepper::DRIVER, 12, 11);
AccelStepper ySlide(AccelStepper::DRIVER, 8, 7);
AccelStepper vSlide(AccelStepper::DRIVER, 10, 9);

    // variables for keeping track of the axis movements
long xSlideCount;
long xSlideInc;
char xSlideDir;

long ySlideCount;
long ySlideInc;
char ySlideDir;

long vSlideCount;
long vSlideInc;
char vSlideDir;

    // variables for managing the use of AccelStepper
long accelStepCount = 0;
long accelStepsToGo;
long curStepsToGo;
long masterCount;
long numSteps;
            // for simplicity I am hard-wiring these values
float stepSpeed = 400;
float accelRate = 1000;

char masterAxis;


    // Movement Data
        // these data simulate what would be sent from my PC for each move
        // masterAxis, numSteps, xSlideInc, ySlideInc, vSlideInc
int moves[2][5] = {
            {88,        -500,       -576,       -894,   16000},
            {88,         500,        576,        894,  -16000}
};
        // NOTE 88 is the ascii code for 'X' (to avoid the need for a struct)
byte numMoves = 2;
byte moveCount = 0;

    // marker to load new data after every move is finished
bool startReqd;

//====================

void setup() {

    Serial.begin(115200);
    Serial.println("Starting");

        // set the modes for the step and direction pins
    for (byte n = 7; n <=12; n++) {
        pinMode(n, OUTPUT);
    }
        // set up the acceleration parameters
    xSlide.setMaxSpeed(stepSpeed);
    xSlide.setAcceleration(accelRate);

    ySlide.setMaxSpeed(stepSpeed);
    ySlide.setAcceleration(accelRate);

    vSlide.setMaxSpeed(stepSpeed);
    vSlide.setAcceleration(accelRate);

        // get things moving
    startReqd = true;

}

//===============

void loop() {

    if (startReqd == true) {
        setStartPosition(); //to simulate new values sent from PC
    }
    else {
            // if there is a movement in progress
        if (curStepsToGo != 0) {
                // call the function for the master axis
            if (masterAxis == 'X') {
                xSlideDrive();
            }
            else if (masterAxis == 'Y') {
                ySlideDrive();
            }
            else {
                vSlideDrive();
            }
        }
            // if the movement is finished prepare the next move
        else {
            startReqd = true;
        }
    }
        // if all the moves are finished ...
    if (moveCount > numMoves) {
        while (true);
    }

}

//===============

void setStartPosition() {

        // put the move data in the appropriate variables
    masterAxis = (char) moves[moveCount][0];
    numSteps = moves[moveCount][1];
    xSlideInc = moves[moveCount][2];
    ySlideInc = moves[moveCount][3];
    vSlideInc = moves[moveCount][4];

    //~ Serial.print("masterAxis "); Serial.println(masterAxis);
    //~ Serial.print("numSteps "); Serial.println(numSteps);
    //~ Serial.print("xSlideInc "); Serial.println(xSlideInc);
    //~ Serial.print("ySlideInc "); Serial.println(ySlideInc);
    //~ Serial.print("vSlideInc "); Serial.println(vSlideInc);
    //~ Serial.println();

        // figure out the direction for each axis
    xSlideDir = 'R';
    ySlideDir = 'R';
    vSlideDir = 'R';
    digitalWrite(dirPinX, HIGH);
    digitalWrite(dirPinY, HIGH);
    digitalWrite(dirPinZ, HIGH);

    if (xSlideInc < 0) {
        xSlideDir = 'F';
        xSlideInc = -xSlideInc;
        digitalWrite(dirPinX, LOW);
    }
    if (ySlideInc < 0) {
        ySlideDir = 'F';
        ySlideInc = -ySlideInc;
        digitalWrite(dirPinY, LOW);
    }
    if (vSlideInc < 0) {
        vSlideDir = 'F';
        vSlideInc = -vSlideInc;
        digitalWrite(dirPinZ, LOW);
    }

        // set up the variables to keep track of axis movements
            // masterCount accumulates the increments for the master axis
    masterCount = 0;
    xSlideCount = xSlideInc;
    ySlideCount = ySlideInc;
    vSlideCount = vSlideInc;


    setMasterStart();
    delay(100);
    moveCount ++;
    Serial.print("Count "); Serial.println(moveCount);
    startReqd = false;
    delay(1000); // just so you can see the move has ended
}

//==============

void setMasterStart() {
        // set the move() distance for the master axis

    if (masterAxis == 'X') {
        xSlide.move(numSteps);
        curStepsToGo = xSlide.distanceToGo();
        //~ Serial.print("curStepsToGo "); Serial.println(curStepsToGo);
    }
    else if (masterAxis == 'l') {
        ySlide.move(numSteps);
        curStepsToGo = ySlide.distanceToGo();
    }
    else {
        vSlide.move(numSteps);
        curStepsToGo = vSlide.distanceToGo();
    }
}


//===============

void xSlideDrive() {
        // there is a version of this for each axis
    curStepsToGo = xSlide.distanceToGo();
        // check if the stepsToGo has changed
    if (accelStepsToGo != curStepsToGo) {
        masterCount += xSlideInc;
        accelStepsToGo = curStepsToGo;
    }
        // calls for the two "slaved" axes
    ySlideStep();
    vSlideStep();

    xSlide.run();

}

//===============

void ySlideDrive() {
    curStepsToGo = ySlide.distanceToGo();
    if (accelStepsToGo != curStepsToGo) {
        masterCount += ySlideInc;
        accelStepsToGo = curStepsToGo;
    }

    xSlideStep();
    vSlideStep();

    ySlide.run();

}

//===============

void vSlideDrive() {
    curStepsToGo = vSlide.distanceToGo();
    if (accelStepsToGo != curStepsToGo) {
        masterCount += vSlideInc;
        accelStepsToGo = curStepsToGo;
    }

    ySlideStep();
    xSlideStep();

    vSlide.run();

}

//=================

void xSlideStep() {
        // there is a version of this for each axis
    if (xSlideInc > 0) {
            // if the master count exceeds the slave count make a step
        if (xSlideCount < masterCount) {
            digitalWrite(stepPinX, HIGH);
            digitalWrite(stepPinX, LOW);
                // then increment the slave count ready for the next move
            xSlideCount += xSlideInc;
        }
    }
}

//=================

void ySlideStep() {
    if (ySlideInc > 0) {
        if (ySlideCount < masterCount) {
            digitalWrite(stepPinY, HIGH);
            digitalWrite(stepPinY, LOW);
            ySlideCount += ySlideInc;
        }
    }
}

//=================

void vSlideStep() {
    if (xSlideInc > 0) {
        if (vSlideCount < masterCount) {
            digitalWrite(stepPinZ, HIGH);
            digitalWrite(stepPinZ, LOW);
            vSlideCount += vSlideInc;
        }
    }
}

…R