Proper Use of AccelStepper Library

I am trying to update the speed of two stepper motors in real time using a potentiometer. I have debugged with the serial monitor and am getting the desired speeds to print dozens of times per second. While my motors do change direction in real time according to the value of the potentiometer (postive/negative for CW/CCW), they are only stepping about once per second.

I have set their accelerations to 1000. In my loop, I call setSpeed() for each stepper based on conditions of the potentiometer in. The last statement of my loop is
stepper1.runSpeed();
stepper2.runSpeed();
I have ran other online examples, so I know my steppers are wired correctly and can certainly rotate faster than 1 step per second.

Am I correctly implementing setSpeed() and runSpeed()? As I said, they are responsive to the direction but not the speed.

I think the problem is in your code.

2 Likes

I forgot to mention that I also call

Serial.println(stepper1.speed());
Serial.println(stepper2.speed());

Which update dozens of times per second in the serial monitor with the correct speeds

Check line 42. It's almost always line 42.

Seriously, you're having a problem with your sketch, but you don't show us your sketch? You might want to rethink that.

4 Likes

Lol I figured it would be overwhelming with my lack of comments. I find it funny that my loop() just so happens to begin on line 42. My analog reading is from a joystick.

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1(AccelStepper::FULL2WIRE, 19, 18);  // left stepper
AccelStepper stepper2(AccelStepper::FULL2WIRE, 17, 16);  // right stepper


#define PI 3.14159

//pins
const int x_pin = 36;
const int y_pin = 39;
const int SW = 34;

//vars for analog readings
int x;  //left right
int y;  //up down

//vars for speeds/directions etc
float hypotenuse;

float p1;
float p2;

float q1;
float q2;
float var1;

const int deadzone = 55;

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

  stepper1.setMaxSpeed(512.0);
  stepper1.setAcceleration(1000.0);

  stepper2.setMaxSpeed(512.0);
  stepper2.setAcceleration(1000.0);
}

void loop() {
  x = analogRead(x_pin) - 512;
  y = analogRead(y_pin) - 512;



  hypotenuse = sqrt((x * x) + (y * y));


  if (hypotenuse > deadzone) {

    if (x > 0 && y > 0) {

      if (x > y) {

        p1 = (PI / 4 + atan(y / x)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);
        stepper1.setSpeed(-var1 * p2);

      } else {

        p1 = (PI / 4 + atan(x / y)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);
        stepper1.setSpeed(var1 * p2);
      }
    }

    else if (x < 0 && y > 0) {

      if (y > -x) {

        p1 = (PI / 4 + atan(-x / y)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);
        stepper1.setSpeed(var1 * p2);

      } else {

        p1 = (PI / 4 + atan(y / -x)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);
        stepper1.setSpeed(var1 * p2);
      }
    }

    else if (x < 0 && y < 0) {

      if (-x > -y) { 

        p1 = (PI / 4 + atan(-y / -x)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);
        stepper1.setSpeed(var1 * p2);

      } else {

        p1 = (PI / 4 + atan(-x / -y)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);
        stepper1.setSpeed(-var1 * p2);
      }
    }

    else if (x > 0 && y < 0) {

      if (-y > x) {

        p1 = (PI / 4 + atan(x / -y)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);
        stepper1.setSpeed(var1 * p2);

      } else {

        p1 = (PI / 4 + atan(-y / x)) / (PI / 2);
        p2 = 1 - p1;

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);
        stepper1.setSpeed(-var1 * p2);
      }
    }

    else if (x == 0) {
      if (y > 0) {
        stepper2.setSpeed(-hypotenuse);
        stepper1.setSpeed(hypotenuse);
      } else {
        stepper2.setSpeed(hypotenuse);
        stepper1.setSpeed(-hypotenuse);
      }
    }

    else if (y == 0) {
      if (x > 0) {
        stepper2.setSpeed(hypotenuse);
        stepper1.setSpeed(hypotenuse);
      } else {
        stepper2.setSpeed(-hypotenuse);
        stepper1.setSpeed(-hypotenuse);
      }
    }
  }

  else {
    stepper1.setSpeed(0);
    stepper2.setSpeed(0);
  }
  stepper1.runSpeed();
  stepper2.runSpeed();


//debugging
  Serial.print("LR: ");
  Serial.print(x);
  Serial.print(" UD: ");
  Serial.println(y);
  Serial.println(hypotenuse);
  Serial.println(var1);
  Serial.println(stepper1.speed());
  Serial.println(stepper2.speed());
  //delay(1000);
}

