[Solved] Accelstepper help needed, Issues running multiple steppers

I'm using an Uno and two stepper(X and R) connected to a CNC shield for bench testing (A different shield is used on the prototype machine but that is not the issue)

My intent is to run a "sled" (X) back and forth, from position 0->500->0->500 and so on and at the same time have a rotating arm (R) moving from position 0->300->0->300 and so on BUT, I want R to only move at the end of the sled stroke, currently set to start when the sled has 150 steps left to move before it's endpoint.

See below for youtube video of my prototype machine, Serial printout and source code. I'm open to any suggestion on this.

Regards,
Emil

This video show a prototype of the machine I'm working on where I have one stepper moving at the time in sequence(with no issues), but I want the rotation of the arm to start it's movement earlier with acceleration, before the sled reach its endpoint Prototype machine

It starts off right but when the sled hit 500 and turn, the R stepper loose it's target.

I added a while(1) to stop the loop after one cycle to check the serial printout for troubleshooting and the R starts fine...
.
.
.
2:37:48.784 -> X steps to go 150
22:37:48.784 -> R steps to go 0
22:37:48.784 -> X steps to go 149
22:37:48.784 -> R steps to go 0
22:37:48.784 -> R starts ---------------------------------
22:37:48.784 -> X steps to go 148
22:37:48.784 -> R steps to go 299
22:37:48.824 -> X steps to go 147
22:37:48.824 -> R steps to go 299
22:37:48.824 -> X steps to go 146
22:37:48.824 -> R steps to go 299
22:37:48.824 -> X steps to go 145
22:37:48.824 -> R steps to go 299

Then, when X get to 0 then R jumps from 200 steps left to go to -101 to go. (I have tried different speeds, accelerations, endpoint targets and get similar issue but different values.)
.
.
.
.
22:37:50.844 -> X steps to go 1
22:37:50.884 -> R steps to go 201
22:37:50.884 -> X steps to go 1
22:37:50.884 -> R steps to go 201
22:37:50.884 -> X steps to go 0
22:37:50.884 -> R steps to go 200
22:37:50.884 -> R starts ---------------------------------
22:37:50.884 -> X steps to go -500
22:37:50.884 -> R steps to go -101
22:37:50.884 -> X steps to go -500
22:37:50.884 -> R steps to go -101
22:37:50.884 -> X steps to go -500
22:37:50.884 -> R steps to go -102
22:37:50.925 -> X steps to go -500
22:37:50.925 -> R steps to go -102
22:37:50.925 -> X steps to go -500

My guess is that I'm not using the library correctly for multiple steppers, or I have "found" a bug or a limitation?

#include <AccelStepper.h>

// Stepper Driver pins
const byte XstepPin = 2;       // Stepper motor for slide(X)
const byte XdirectionPin = 5;

const byte RstepPin = 3;       // Stepper motor for rotating arm(R)
const byte RdirectionPin = 6;

const byte enablePin = 8;      // Enable pin for both drivers

const int xMax = 500;         // End position in steps for X
const int rMax = 300;          // End position in steps for R

const int rStartPoint = 150;   // R starts when X have rStartPoint steps left to reach its endpoint

byte xDirection = 0;           // Direction of x(0 = going Right, 1 = going left)



// Define steppers (Mode, Step Pin, Direction Pin)
AccelStepper stepperX(AccelStepper:: DRIVER, XstepPin, XdirectionPin); // Slider stepper
AccelStepper stepperR(AccelStepper:: DRIVER, RstepPin, RdirectionPin); // Rotationg arm stepper

void setup()
{
  Serial.begin(57600);

  pinMode(enablePin, OUTPUT);       // Setting up enable pin(LOW active, HIGH off)

  digitalWrite(enablePin, LOW);     // Set steppers to active


  stepperX.setMaxSpeed(500);        // Max speed for X stepper
  stepperX.setAcceleration(100);    // Acceleration for R stepper

  stepperR.setMaxSpeed(100 );       // Max speed for R stepper
  stepperR.setAcceleration(50);     // Acceleration for R stepper

  stepperX.moveTo(xMax);            // Set initial target for slide, to move right (X stepper)

}

