Having trouble running multiple steppers at constant speed using accelstepper

Hi,

I have a project where I have to move 3 steppers at the same time, I managed to get it running using accelstepper, however it would be better if the steppers moved at constant speed, but when I modify the function to make it run at constant speed, the steppers vibrate a lot and move very slowly, increasing or decreasing the speed does not help. I think I may be misusing the library.

void run_steppers(AccelStepper stepper_a1 , AccelStepper stepper_a2, AccelStepper stepper_a3, int steps[3])
{
  //Serial.print("Called!");
  digitalWrite(dir1, LOW);
  digitalWrite(dir2, LOW);
  digitalWrite(dir3, LOW);
  stepper_a1.setSpeed(500);
  stepper_a2.setSpeed(500);
  stepper_a3.setSpeed(500);
  stepper_a1.moveTo(steps[0]);
  stepper_a2.moveTo(steps[1]);
  stepper_a3.moveTo(steps[2]);

  Serial.println(steps[0]);
  Serial.println(steps[1]);
  Serial.println(steps[2]);

  while(stepper_a1.distanceToGo() != 0 | stepper_a2.distanceToGo() != 0 | stepper_a3.distanceToGo() != 0)
  {
    stepper_a1.runSpeed();
    stepper_a2.runSpeed();
    stepper_a3.runSpeed();
  }

  stepper_a1.setCurrentPosition(0);
  stepper_a2.setCurrentPosition(0);
  stepper_a3.setCurrentPosition(0);
  return;
}

If I change the "runSpeed" calls with just "run" it works, what am I doing wrong?

AccelStepper has two distinct modes, it is easy to confuse the two. In speed mode, there is no acceleration, and the stepper runs for as long as you call runSpeed(), the step count does not apply, however you can monitor the position by calling currentPosition()

If the steppers vibrate it is probably because there is no acceleration, and you are trying to run them too fast from a standstill. Either way, you need to decide which mode to use and then use it consistently.

I don't think that's the issue, somewhere else in the code I'm running only one stepper at a time using the same speed and it works fine, I think it's breaking when trying to run multiple at once.

void run_stepper(AccelStepper stepper, int steps)
{
  stepper.moveTo(steps);
  stepper.setSpeed(1000);
  if(steps < 0){
  stepper.setSpeed(-1000);
  }
  while(stepper.distanceToGo() != 0)
  {
    stepper.runSpeed();
  }

  stepper.setCurrentPosition(0);
  return;
}

This runs fine, it's when incorporating more steppers where it breaks.

This while loop blocks any other stepper from moving.

Bitwise OR may be giving unexpected results; should be

