Multistepper combined speed

Hi all! Its my first post here.. :slight_smile:

I'm trying to move 2 steppers at a constant speed from point A to point B. I dont really care how fast, each motor is running, what I really care about is the resulting speed of both x/y axes combined.

Is there a way to tell the library what the combined speed should be? currently I have this, and it takes abotu 50 seconds to arrive...

.....

AccelStepper stepperX(AccelStepper::DRIVER, 12, 14);
AccelStepper stepperY(AccelStepper::DRIVER, 4, 5);
MultiStepper steppers;

.....


void setup() {
      // Configure each stepper
    stepperX.setMaxSpeed(255);
    stepperY.setMaxSpeed(255);
    // Then give them to MultiStepper to manage
    steppers.addStepper(stepperX);
    steppers.addStepper(stepperY);  
}


void loop() {

    positions[0] = currX;
    positions[1] = currY;
    
    steppers.moveTo(positions);

    stepperX.setSpeed(currSpeed);
    stepperY.setSpeed(currSpeed);

    if (!steppers.run())
    {
       //motion finished, reset the status bit
       currX = 0;
       currY = 0;
       currSpeed = 0;
       currPower = 0; 
       hasCurrCmd = false;
       Serial.println("arrived!");
    }
  
}

Thank you very much for your help!

Best regards

Igor

You set the speed here

    stepperX.setSpeed(currSpeed);
    stepperY.setSpeed(currSpeed);

if you call Vx the speed vector along the X axis and Vy the speed vector along the Y axis then the perceived Speed Vector is Vx + Vy

Say you want to go from (x0, y0) to (x1, y1)

Every ∆t you'll move along the X axis by Dx = Vx . ∆t
Every ∆t you'll move along the Y axis by Dy = Vy . ∆t

if ∆x = |x1 - x0| is the same as ∆y = |y1 - y0| then you need the same amount of time, at the same given speed on both axis, to reach the destination point.

But if the distance to cross along the X axis is not the same as the one on the Y axis you'll reach one coordinate (X or Y) before the other if both motors move at the same speed.

==> So You need to calculate Vx and Vy so that you arrive at the same time.

J-M-L:
You set the speed here

    stepperX.setSpeed(currSpeed);

stepperY.setSpeed(currSpeed);

I'm not sure that that is correct. The MultiStepper documentation for moveTo() states

Set the target positions of all managed steppers according to a coordinate array. New speeds will be computed for each stepper so they will all arrive at their respective targets at very close to the same time.

Unfortunately the documentation does not say whether it uses setSpeed() at all, or whether it uses it as the value for the fastest or the slowest motor. However a short test program should allow that to be figured out.

...R

bad guess on my side, always read the documentation !
then it turns out that moveTo() calculate the Vx and Vy for you as I explained so you should not mess around with the calculated speeds after calling moveTo() --> remove

 stepperX.setSpeed(currSpeed);
stepperY.setSpeed(currSpeed);

try with

.....

AccelStepper stepperX(AccelStepper::DRIVER, 12, 14);
AccelStepper stepperY(AccelStepper::DRIVER, 4, 5);
MultiStepper steppers;

.....


void setup() {
    // Configure each stepper
    stepperX.setMaxSpeed(500);
    stepperY.setMaxSpeed(500);

    // Then give them to MultiStepper to manage
    steppers.addStepper(stepperX);
    steppers.addStepper(stepperY);  
}


void loop() {

    positions[0] = currX;
    positions[1] = currY;
    
    steppers.moveTo(positions);

    if (!steppers.run())   {       //motion finished, reset the status bit
       currX = 0;
       currY = 0;
       currSpeed = 0;
       currPower = 0; 
       hasCurrCmd = false;
       Serial.println("arrived!");
    }
}

hello guys! thank you for your answer... i THINK that i have found the proper way of doing it, but I am not sure.. it is inspired by your posts up here..

the idea is to calculate the Vx and Vy vectors, then take whatever is the bigger one, and set that as maxspeed for both steppers with "stepperY.setMaxSpeed(Vmax);"

that way, the faster motor will runn at the vector of the desired speed, and the slower motor will run accordingly..

Does that sound reasonable?

Thank you!

set that as maxspeed for both steppers with "stepperY.setMaxSpeed(Vmax);"

That only sets one dimension. What do you mean?

if the doc is right, you don't have to do anything, just let moveTo() do its job. (but I would not call it at every spin of the loop though, you should have one moveTo and then some sort of asynchronous wait until you reach the destination)

Another way to synchronise to stepper-motors to run at a certain speed-ratio is to use the bresenham algorithm

recently I came across this code

and modified it for the use of Step-Dir-stepper-drivers
I stripped it down to the pure mimimum. It does compile but for testing some calls to function line() must be added. It does not have acceleration/decceleration