void loop()
{
  // Moving X to the Right  ------------------------------------------------------------------------------------------------

  while (stepperX.distanceToGo() != 0) {                            // Move side if distance to go is more then 0
    if (stepperX.distanceToGo() < rStartPoint && xDirection == 0) { // Set goal for R steper when slide is close to endpoint
      xDirection = 1;
      Serial.println("R starts ---------------------------------");  // For trouble shooting
      stepperR.moveTo(rMax);
    }
    stepperX.run();            // Run steppers, X always, R only if it has a new goal
    stepperR.run();
    currentPositions();
  }

  stepperX.moveTo(0);       // Set new target for slide, to move left (X stepper)

  // Moving X to the Right  ------------------------------------------------------------------------------------------------

  while (stepperX.distanceToGo() != 0) {                            // Move side if distance to go is more then 0
    if (stepperX.distanceToGo() < rStartPoint && xDirection == 1) { // Set goal for R steper when slide is close to endpoin
      xDirection = 0;
      Serial.println("R starts ---------------------------------");  // For trouble shooting
      stepperR.moveTo(0);
    }
    stepperX.run();
    stepperR.run();
    currentPositions();
  }


  digitalWrite(enablePin, HIGH);   // Disable the stepper dirvers if trouble shooting
  while (1);                       // Stop here, used for trouble shooting

  stepperX.moveTo(xMax);      // Set new target for slide, to move left (X stepper)

}



// Trouble shooting printouts of distances to go
void currentPositions() {
  Serial.print("X steps to go ");
  Serial.println(stepperX.distanceToGo());

  Serial.print("R steps to go ");
  Serial.println(stepperR.distanceToGo());
}

Hi Emila,

welcome to the forum. Pretty detailed description of what you want to do.
THere is one thing that you should do for posting.
Posting code in code-tags easiest way to do this is
press Ctrl-T for auto-format code-indention
doing a right-click in the Arduino-IDE choose copy for forum
and then paste the clipboard into a posting.

I have gearched for the accelstepper-reference and found this

classAccelStepper

This reference says

long AccelStepper::distanceToGo ( )
The distance from the current position to the target position.

Returns
the distance from the current position to the target position in steps. Positive is clockwise from the current position.

This sounds like for counterclockwise rotation of the stepper-motor the sign will be negative.
So your code has to include the change of the sign.

Same thing for

long AccelStepper::currentPosition ( )
The current motor position.

Returns
the current motor position in steps. Positive is clockwise from the 0 position.

google is always worth a five minute search with specific keywords

then I did a gearch with the following keywords "arcuino accelstepper coordinated motion of two stepper motors"

very specific - even hat a spelling error :wink: but successfull!
google came up with this hit

Co-ordinated stepper movements using AccelStepper

Another approach could be to program the bresenham algorithm on your own.
The bresenham-algorithm is able to coordinate two stepper-axles making them move in sync but with different speeds.

EDIT: The code from Robin2 does implement the Bresenham-algorithm on the 2nd and 3rd axle.
The basic principle is to bit-bang step and direction.

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

In your case the second move starts later. If your rotating axle works without acceleration you just would have to add an if-condition if x-pos at start

best regards Stefan

I'm not sure I understand the sequence that you are trying to implement.

It seems as if you want the pins to rotate to a new position after the slider has completed its move - but I don't think that's what your code is trying to do.

And when posting code please use the code button </> so it looks like this and is easy to copy to a text editor

#include <AccelStepper.h>

// Stepper Driver pins
const byte XstepPin = 2;       // Stepper motor for slide(X)
const byte XdirectionPin = 5;

const byte RstepPin = 3;       // Stepper motor for rotating arm(R)
const byte RdirectionPin = 6;

const byte enablePin = 8;      // Enable pin for both drivers