while(stepper_a1.distanceToGo() != 0 || stepper_a2.distanceToGo() != 0 || stepper_a3.distanceToGo() != 0)
  {

Also, I wonder what happens if you call runSpeed when distanceToGo() is zero, does it go negative?

That's from my response, it's another function that I use to move a single stepper, so that's actually ok.

It had the same result; however, I think I may have to use a multistepper object instead.

I just implemented it and it sort of works:

void run_steppers(MultiStepper steppers, long steps[3])
{
  //Serial.print("Called!");
  steppers.moveTo(steps);
  steppers.runSpeedToPosition();  
}

This works fine but it seems to adjust its speed automatically depending on how far each stepper has to go, I would rather set them manually, is there a way to do that?

Bobcousins
Also, I wonder what happens if you call runSpeed when distanceToGo() is zero, does it go negative?

No, it just does not move

How far do they have to go?

Multistepper adjusts the speeds so that the multiple motions start and finish at the same times. If they are different distances, they will have to move at different speeds to reach their targets at the same time.

If they all must travel the same speed, and have different distances to travel, they should take different times to finish, and multiStepper isn't the right tool.

Maybe instead of runSpeed() you want stepper.runSpeedToPosition() because it stops when it reaches a target.

Try this simulation:

// https://wokwi.com/projects/426553084377452545
// for https://forum.arduino.cc/t/having-trouble-running-multiple-steppers-at-constant-speed-using-accelstepper/1367416
// based on:
// https://wokwi.com/projects/410062239608936449
// Code from https://github.com/waspinator/AccelStepper/blob/master/examples/MultipleSteppers/MultipleSteppers.pde
// Other example simulations https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754
//

// MultiStepper.pde
// -*- mode: C++ -*-
//
// Shows how to multiple simultaneous steppers
// Runs one stepper forwards and backwards, accelerating and decelerating
// at the limits. Runs other steppers at the same time
//
// Copyright (C) 2009 Mike McCauley
// $Id: MultiStepper.pde,v 1.1 2011/01/05 01:51:01 mikem Exp mikem $

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1; // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5
AccelStepper stepper2(AccelStepper::FULL4WIRE, 6, 7, 8, 9);
AccelStepper stepper3(AccelStepper::FULL2WIRE, 10, 11);

float Speed = 200;


void setup()
{
  stepper1.setMaxSpeed(Speed);
  stepper1.setAcceleration(100.0);
  stepper1.moveTo(Speed);

  stepper2.setMaxSpeed(Speed);
  stepper2.setAcceleration(100.0);
  stepper2.moveTo(2*Speed);

  stepper3.setMaxSpeed(Speed);
  stepper3.setAcceleration(100.0);
  stepper3.moveTo(3*Speed);

    stepper1.setSpeed(Speed);
    stepper2.setSpeed(Speed);
    stepper3.setSpeed(Speed);

}

void loop()
{
  // Change direction at the limits
  if (stepper3.distanceToGo() == 0) {
    stepper1.moveTo(-stepper1.currentPosition());
    stepper2.moveTo(-stepper2.currentPosition());
    stepper3.moveTo(-stepper3.currentPosition());
    stepper1.setSpeed(Speed);
    stepper2.setSpeed(Speed);
    stepper3.setSpeed(Speed);
    delay(1000);
  }
  stepper1.runSpeedToPosition();
  stepper2.runSpeedToPosition();
  stepper3.runSpeedToPosition();
}

It bounces the three steppers back and forth at constant speed. Since they have different targets, they stop at different times and wait until all three steppers reach their destination before re-targeting.

As I mentioned in #2 AccelStepper has two modes which are easy to confuse. AccelStepper tries to detect internally which mode is in use. If you call moveTo() it thinks you are in position mode, and calling runSpeed() gives incorrect results (wrong speed).

Inside AccelStepper, run() calls runSpeed() to do the stepping; calling runSpeed() directly bypasses the acceleration calculation.

Internally, MultiStepper just calls runSpeed() for each stepper.

distanceToGo() can go negative if you keep calling runSpeed();

So anyway, I tested the following code and I believe it does what you want. Each stepper moves at the programmed speed, and for only the number of steps required. I made an assumption that all the steppers are moving in a positive direction.

#include <AccelStepper.h>

#define HAVE_X
#define HAVE_Y
#define HAVE_Z

// For Arduino Uno + CNC shield V3
#define MOTOR_X_ENABLE_PIN 8
#define MOTOR_X_STEP_PIN 2
#define MOTOR_X_DIR_PIN 5

#define MOTOR_Y_ENABLE_PIN 8
#define MOTOR_Y_STEP_PIN 3
#define MOTOR_Y_DIR_PIN 6

#define MOTOR_Z_ENABLE_PIN 8
#define MOTOR_Z_STEP_PIN 4
#define MOTOR_Z_DIR_PIN 7

#define MOTOR_A_ENABLE_PIN 8
#define MOTOR_A_STEP_PIN 12
#define MOTOR_A_DIR_PIN 13

AccelStepper motorX(AccelStepper::DRIVER, MOTOR_X_STEP_PIN, MOTOR_X_DIR_PIN);
AccelStepper motorY(AccelStepper::DRIVER, MOTOR_Y_STEP_PIN, MOTOR_Y_DIR_PIN);
AccelStepper motorZ(AccelStepper::DRIVER, MOTOR_Z_STEP_PIN, MOTOR_Z_DIR_PIN);

float max_accel = 1e6f;
float max_speed = 1e9f;  // steps per sec

float speed = 500.0f;

void run_steppers(AccelStepper stepper_a1 , AccelStepper stepper_a2, AccelStepper stepper_a3, int steps[3])
{
  Serial.print("Called!");
  //digitalWrite(dir1, LOW);
  //digitalWrite(dir2, LOW);
  //digitalWrite(dir3, LOW);

  stepper_a1.setSpeed(speed);
  stepper_a2.setSpeed(speed);
  stepper_a3.setSpeed(speed);

  Serial.println(steps[0]);
  Serial.println(steps[1]);
  Serial.println(steps[2]);

  int running = 7;
  while (running != 0)
  {
    if (stepper_a1.currentPosition () == steps[0])
      running &= ~1;
    else
      stepper_a1.runSpeed();

    if (stepper_a2.currentPosition () == steps[1])
      running &= ~2;
    else
      stepper_a2.runSpeed();

    if (stepper_a3.currentPosition () == steps[2])
      running &= ~4;
    else
      stepper_a3.runSpeed();
  }

  stepper_a1.setCurrentPosition(0);
  stepper_a2.setCurrentPosition(0);
  stepper_a3.setCurrentPosition(0);
}

void setup() 
{
  Serial.begin(115200);
  Serial.println("CNC shield test");

#ifdef HAVE_X
  pinMode(MOTOR_X_ENABLE_PIN, OUTPUT);
  motorX.setEnablePin(MOTOR_X_ENABLE_PIN);
  motorX.setPinsInverted(false, false, true);
  motorX.setAcceleration(max_accel);
  motorX.setMaxSpeed(max_speed);
  motorX.setSpeed(max_speed);
  motorX.enableOutputs();
#endif

#ifdef HAVE_Y
  pinMode(MOTOR_Y_ENABLE_PIN, OUTPUT);
  motorY.setEnablePin(MOTOR_Y_ENABLE_PIN);
  motorY.setPinsInverted(false, false, true);
  motorY.setAcceleration(max_accel);
  motorY.setMaxSpeed(max_speed);
  motorY.enableOutputs();
#endif

#ifdef HAVE_Z
  pinMode(MOTOR_Z_ENABLE_PIN, OUTPUT);
  motorZ.setEnablePin(MOTOR_Z_ENABLE_PIN);
  motorZ.setPinsInverted(false, false, true);
  motorZ.setAcceleration(max_accel);
  motorZ.setMaxSpeed(max_speed);
  motorZ.enableOutputs();
#endif
}

void loop() 
{
  int steps[3];

  steps[0] = 100;
  steps[1] = 200;
  steps[2] = 300;

  run_steppers (motorX, motorY, motorZ, steps);

  delay (500);
}

Using the AccelStepper::runSpeedToPosition() mode similarly bypasses the speed/acceleration recalculation that is built into run(), but it still wraps the call to run speed with this sort of position checking:

The work of stepper_a1.currentPosition () == steps[0]) in that code is built into the AccelStepper::runSpeedToPosition():