The bresenham-algorithm works similar to your description. It calculates the dx/dy-ratio between position now and position to drive to
it determines if dx or dy is greater and then does use the bigger one as "leader" and the smaller as follower

The bresenham-algorithm is inside of the function line

//#include "Arduino.h"      //core arduino functions

//------------------------------------------------------------------------------
// GLOBALS
//------------------------------------------------------------------------------
#define VERSION        (1)  
#define BAUD           (115200)  
#define MAX_BUF        (64)  
#define STEPS_PER_TURN (4076)  
#define MIN_STEP_DELAY (5000) 
#define MAX_FEEDRATE   (1000000.0 / MIN_STEP_DELAY)
#define MIN_FEEDRATE   (0.01)
#define XstpMM   (109) 
#define YstpMM   (97.8) 
// for arc directions
#define ARC_CW          (-1)
#define ARC_CCW         (1)


// Motor pin definitions
#define DirX_Pin   3     
#define StepX_Pin  4     
#define DirY_Pin   5     
#define StepY_Pin  6     

// endstops
#define Xend  7
#define Yend  8




// other settings
char  serialbuffer[MAX_BUF];  // where we store the message until we get a newline
int   sofar;            // how much is in the buffer
float px, py;      // location

// speeds
float fr = 0;  // human version
long  step_delay;  // machine version

// settings
char mode_abs = 1;  
int stepper1  = 0;
int stepper2  = 0;
int laseron   = 105; 
int laseroff  = 155; 
int laserlevel = 155; 
//------------------------------------------------------------------------------
// Program
//------------------------------------------------------------------------------


// First thing this machine does on startup.  Runs only once.
void setup() {
  pinMode(DirX_Pin,  OUTPUT);
  pinMode(StepX_Pin, OUTPUT);
  pinMode(DirY_Pin,  OUTPUT);
  pinMode(StepY_Pin, OUTPUT);

  Serial.begin(BAUD);  // open coms
  feedrate((MAX_FEEDRATE + MIN_FEEDRATE) / 2);  // set default speed
  setup_controller();  //setup_controller (not yet defined) (move to endstops and set as 0,0)
  //myposition(0,0);  // set staring position

}


// After setup() this machine will repeat loop() forever.
void loop() {

}


//------------------------------------------------------------------------------
// METHODS
//------------------------------------------------------------------------------



// print the current position, feedrate, and absolute mode.
void where() {
  output("X", px);
  output("Y", py);
  output("F", fr);
  Serial.println(mode_abs ? "ABS" : "REL");
}


// stepper code:
void m1step(int dir) { // stepper motor 1 (X) activate
  //Serial.print("M1 move 1 step in dir=");
  //Serial.print(dir);
  if (dir > 0 ) { // std direction for stepper 1
    digitalWrite(DirX_Pin, HIGH);
  }
  else {
    digitalWrite(DirX_Pin, LOW);
  }
  // create a single step-pulse
  digitalWrite(StepX_Pin, HIGH);
  delayMicroseconds(MIN_STEP_DELAY);
  digitalWrite(StepX_Pin, LOW);  
}


void m2step(int dir) { // stepper motor 2 (y) activate
  //Serial.print("M1 move 1 step in dir=");
  //Serial.print(dir);
  if (dir > 0 ) { // std direction for stepper 1
    digitalWrite(DirY_Pin, HIGH);
  }
  else {
    digitalWrite(DirY_Pin, LOW);
  }  
  // create a single step-pulse
  digitalWrite(StepY_Pin, HIGH);
  delayMicroseconds(MIN_STEP_DELAY);
  digitalWrite(StepY_Pin, LOW);  
}


// delay for the appropriate number of microseconds * @input ms how many milliseconds to wait
void pause(long ms) {
  //delay(ms);
  delay( ms / 1000);
  delayMicroseconds(ms % 1000);  // delayMicroseconds doesn't work for values > ~16k.
}

// Set the feedrate (speed motors will move) * @input nfr the new speed in steps/second
void feedrate(float nfr) {
  if (fr == nfr) return; // same as last time?  quit now.
  if (nfr > MAX_FEEDRATE || nfr < MIN_FEEDRATE) { // don't allow crazy feed rates
    Serial.print(F("New feedrate must be greater than "));
    Serial.print(MIN_FEEDRATE);
    Serial.print(F("steps/s and less than "));
    Serial.print(MAX_FEEDRATE);
    Serial.println(F("steps/s."));
    return;
  }
  step_delay = 1000000.0 / nfr;
  if (step_delay < MIN_STEP_DELAY) {
    step_delay = MIN_STEP_DELAY;
  }
  fr = nfr;
}