const int xMax = 500;         // End position in steps for X
const int rMax = 300;          // End position in steps for R

const int rStartPoint = 150;   // R starts when X have rStartPoint steps left to reach its endpoint

byte xDirection = 0;           // Direction of x(0 = going Right, 1 = going left)



// Define steppers (Mode, Step Pin, Direction Pin)
AccelStepper stepperX(AccelStepper:: DRIVER, XstepPin, XdirectionPin); // Slider stepper
AccelStepper stepperR(AccelStepper:: DRIVER, RstepPin, RdirectionPin); // Rotationg arm stepper

void setup()
{  
  Serial.begin(57600);
  
  pinMode(enablePin, OUTPUT);       // Setting up enable pin(LOW active, HIGH off)
     
  digitalWrite(enablePin, LOW);     // Set steppers to active


  stepperX.setMaxSpeed(500);        // Max speed for X stepper
  stepperX.setAcceleration(100);    // Acceleration for R stepper
  
  stepperR.setMaxSpeed(100 );       // Max speed for R stepper
  stepperR.setAcceleration(50);     // Acceleration for R stepper

  stepperX.moveTo(xMax);            // Set initial target for slide, to move right (X stepper)
    
}

void loop()
{
  // Moving X to the Right  ------------------------------------------------------------------------------------------------
  
  while(stepperX.distanceToGo() != 0){                              // Move side if distance to go is more then 0
    if (stepperX.distanceToGo() < rStartPoint && xDirection == 0){  // Set goal for R steper when slide is close to endpoint
     xDirection = 1;
     Serial.println("R starts ---------------------------------");  // For trouble shooting
     stepperR.moveTo(rMax);
    }
   stepperX.run();            // Run steppers, X always, R only if it has a new goal                                    
   stepperR.run();
   currentPositions();         
  }

    stepperX.moveTo(0);       // Set new target for slide, to move left (X stepper)

 // Moving X to the Right  ------------------------------------------------------------------------------------------------
 
   while(stepperX.distanceToGo() != 0){                              // Move side if distance to go is more then 0
     if (stepperX.distanceToGo() < rStartPoint && xDirection == 1){  // Set goal for R steper when slide is close to endpoin
        xDirection = 0;
        Serial.println("R starts ---------------------------------");  // For trouble shooting
        stepperR.moveTo(0);
     }
   stepperX.run();
   stepperR.run();
   currentPositions();
  }
  
 
  digitalWrite(enablePin, HIGH);   // Disable the stepper dirvers if trouble shooting
  while (1);                       // Stop here, used for trouble shooting
   
  stepperX.moveTo(xMax);      // Set new target for slide, to move left (X stepper)
  
}



// Trouble shooting printouts of distances to go
void currentPositions(){
  Serial.print("X steps to go ");
  Serial.println(stepperX.distanceToGo());

  Serial.print("R steps to go ");
  Serial.println(stepperR.distanceToGo());
}

...R

accelstepper is not good suited for synchronised action of multiple stepper-motors.
They are not in sync as both do the same amout of steps in the same time
but have to act synchronised:
stepper-motor start at a certain well defined point accelerate move deccelerate. all in a well defined way.

So another idea is to run a loop at a frequency that is higher than both stepping-frequencies.

through arrays of bytes the pulses are creating for accel. /and deccel

each black line is an array element.
But not all of them create a step-pulse. Only the yellow and the red lines create step-pulses which means the array-element is set to "HIGH"
The array-elements are "counted up/down for the movement. When ever an array-element is set HIGH it creates step-pulse. if set LOW not.

The graphic shall illustrate the basic principle how it is done. If you could use an array for all steps depends on the resolution you need for the acclerating-phase,
RAM-usage can be reduced by adding if-conditions that switch from creating pulses through array to a steady method.

as pseudo-code

if StepCount < 80
create steps with array // acceleration phase