It looks like an awful lot of floating point math per loop(). How often do you actually move your potentiometers? I think you could improve performance significantly if you rate-limited the updating to something matching a human interface time (10Hz?) and perhaps further limited the updating to only happen with changes in the inputs.

Also, Arduino has atan2(y,x):

atan2f()

float atan2f ( float __y,
float __x
)

The atan2f() function computes the principal value of the arc tangent of __y / __x, using the signs of both arguments to determine the quadrant of the return value. The returned value is in the range [-pi, +pi] radians.

https://www.nongnu.org/avr-libc/user-manual/group__avr__math.html#ga9d13156d340f22e6f85a63edf291d751

1 Like

The joystick will be moved almost constantly, so I need constant updating. I am happy with how many times I am getting serial monitor updates of the math (dozens per second). I just don't see how speed() gives the correct speed in the monitor, yet my stepper motors update at a seemingly constant rate of about once per second (in the correct direction).

That looks interesting thanks I will look into it

I see also that you are doing a lot 9600 baud IO per loop. That will also rate-limit your steppers.

Here's a modification to rate limit the joystick reading and debug reporting to 10Hz, while increasing the IO to a more modern 115200baud:

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1(AccelStepper::FULL2WIRE, 19, 18);  // left stepper
AccelStepper stepper2(AccelStepper::FULL2WIRE, 17, 16);  // right stepper


#define PI 3.14159

//pins
const int x_pin = 36;
const int y_pin = 39;
const int SW = 34;

//vars for analog readings
int x;  //left right
int y;  //up down

//vars for speeds/directions etc
float hypotenuse;

float p1;
float p2;

float q1;
float q2;
float var1;

const int deadzone = 55;

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

  stepper1.setMaxSpeed(512.0);
  stepper1.setAcceleration(1000.0);

  stepper2.setMaxSpeed(512.0);
  stepper2.setAcceleration(1000.0);
}