//move to enstop on x and y and stop, set the position to x=0 y=0
void setup_controller() {
  int XsensorValue = 0;
  int YsensorValue = 0;
  while (XsensorValue < 150) {
    m1step(-1);
    pause(step_delay);
    XsensorValue = analogRead(Xend);
  }
  while (YsensorValue < 150) {
    m2step(1);
    pause(step_delay);
    YsensorValue = analogRead(Yend);
  }
  px = 0;
  py = 0;
}

/**
   Uses bresenham's line algorithm to move both motors
   @input newx the destination x position
   @input newy the destination y position
   **/
void line(float newx, float newy) {
  long i;
  long over = 0;
  long dx  = newx - px;
  long dy  = newy - py;
  int dirx = dx > 0 ?  1 : -1;
  int diry = dy > 0 ? -1 : 1; // because the motors are mounted in opposite directions
  dx = abs(dx);
  dy = abs(dy);
  if (dx > dy) {
    over = dx / 2;
    for (i = 0; i < dx; ++i) {
      m1step(dirx);
      over += dy;
      if (over >= dx) {
        over -= dx;
        m2step(diry);
      }
      pause(step_delay);
    }
  } else {
    over = dy / 2;
    for (i = 0; i < dy; ++i) {
      m2step(diry);
      over += dx;
      if (over >= dy) {
        over -= dy;
        m1step(dirx);
      }
      pause(step_delay);
    }
  }
  px = newx;
  py = newy;
}



//------------- debugging --------------------
void output(const char *code, float val) {
  Serial.print(code);
  Serial.println(val);
}

/**
   Set the logical position
   @input npx new position x
   @input npy new position y
*/
void myposition(float npx, float npy) {
  // here is a good place to add sanity tests
  px = npx;
  py = npy;
}

best regards Stefan

1 Like

AFAIK stepper.setMaxSpeed() only applies to the AccelStepper library when it is using acceleration. The MultiStepper library does not use acceleration.

When using a constant speed stepper.setSpeed() is what determines the speed.

I presume the MultiStepper library uses the Bresenham algorithm or an equivalent.

...R

Robin2:
AFAIK stepper.setMaxSpeed() only applies to the AccelStepper library when it is using acceleration. The MultiStepper library does not use acceleration.

When using a constant speed stepper.setSpeed() is what determines the speed.

I presume the MultiStepper library uses the Bresenham algorithm or an equivalent.

...R

the sample on the official page uses maxspeed: AccelStepper: MultiStepper.pde
the idea is that if i know the speed of the fastest motor, it will automatically limit the slower one.. at elast that is what I hope...

J-M-L:
You set the speed here

    stepperX.setSpeed(currSpeed);

stepperY.setSpeed(currSpeed);




if you call V<sub>x</sub> the speed vector along the X axis and V<sub>y</sub> the speed vector along the Y axis then the perceived Speed Vector is V<sub>x</sub> + V<sub>y</sub> 

Say you want to go from (x<sub>0</sub>, y<sub>0</sub>) to (x<sub>1</sub>, y<sub>1</sub>) 

Every ∆t you'll move along the X axis by D<sub>x</sub> = V<sub>x</sub> . ∆t 
Every ∆t you'll move along the Y axis by D<sub>y</sub> = V<sub>y</sub> . ∆t 

if ∆<sub>x</sub> = |x<sub>1</sub> - x<sub>0</sub>| is the same as ∆<sub>y</sub> = |y<sub>1</sub> - y<sub>0</sub>| then you need the same amount of time, at the same given speed on both axis, to reach the destination point.

But if the distance to cross along the X axis is not the same as the one on the Y axis you'll reach one coordinate (X or Y) before the other if both motors move at the same speed.

==> So You need to calculate V<sub>x</sub> and V<sub>y</sub> so that you arrive at the same time.

what do you think would be a good way to calculate the single Vx and Vy? I know that some calculations are very intensive for the arduino... Is there a preferred way to calculate this or are all options quite bad?

You can have a look how it’s done in the accelStepper library

C++ for Arduino has a lot of math-functions. including aquare and square-root

I'm not sure what you want to use as input and what "output" needs to be calculated.

If you want to have a resulting speed V_r and this speed has to be split into a V_x and a V_y-speed

you can take any value for V_x or V_y and calculate the other with the pythagoras

given V_R and V_x V_y = squareroot( (V_r)^2 - (V_x)^2 )

If you have start-coordinates (Xs |Ys) and endcoordiantes (Xe | Ye) you can calculate the slope

slope = (Ye - Ys) / (Xe - Xs)

or dx = Xe - Xs and dy = Ye - Ys

slope = dy / dx

the slope is the same as tangens (alpha) with alpha beeing the angle between X-Axis the the direct way from start to endpoint