if (StepCount >= 80 && StepCount < (MaxStep - 80)
create constant speed steps

if StepCount > MaxStep - 80
create steps with array // decceleration phase

same thing for the second stepper-motor

if stepCount > 300
Create Steps For Steppe-Motor 2

I think this could even be put into an timer-triggered interrupt-service-routine to run in the backround.

best regards Stefan

I tried to measure the time the accel./deccel. needs in the video by reducing the playbackspeed to 0.25.
Whole movement 500 steps taking 3 seconds (at 0.25 speed) around 1 second for accel./deccel. which would mean around 100 steps running the loop at 4times of the max stepper-freq. would require 400 array-elements. Quite a lot.

So a method that creates a series of wait-times between the step-pulses would reduce the RAM-usage.
Then the step-creating works as this:

a loop running at a pretty high speed each array-element contains a number.
The loop increases a counter and compares actual-counter-value with number of the array-element

if ( LoopCnt == AccelCnt[stepNr] )
create pulse
}

AccelCnt[0] : 300
AccelCnt[1] : 200
AccelCnt[2] : 150
AccelCnt[3] : 120
AccelCnt[4] : 100
AccelCnt[5] : 80
AccelCnt[6] : 75
etc.

lower number means counting up needs shorter time => higher step-frequency

This method allows all kinds of acceleration-curve and maximum speeds
The same thing is done with a second array for the second stepper-motor.
Then the two stepper-motors can run at different speeds and still are always "in sync"
as the step-pulse-creating is based on the same loop.

best regards Stefan

Robin2:
I'm not sure I understand the sequence that you are trying to implement.

It seems as if you want the pins to rotate to a new position after the slider has completed its move - but I don't think that's what your code is trying to do.

And when posting code please use the code button </> so it looks like this and is easy to copy to a text editor

#include <AccelStepper.h>

// Stepper Driver pins
const byte XstepPin = 2;       // Stepper motor for slide(X)
const byte XdirectionPin = 5;

const byte RstepPin = 3;       // Stepper motor for rotating arm(R)
const byte RdirectionPin = 6;

const byte enablePin = 8;      // Enable pin for both drivers

const int xMax = 500;         // End position in steps for X
const int rMax = 300;          // End position in steps for R

const int rStartPoint = 150;   // R starts when X have rStartPoint steps left to reach its endpoint

byte xDirection = 0;           // Direction of x(0 = going Right, 1 = going left)

// Define steppers (Mode, Step Pin, Direction Pin)
AccelStepper stepperX(AccelStepper:: DRIVER, XstepPin, XdirectionPin); // Slider stepper
AccelStepper stepperR(AccelStepper:: DRIVER, RstepPin, RdirectionPin); // Rotationg arm stepper

void setup()
{  
 Serial.begin(57600);
 
 pinMode(enablePin, OUTPUT);       // Setting up enable pin(LOW active, HIGH off)
   
 digitalWrite(enablePin, LOW);     // Set steppers to active

stepperX.setMaxSpeed(500);        // Max speed for X stepper
 stepperX.setAcceleration(100);    // Acceleration for R stepper
 
 stepperR.setMaxSpeed(100 );       // Max speed for R stepper
 stepperR.setAcceleration(50);     // Acceleration for R stepper

stepperX.moveTo(xMax);            // Set initial target for slide, to move right (X stepper)
   
}

void loop()
{
 // Moving X to the Right  ------------------------------------------------------------------------------------------------
 
 while(stepperX.distanceToGo() != 0){                              // Move side if distance to go is more then 0
   if (stepperX.distanceToGo() < rStartPoint && xDirection == 0){  // Set goal for R steper when slide is close to endpoint
    xDirection = 1;
    Serial.println("R starts ---------------------------------");  // For trouble shooting
    stepperR.moveTo(rMax);
   }
  stepperX.run();            // Run steppers, X always, R only if it has a new goal                                    
  stepperR.run();
  currentPositions();        
 }

stepperX.moveTo(0);       // Set new target for slide, to move left (X stepper)

// Moving X to the Right  ------------------------------------------------------------------------------------------------

while(stepperX.distanceToGo() != 0){                              // Move side if distance to go is more then 0
    if (stepperX.distanceToGo() < rStartPoint && xDirection == 1){  // Set goal for R steper when slide is close to endpoin
       xDirection = 0;
       Serial.println("R starts ---------------------------------");  // For trouble shooting
       stepperR.moveTo(0);
    }
  stepperX.run();
  stepperR.run();
  currentPositions();
 }

digitalWrite(enablePin, HIGH);   // Disable the stepper dirvers if trouble shooting
 while (1);                       // Stop here, used for trouble shooting
 
 stepperX.moveTo(xMax);      // Set new target for slide, to move left (X stepper)
 
}