void loop() {
  x = analogRead(x_pin) - 512;
  y = analogRead(y_pin) - 512;

  static unsigned long lastUiMillis = 0;
  unsigned long currentMillis = millis();
  if (currentMillis - lastUiMillis >= 100) {
    lastUiMillis = currentMillis;

    hypotenuse = sqrt((x * x) + (y * y));


    if (hypotenuse > deadzone) {

      if (x > 0 && y > 0) {

        if (x > y) {

          p1 = (PI / 4 + atan(y / x)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(-var1 * p1);
          stepper1.setSpeed(-var1 * p2);

        } else {

          p1 = (PI / 4 + atan(x / y)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(-var1 * p1);
          stepper1.setSpeed(var1 * p2);
        }
      }

      else if (x < 0 && y > 0) {

        if (y > -x) {

          p1 = (PI / 4 + atan(-x / y)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(-var1 * p1);
          stepper1.setSpeed(var1 * p2);

        } else {

          p1 = (PI / 4 + atan(y / -x)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(var1 * p1);
          stepper1.setSpeed(var1 * p2);
        }
      }

      else if (x < 0 && y < 0) {

        if (-x > -y) {

          p1 = (PI / 4 + atan(-y / -x)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(var1 * p1);
          stepper1.setSpeed(var1 * p2);

        } else {

          p1 = (PI / 4 + atan(-x / -y)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(var1 * p1);
          stepper1.setSpeed(-var1 * p2);
        }
      }

      else if (x > 0 && y < 0) {

        if (-y > x) {

          p1 = (PI / 4 + atan(x / -y)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(-var1 * p1);
          stepper1.setSpeed(var1 * p2);

        } else {

          p1 = (PI / 4 + atan(-y / x)) / (PI / 2);
          p2 = 1 - p1;

          q1 = p1 * p1;
          q2 = p2 * p2;

          var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

          stepper2.setSpeed(-var1 * p1);
          stepper1.setSpeed(-var1 * p2);
        }
      }

      else if (x == 0) {
        if (y > 0) {
          stepper2.setSpeed(-hypotenuse);
          stepper1.setSpeed(hypotenuse);
        } else {
          stepper2.setSpeed(hypotenuse);
          stepper1.setSpeed(-hypotenuse);
        }
      }

      else if (y == 0) {
        if (x > 0) {
          stepper2.setSpeed(hypotenuse);
          stepper1.setSpeed(hypotenuse);
        } else {
          stepper2.setSpeed(-hypotenuse);
          stepper1.setSpeed(-hypotenuse);
        }
      }
    }

    else {
      stepper1.setSpeed(0);
      stepper2.setSpeed(0);
    }
    //debugging
    Serial.print("LR: ");
    Serial.print(x);
    Serial.print(" UD: ");
    Serial.println(y);
    Serial.println(hypotenuse);
    Serial.println(var1);
    Serial.println(stepper1.speed());
    Serial.println(stepper2.speed());
  }
  stepper1.runSpeed();
  stepper2.runSpeed();


  //delay(1000);
}

With these two changes, the runspeed()s can potentially happen much faster than dozens of Hz, decoupling the stepping from the IO and allowing faster speeds.

1 Like

What device are you using such that 36 and 39 are analog pins?

esp32 devkit

Thanks.

Here's a snippet of some code to do change detection on the pot inputs.

    static int lastX, lastY;
    const int slop=2;
    if (abs(lastX - x) > slop || abs(lastY -  y) > slop) { // change detect x,y
      lastX = x;
      lastY = y;
      ...

    }

That change-detection code could limit the recalculations further and potentially allow for faster stepping speeds.

1 Like

I would be concerned about the effect of calling stepperX.setSpeed(...) umpteen times per second, and whether each call was restarting/resetting some process/calculations/variables in the library over and over and over again.

1 Like

I think I understand, but I don't think it would work with my low quality joystick as the values change constantly even without movement. I do plan on upgrading though

This was my initial fear. Perhaps acceleration is restarting at zero with every call of setSpeed()? So the motor is always restarting its acceleration from zero or something like that

Hence "slop":

2 Likes

Acceleration doesn't happen at all with runSpeed():

◆ runSpeed()

boolean AccelStepper::runSpeed ( )

Poll the motor and step it if a step is due, implementing a constant speed as set by the most recent call to setSpeed(). You must call this as frequently as possible, but at least once per step interval,

-- AccelStepper: AccelStepper Class Reference

Since x &y are ints, something flaky could happen with integer math. When I tried this code on an uno I got some NaNs.

1 Like

I am 99% confident this is due to my misunderstanding of how the AccelStepper library works... particularly the .setSpeed() and .runSpeed() functions. Why?

  1. the motors spin as expected with various online example codes... both directions, fast/slow... so they are wired correctly.
  2. in the end of my loop(), I print the stepper1.speed(), stepper2.speed(), and these give my desired RPM in the correct directions... so it is not my conditional statement logic.
  3. the serial log updates dozens of times per second (enough for my purposes), so it is not a matter of processor speed/expensive calculations.

When I power my circuit, the motors make an idle running noise and take just a single step every few seconds or so (not often) in seemingly random intervals.

My only guess would be that I am not giving the steppers enough time to accelerate, so with every update, they start with a velocity of zero (dozens of times per second, briefly accelerating to one step before restarting a fraction of a second later). Would millis() work around this? Here is my code (I call runSpeed() at the very end):

#include <AccelStepper.h>

// Define some steppers and the pins the will use
AccelStepper stepper1(AccelStepper::FULL2WIRE, 19, 18);  // left stepper
AccelStepper stepper2(AccelStepper::FULL2WIRE, 17, 16);  // right stepper


#define PI 3.14159

//pins
const int x_pin = 34;  //should be 36??????
const int y_pin = 35;  //should be 39??????
//const int SW = 34;

//vars for analog readings
float x = 0;  //left right
float y = 0;  //up down

//vars for speeds/directions etc
float hypotenuse;

float p1;
float p2;

float q1;
float q2;
float var1;

const int deadzone = 11;

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

  stepper1.setMaxSpeed(1024.0);
  stepper1.setAcceleration(1000.0);

  stepper2.setMaxSpeed(1024.0);
  stepper2.setAcceleration(1000.0);

    // Set ADC resolution to 10 bits

  analogReadResolution(10);
}

void loop() {
  x = -(analogRead(x_pin) - 473);  // xpin reads 0 to 1023 where 512 is middle (stationary)
  y = (analogRead(y_pin) - 473);  // ypin reads 0 to 1023 where 512 is middle (stationary)

  hypotenuse = sqrt((x * x) + (y * y));


  if (hypotenuse > deadzone) {

    if (x > 0 && y > 0) {  // first quadrant

      if (x > y) {

        p1 = (PI / 4 + atan(y / x)) / (PI / 2);  // % for RIGHT motor
        p2 = 1 - p1;                             // % for LEFT motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);  //negative for counterclockwise
        stepper1.setSpeed(-var1 * p2);  //negative for counterclockwise

      } else {

        p1 = (PI / 4 + atan(x / y)) / (PI / 2);  // % for RIGHT motor
        p2 = 1 - p1;                             // % for LEFT motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);  //negative for counterclockwise
        stepper1.setSpeed(var1 * p2);   //positive for clockwise
      }
    }

    else if (x < 0 && y > 0) {  // second quadrant

      if (y > -x) {

        p1 = (PI / 4 + atan(-x / y)) / (PI / 2);  // % for right motor
        p2 = 1 - p1;                              // % for left motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);  //negative for counterclockwise
        stepper1.setSpeed(var1 * p2);   //positive for clockwise

      } else {

        p1 = (PI / 4 + atan(y / -x)) / (PI / 2);  // % for RIGHT motor
        p2 = 1 - p1;                              // % for LEFT motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);  //positive for clockwise
        stepper1.setSpeed(var1 * p2);  //positive for clockwise
      }
    }

    else if (x < 0 && y < 0) {  // third quadrant

      if (-x > -y) {

        p1 = (PI / 4 + atan(-y / -x)) / (PI / 2);  // % for RIGHT motor
        p2 = 1 - p1;                               // % for LEFT motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);  //positive for clockwise
        stepper1.setSpeed(var1 * p2);  //positive for clockwise

      } else {

        p1 = (PI / 4 + atan(-x / -y)) / (PI / 2);  // % for right motor
        p2 = 1 - p1;                               // % for left motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(var1 * p1);   //positive for clockwise
        stepper1.setSpeed(-var1 * p2);  //negative for counterclockwise
      }
    }

    else if (x > 0 && y < 0) {  // forth quadrant

      if (-y > x) {

        p1 = (PI / 4 + atan(x / -y)) / (PI / 2);  // % for right motor
        p2 = 1 - p1;                              // % for left motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);  //negative for counterclockwise
        stepper1.setSpeed(var1 * p2);   //positive for clockwise

      } else {

        p1 = (PI / 4 + atan(-y / x)) / (PI / 2);  // % for RIGHT motor
        p2 = 1 - p1;                              // % for LEFT motor

        q1 = p1 * p1;
        q2 = p2 * p2;

        var1 = sqrt(hypotenuse * hypotenuse / (q1 + q2));

        stepper2.setSpeed(-var1 * p1);  //negative for counterclockwise
        stepper1.setSpeed(-var1 * p2);  //negative for counterclockwise
      }
    }

    else if (x == 0) {
      if (y > 0) {
        stepper2.setSpeed(-hypotenuse);
        stepper1.setSpeed(hypotenuse);
      } else {
        stepper2.setSpeed(hypotenuse);
        stepper1.setSpeed(-hypotenuse);
      }
    }

    else if (y == 0) {
      if (x > 0) {
        stepper2.setSpeed(hypotenuse);
        stepper1.setSpeed(hypotenuse);
      } else {
        stepper2.setSpeed(-hypotenuse);
        stepper1.setSpeed(-hypotenuse);
      }
    }
  }

  else {  // joystick in "deadzone" (no movement)
    stepper1.setSpeed(0);
    stepper2.setSpeed(0);
  }
  stepper1.runSpeed();
  stepper2.runSpeed();

  Serial.print("LR: ");
  Serial.print(x);
  Serial.print(" UD: ");
  Serial.println(y);
  Serial.println(hypotenuse);
  Serial.println(var1);
  Serial.print("Left motor speed: ");
  Serial.println(stepper1.speed());
  Serial.print("Right motor speed: ");
  Serial.println(stepper2.speed());
}

AccelStepper is primarily designed to be used in a positional mode; ie. request to move a number of steps forward or reverse.

Continuous rotation mode is kind of a bonus mode, if you just want to run the stepper continuously (although the code for that is somewhat trivial and doesn't need a library).

Using setspeed() and creating your own acceleration profile is probably well outside the range of expected usage. I haven't checked but I guess every time you call setspeed() it calculates the time to the next step and restarts a timer. If you repeatedly call setspeed(), it will never (or rarely) get to the point where it does a step.

Perhaps if you call setpeed() less frequently, e.g. 10 times a second, you might get better results. Do you even need to use AccelStepper anyway?

1 Like