dy / dx = tan alpha

so V_y = V_r * tan aplha = V_r * dy / dx

V_y = V_r * dy / dx

and V_x = V_r * cos alpha

this means calculate alpha with
alpha = arcustangens(dy/dx) so you can do cos (alpha)

V_x = V_r * cos ( arctan(dy/dx) )

The math does not get simpler like that.
You wrote

I dont really care how fast, each motor is running, what I really care about is the resulting speed of both x/y axes combined.

So I guess the calculation will be made fast enough.
If not consider using a Teensy 4.0 ($20) or Teensy 4.1 ($30) running at 600 Mhz able of 32Bit-calculations
compared to 8bit at 16 MHz of an Arduino
best regards Stefan

StefanL38:
C++ for Arduino has a lot of math-functions. including aquare and square-root

I'm not sure what you want to use as input and what "output" needs to be calculated.

If you want to have a resulting speed V_r and this speed has to be split into a V_x and a V_y-speed

you can take any value for V_x or V_y and calculate the other with the pythagoras

given V_R and V_x V_y = squareroot( (V_r)^2 - (V_x)^2 )

If you have start-coordinates (Xs |Ys) and endcoordiantes (Xe | Ye) you can calculate the slope

slope = (Ye - Ys) / (Xe - Xs)

or dx = Xe - Xs and dy = Ye - Ys

slope = dy / dx

the slope is the same as tangens (alpha) with alpha beeing the angle between X-Axis the the direct way from start to endpoint

dy / dx = tan alpha

so V_y = V_r * tan aplha = V_r * dy / dx

V_y = V_r * dy / dx

and V_x = V_r * cos alpha

this means calculate alpha with
alpha = arcustangens(dy/dx) so you can do cos (alpha)

V_x = V_r * cos ( arctan(dy/dx) )

The math does not get simpler like that.
You wrote So I guess the calculation will be made fast enough.
If not consider using a Teensy 4.0 ($20) or Teensy 4.1 ($30) running at 600 Mhz able of 32Bit-calculations
compared to 8bit at 16 MHz of an Arduino
best regards Stefan

holy moly thank you very much! now i have a lot to read and understand.. :smiley: what i have is the delta X and delta Y of the current movement and the desired combined speed... I will try! thank you very much again!

StefanL38:
V_y = V_r * dy / dx

I think that I am a bit lost here.. Lets take for example the unit circle, and my desired speed is 1 and the desired distance is also 1... At an agnle of 30°, my horizontal speed would be 0,86 and my vertical speed would be 0,5. the resulting speed should then be 1.
but if I use your formula, I get this: V_y = 1 * 0,5 / 0,86... this gives me a V_y of 0,5813 instead of 0,5...
what am I doing wrogn?

my fault: I was a little too quick with concluding which length is what in the rectangular triangle.

V_y = V_r * sin(arctan(dy/dx))

and V_x = V_r * cos((arctan(dy/dx))

And that is just the standard-thing if you if want to draw a circle through incrementing an angle

best regards Stefan

StefanL38:
my fault: I was a little too quick with concluding which length is what in the rectangular triangle.

V_y = V_r * sin(arctan(dy/dx))

and V_x = V_r * cos((arctan(dy/dx))

And that is just the standard-thing if you if want to draw a circle through incrementing an angle

best regards Stefan

thank you very much.. lookint into it tomorrow.. will go to bed now.. meanwhile I made this, should work too, right?

  int deltaStepsX = destStepX - curX; //x distance of movement
  int deltaStepsY = destStepY - curY; //y distance of movement
  
  double totaldistance;
  totaldistance = sqrt(  (deltaStepsX)^2 + (deltaStepsY)^2 ); //combined (diagonal) distance of movement
  
  int maxSpeedReturn;
  if (deltaStepsX > deltaStepsY)
  {
    maxSpeedReturn = (deltaStepsX / totaldistance) * desSpeed; //proportion between diagonal and longest side, multiplied by the desired speed...
  }
  else
  {
    maxSpeedReturn = (deltaStepsY / totaldistance) * desSpeed; //proportion between diagonal and longest side, multiplied by the desired speed...
  }
  
  return maxSpeedReturn; //return speed of fastest stepper. other stepper is calculated automatically by library.

quick test:

desired speed: 1
diagonal distance: 1
dx = 0,866
dy = 0,5
totaldistance = sqrt ( 0,866^2 + 0,5^2) = 1

dx > dy so:

maxSpeedReturn = (0,866 / 1) * 1 = 0,866

sharkyenergy:
the idea is that if i know the speed of the fastest motor, it will automatically limit the slower one.. at elast that is what I hope...

Write a short test program to turn HOPE into KNOW

...R