// Trouble shooting printouts of distances to go
void currentPositions(){
 Serial.print("X steps to go ");
 Serial.println(stepperX.distanceToGo());

Serial.print("R steps to go ");
 Serial.println(stepperR.distanceToGo());
}




...R

Thanks Robin for your feedback
I want one stepper (X) to move back and forth with acceleration and no delay at each end(and this works just fine) I then want the second stepper(R) to only move just before, during and just after X passes it's endpoints and not move during X's "long" pass in the middle.

Also, thanks for the input on how to post the source code in the forum, I should had checked that before I posted.
Regards,
Emil

StefanL38:
accelstepper is not good suited for synchronised action of multiple stepper-motors.

I don't think the OP needs that sort of synchronisation. His project is not like a 3D printer.

The only synchronisation seems to be the start time for the moves of the R motor.

...R

StefanL38:
I tried to measure the time the accel./deccel. needs in the video by reducing the playbackspeed to 0.25.
Whole movement 500 steps taking 3 seconds (at 0.25 speed) around 1 second for accel./deccel. which would mean around 100 steps running the loop at 4times of the max stepper-freq. would require 400 array-elements. Quite a lot.

So a method that creates a series of wait-times between the step-pulses would reduce the RAM-usage.
Then the step-creating works as this:

a loop running at a pretty high speed each array-element contains a number.
The loop increases a counter and compares actual-counter-value with number of the array-element

if ( LoopCnt == AccelCnt[stepNr] )
create pulse
}

AccelCnt[0] : 300
AccelCnt[1] : 200
AccelCnt[2] : 150
AccelCnt[3] : 120
AccelCnt[4] : 100
AccelCnt[5] : 80
AccelCnt[6] : 75
etc.

lower number means counting up needs shorter time => higher step-frequency

This method allows all kinds of acceleration-curve and maximum speeds
The same thing is done with a second array for the second stepper-motor.
Then the two stepper-motors can run at different speeds and still are always "in sync"
as the step-pulse-creating is based on the same loop.

best regards Stefan

Thanks Stefan for your feedback on both how to post here(I edited my original post to be source code friendly) and the issue I have.

I'll read through your replies and I will do so again tonight and see how I can change the code to better control the steppers.
Regards,
Emil

Emila:
I want one stepper (X) to move back and forth with acceleration and no delay at each end(and this works just fine) I then want the second stepper(R) to only move just before, during and just after X passes it's endpoints and not move during X's "long" pass in the middle.

I think you can achieve that with much simpler code. Perhaps like this (not tested)

void setup() {
    // other stuff
    
    xDirection = 'R'; // moving to the right
    stepperXdistance = xMax;
    stepperX.move(stepperXdistance)
    
    stepperRstarted = false
}



void loop() {

    if (stepperX.distanceToGo() < rStartPoint and stepperRstarted = false) {  
        if (xDirection == 'R') {
            stepperRdistance = rMax;
        }
        else {
            stepperRdistance = - rMax;
        }
         stepperR.moveTo(stepperRdistance);
         stepperRstarted = true;
    }
    
    if (stepperX.distanceToGo() == 0) {
        if (xDirection == 'R') { // doing it this way for clarity
            xDirection = 'L';
        }
        else {
            xDirection = 'R';
        }
        stepperXdistance = - stepperXdistance;
        stepperX.move(stepperXdistance);
    }
    
    if (stepperR.distanceToGo == 0) {
        stepperRstarted = false;
    }

    stepperX.run();                         
    stepperR.run();
    
}