There are five AccelStepper modes, two of which are blocking and three that require repeated calling:

run(); // moves to position while adjusting speed with accel/decel
runSpeedToPosition() ; // moves to position at constant speed, zeroes speed at finish
runSpeed(); // moves at constant speed 
runToPosition(); // blocks while moving to position w/accel/decel
runToNewPosition(position); // blocks while moving to position w/accel/decel

The runSpeedToPosition() mode shouldn't be confused with the similarly named blocking modes like runToPosition() or runToNewPosition(pos), and seems adequate for the OP's use-case.

In order to set targetPosition, you need to call move/moveTo, those compute acceleration and set a speed (_stepInterval ). You at least need to ensure you call setSpeed() after those calls to set the desired speed.

I stand by my assertion that there are essentially two modes : move to position and move at constant speed, and the two should not be mixed. However, the API is quite flexible and allows you to do lots of things, but not always what is expected. So perhaps I should add the caveat ".. unless you have studied the library code and know exactly what you are doing".
Generally new or casual users of the library will not fall under that clause.

I agree with the sentiment tha the modes should not be mixed, and with:

...but, if I was grouping the 5 run???() commands into two modes, I'd set the divide between moving with acceleration or without.

Maybe it is easier to stick-build other modes from runSpeed() and currentPosition() than it is to learn the library.

Hey, sorry for the late response, life got in the way