...R

Emila:
...then want the second stepper(R) to only move just before, during and just after X passes it's endpoints and not move during X's "long" pass in the middle.

So this means like in this text-picture "the left and right rotate-version

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
RRRRRRRRRRRRRRRR----------------------------------------------RRRRRRRRRRRRRRRRRRRR

X means Xstepper is running from left to right and back
R rotating stepper runs at the beginning but stops after some time while the x-steppers moves on to the right
short before Xstepper reaches the end R the rotating stepper runs until the end
and should continue to run as the X-stepper moves from right to left?
(that's how I would understand your last posting
or should it be like this: the rotate-in-the-middle-version

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----------------------------------RRRRRRRRRRRRRRRR-------------------------------

best regards Stefan

Hi Stefan,
Attached is a simple Speed over time graph for the steppers, and it's pretty much what you showed in your last post.

Also, see this second video of the first mock-up I put together. It's slower then the mock-up in the mock-up video I linked to in the first post. This second video includes the parachute cord that give a better visual on the figure 8 winding I'm after. The hardware is slightly different but that should not effect the code. I want the winding to be smoother and slightly more time efficient.

Regards,
Emil

So this means the rotation has its maximum speed when the Xstepper stops to start move in the opposite direction.

This means at startup the rotation starts first! When rotation is at its middle the transversal movement of X-stepper starts
So I guess R-Stepper has the leading function x-stepper chimes in at a certain point x-stepper has to change direction and run back right away.

Edit: if think more about it. There has to be loop above both movements. R- stepper has pauses. But shall be at maximum speed if x-stepper is at speed zero.

best regards Stefan

StefanL38:
So this means the rotation has its maximum speed when the Xstepper stops to start move in the opposite direction.

This means at startup the rotation starts first! When rotation is at its middle the transversal movement of X-stepper starts
So I guess R-Stepper has the leading function x-stepper chimes in at a certain point x-stepper has to change direction and run back right away.

best regards Stefan

The first prototype that last post had a video of had a limit switch for the slide(X) and a start button. At start up of the machine the slide(x) first slid over to that limit switch and zero that stepper count (most left). Then the "operator" set up the parachute cord in the fork in the middle and manually set the rotation to one limit at roughly 30 degrees (that is that steppers zero position (I may add a limit switch for this as well later on) and the operator then press a start button. On the very first cycle, at start, I had only the slider start the movement and the first rotation happen when the slide gets to the other end. The machine then cycle for X times until the X amount of cord is bundle up. To me, the slide(X) is in constant movement back and forth and the rotation just run short burst timed on when the the slider reaches its two limits.
I'm at work now and I'll tonight look at this some more and google more on the Accelsteppers functions and perhaps look at the code in the libary to see if I can figure it out. If I'm still stuck I'll then post a short vid showing the behaviour of the stepper and I'll also look at the ideas above to run it in a different way. I may test to run stepper R without acceleration at constant speed during the time the slide is at the endpoint. The speed is not that high so it should be ok.
Regards,
Emil

First, Thank you all for the feedback and the warm welcome to the forum(I have been here many time for some years but this was the first time I reached out for help)

Second, the issue is solved.
I added in serial printouts all over the code to print out "currentPosition, dsitanceToGo, and target.Position". Reading the printout I realized that at the second if statement, see below, the "stepperX.distanceToGo()" end up as a negative value. I added in a " *-1 " and it solved the issue.

I found though, unfortunately , that the steppers jerks a little when new goals are set so I'll see how I fix that.

if (stepperX.distanceToGo() < rStartPoint && xDirection == 1) {

I tested the code on the latest mock-up and it's working just as intended, really smooth, no jerking. I guess the better drivers on this setup handle the signals better. I just need to tweak/calibrate the values for the moves to improve the timing.

Regards,
Emil