I did try using runSpeedToPosition, it just seems to adjust the speed on its own, where I actually want to do it manually. Part of the problem with this is that if one of my axis has to move one step and the other has to move 2000 it has a very jerky behavior.

Hey sorry for the late response, life got in the way.

Given what you said, maybe accelstepper isn´t the right tool for the job, part of the reason that I´m using it is that it simplifies some coding for me by not having to worry about direction when using negative steps but the main thing is handling multiple steppers. Is there another library that can handle multiple steppers?

In another sketch I have something like this:

void advance_x_steps_w_y_speed(int steps, int speed, int pul, int dir)
{
  
  for(int x = 0; x < steps; x++)
	{
		digitalWrite(pul, HIGH);
		delayMicroseconds(speed);
		digitalWrite(pul, LOW);
		delayMicroseconds(speed);
	}
}

It has two issues; I have to take track of what's the direction, this is minor it's easy to implement. The second issue is that this function does not work with multiple steppers, I have tried a few things but using an existing library ended up being easier, is there a better way to do this?

I just coded a solution that works as intended, it´s rough, incomplete and needs some work but using micros() I managed to pull it off. I'm posting it here in case someone comes up with a similar problem. If there's a better solution, please let me know.

void advance_x_steps_w_y_speed(int steps1, int speed1, int pulpin1, int direction1, long timer1, int counter1, int steps2, int speed2, int pulpin2, int direction2, long timer2, int counter2)
{
  Serial.print("Called!");
  bool done = false;

  while(done == false)
  {
    unsigned long currentTime = micros();
    if(currentTime-timer1 >= speed1 && counter1 < steps1)
    {
      counter1++;
      digitalWrite(pulpin1, HIGH);
      digitalWrite(pulpin1, LOW);
      //Serial.print("Stepper 1: ");
      //Serial.println(counter1);

      timer1 = currentTime;
    }

    if(currentTime-timer2 >= speed2 && counter2 < steps2)
    {
      counter2++;
      digitalWrite(pulpin2, HIGH);
      digitalWrite(pulpin2, LOW);
      //Serial.print("Stepper 2: ");
      //Serial.println(counter2);
      timer2 = currentTime;
    }

    if(counter1 == steps1 && counter2 == steps2)
    {
      done = true;
      steps1 = 0;
      steps2 = 0;
      counter1 = 0;
      counter2 = 0;
    }
  }
  return;
  }

No, runSpeedToPosition() just wraps the constant runSpeed() in a position check:

It does not adjust the speed.

What do you want to happen in that 2000:1 case that isn't "jerky"?

AccelStepper 200:1:2000 runSpeedToPosition code

To be dropped into this simulation:

Wokwi - Online ESP32, STM32, Arduino Simulator

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1; // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5
AccelStepper stepper2(AccelStepper::FULL4WIRE, 6, 7, 8, 9);
AccelStepper stepper3(AccelStepper::FULL2WIRE, 10, 11);

float Speed = 200;


void setup()
{
  stepper1.setMaxSpeed(Speed);
  stepper1.setAcceleration(100.0);
  stepper1.moveTo(Speed);

  stepper2.setMaxSpeed(Speed);
  stepper2.setAcceleration(100.0);
  stepper2.moveTo(1);

  stepper3.setMaxSpeed(Speed);
  stepper3.setAcceleration(100.0);
  stepper3.moveTo(2000);

    stepper1.setSpeed(Speed);
    stepper2.setSpeed(Speed);
    stepper3.setSpeed(Speed);

}

void loop()
{
  // Change direction at the limits
  if (stepper3.distanceToGo() == 0) {
    stepper1.moveTo(-stepper1.currentPosition());
    stepper2.moveTo(-stepper2.currentPosition());
    stepper3.moveTo(-stepper3.currentPosition());
    // since moveTo() has the side-effect of
    // recalculating the speed and setting it to minimum 
    // we need to manually control the desired speed:
    stepper1.setSpeed(Speed);
    stepper2.setSpeed(Speed);
    stepper3.setSpeed(Speed);
    delay(1000);
  }
  stepper1.runSpeedToPosition();
  stepper2.runSpeedToPosition();
  stepper3.runSpeedToPosition();
}