Using AccelStepper to execute premade set of movements

Currently running two NEMA17 steppers off of generic A4988 drivers that are driving a small wheeled vehicle. To move the vehicle I've created 4 functions: Forward(), Backward(), Left(), and Right(), which include a setMaxSpeed(), setAcceleration(), and move() for both motors (code is pasted below). I have a very incomplete understanding of how AccelStepper actually calls these values when run() is going, but it seems to me that there should be a way to execute these functions in a specific order without a whole mess of if statements or switch cases, as I need the vehicle to be fairly flexible in terms of what set of motions it does.

#include <AccelStepper.h>

const int dirPin1 = 6;
const int stepPin1 = 9;

const int dirPin2 = 2;
const int stepPin2 = 3;

AccelStepper StepperR(AccelStepper::DRIVER, stepPin1, dirPin1);
AccelStepper StepperL(AccelStepper::DRIVER, stepPin2, dirPin2);

int stepsPerRev = 400;  //I'm using a 0.9deg motor              

void Forward(float forward) { 
  
  StepperR.setMaxSpeed(-200.0);
  StepperR.setAcceleration(20.0);

  StepperL.setMaxSpeed(200.0);
  StepperL.setAcceleration(20.0);

  StepperR.move(-forward*stepsPerRev);
  StepperL.move(forward*stepsPerRev);
}

void Backward(float backward) {
    
  StepperR.setMaxSpeed(200.0);
  StepperR.setAcceleration(20.0);

  StepperL.setMaxSpeed(-200.0);
  StepperL.setAcceleration(20.0);

  StepperR.move(backward*stepsPerRev);
  StepperL.move(-backward*stepsPerRev);
}

void Right(int right) {
    
  StepperR.setMaxSpeed(200.0);
  StepperR.setAcceleration(20.0);

  StepperL.setMaxSpeed(200.0);
  StepperL.setAcceleration(20.0);

  StepperR.move(right*stepsPerRev);
  StepperL.move(right*stepsPerRev);
}

void Left(int left) {
    
  StepperR.setMaxSpeed(-200.0);
  StepperR.setAcceleration(20.0);

  StepperL.setMaxSpeed(-200.0);
  StepperL.setAcceleration(20.0);

  StepperR.move(-left*stepsPerRev);
  StepperL.move(-left*stepsPerRev);
}

void setup() {
}

void loop(){
  Left(1);
  StepperL.run();
  StepperR.run();
  delay(100);
  Forward(1);
  StepperL.run();
  StepperR.run();
  delay(100);
}

I've read that run() doesn't play well with delays and that it should be called frequently, but I'm fairly certain this isn't what is meant by that. Using just one of the functions and then run() for both motors works just fine, but as soon as I try to add another, the motors get quite upset.

Run only does one step when the step is due based on acceleration and speed requirements
So you need to indeed call it often to see the motor take a few steps

When you have only one call it works because the loop() loops and you keep calling run()

The way you wrote that won’t work as you are alternating between the two commands

May be you need too check runToPosition() which is blocking until it arrives there

https://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html#a344f58fef8cc34ac5aa75ba4b665d21c

The AccelStepper does not use interrupts. The function .run() decides if a step needs to be done and then makes that step. You have to call the .run() functions as often as possible. Preferably hundreds of times per second (at least).

Your loop() should have this:

void loop()
{
  StepperL.run();
  StepperR.run();
}

Then once in a while, you can give a command to go to a certain position or make a number of steps.
Never stop the .run() functions, they should always be called as often as possible, even if no stepper motor is moving.

To keep the loop() running very fast, you can use a timer with millis() to give a command once in a while.

Can you give a list of commands as an example ?
For example:

Forward 2 wheel rotations.
Wait 10 seconds.
Right 1 wheel rotation.
Forward 20 wheel rotations.

Then you can put that in an array, and use a timer with millis() to go through the array and call the commands.

As an alternative, you can use .isRunning() to go to the next command in the list.

Instead of using an array, you can also use a Finite State Machine: https://majenko.co.uk/blog/finite-state-machine

:point_right: My suggestion: Use .runToPosition() that J-M-L wrote about. That is the easiest. If that works and you want more, then you try what I wrote about in this post.

[EDIT] I have changed this post, after I thought about it :brain:

That would certainly be the simplest option, but (definitely correct me if I'm wrong) I believe runToPosition uses the same type of absolute coordinates that moveTo() does, which would make programming multiple movements in a row quite difficult, especially if I want to change the order.

It would look very similar to what you wrote, as an example:

Forward(1);
Left(2);
Forward(1);

Should move the wheels forward one rotation, turn the vehicle 180 degrees, and return to the starting position. That is assuming you already defined the functions as in first program I posted.

Would the use of an array require feedback to ensure the motor has completed each command, or would millis() just be used to ensure there was some delay in between each command? When I try to place the movement commands outside void loop(), I get a compiling error, do I need to have separate loops executing each movement and executing run() as often as possible?

If you want to do multiple movements in a row use a variable to remember at what stage you are

In the loop you call run()
Then check if you’ve reached the target position and if so set the new target for the next stage and loop over

A very simple state machine, only event is you arrived to destination. Here is a small introduction to the topic: Yet another Finite State Machine introduction

Can you write code that makes those moves ? If it compiles and the motors run smooth, then you are one step further (pun intended).

I hope that someone else has an easier solution, because this is over the top:

#define STOP 1
#define LEFT 2
#define RIGHT 3
#define FORWARD 4
...

int command[][2] =
{
  { FORWARD, 1 },
  { LEFT, 2 },
  { FORWARD, 1},
  { BLINK_LED, 5},
  { WAIT, 10},
  { BUZZER, 1},
  { _END_OF_LIST_, 0},
}

After looking at some resources about state machines and running multiple processes concurrently, I think I've got an idea how to do this by defining an array, advancing a counter for each element of the array, and using if statements for each possible move (forward, backward, left, right).

That’s a possibility

I've gotten significantly farther than expected: currently using a switch case to execute each movement based on an input array. However, I've run into an error with the way I generate a new position for the motor with moveTo every time a new case is run. My current code is pasted below, and I think the issue is that desiredPosition is continuously calling for each motor's currentPosition() and adding to it, so every time the if statement in a given case loops, it just increases (or decreases) desiredPosition ad infinitum. Any guidance on how I can get around this issue would be amazing, as I'm certain there's a better way to tell the motor how it should move, I just haven't been able to figure it out.

#include <AccelStepper.h>

AccelStepper StepperR(AccelStepper::DRIVER, 9, 6);
AccelStepper StepperL(AccelStepper::DRIVER, 3, 2);

const int halfTime = 10000;
const int fTime = 10000;
const int bTime = 10000;
const int rTime = 10000;
const int lTime = 10000;

int currentAction = 0;
unsigned long startTime;
bool actionComplete = false;
unsigned long lastPrintTime = 0;
const unsigned long printInterval = 1000;

const int numberOfMoveTos = 7;
int test[numberOfMoveTos] = {1,1,1,1,1,1,1};

int stepsPerRev = 400;
float revPerSq = (50/2*PI*3.81);

void setup() {

  Serial.begin(115200);

  StepperR.setMaxSpeed(200);
  StepperL.setMaxSpeed(200);

  StepperR.setAcceleration(10);
  StepperL.setAcceleration(10);

  startTime = millis();
  lastPrintTime = millis();

  pinMode(13, OUTPUT);
}

void loop() {
  unsigned long currentTime = millis();
  unsigned long elapsedTime = currentTime - startTime;

  switch (test[currentAction]) 
  {  
    case 1:
      {
        if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() + (stepsPerRev*revPerSq));
          long desiredPositionL = (StepperL.currentPosition() - (stepsPerRev*revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= fTime) 
        {
          Serial.println("Forward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }
    case 2:
      {
        if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (stepsPerRev*revPerSq));
          long desiredPositionL = (StepperL.currentPosition() + (stepsPerRev*revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= bTime) 
        {
          Serial.println("Backward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 3:
      {
        if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() + (stepsPerRev));
          long desiredPositionL = (StepperL.currentPosition() + (stepsPerRev));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= lTime) 
        {
          Serial.println("Left");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 4:
      {
        if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (stepsPerRev));
          long desiredPositionL = (StepperL.currentPosition() - (stepsPerRev));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= rTime) 
        {
          Serial.println("Right");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 5:
      {
        if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (0.5*stepsPerRev*revPerSq));
          long desiredPositionL = (StepperL.currentPosition() + (0.5*stepsPerRev*revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= halfTime) 
        {
          Serial.println("halfForward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    default:
      {
        digitalWrite(13, HIGH); //indicates an error (or the end of the array?)
        StepperR.stop();
        StepperL.stop();
        delay(10000);
        break;
      }
  }

  StepperR.run();
  StepperL.run();
}

Note: revPerSq is determining the number of wheel revolutions (my wheels are 3" dia) required to traverse a distance of 50cm.

We use a Finite State Machine in a different way.

enum
{
  FORWARD,
  LEFT,
} state;

void loop()
{
  switch(state)
  {
    case FORWARD:
      ...
     break;
  }

If you look at my millis_and_finite_state_machine.ino example, then the "STATE_ON_BEFORE_BUTTON" set the previousMillis value, which is used in the next state for a timeout.

With a (simpler) Finite State Machine, there would be no need for switch (test[currentAction]) or if (!actionComplete). If the state is completed, then set the state to the next state (or any other state) and next time it will execute the next state.

To set the variables for the next state, you can do that in the current state, or create a special state for that.

I have made a Wokwi simulation for your project:

You are setting "actionComplete" to true immediately after you give the command to start the move.
Also there are 2 actions(steppers) and you need to know that they have both completed their moves.

The "isRunning" function can tell you if the action has been completed.


  StepperR.run();
  StepperL.run();
  
  if(StepperR.isRunning() == false && StepperL.isRunning() == false) {
    actionComplete = true;
  }

I'm sure my current implementation could be simplified considerably, but I am trying to execute the states in any possible order using the input array. Is there a simpler way to accomplish that? Thank you for the simulation as well, very helpful for debugging, although I'm now even more unsure where the error I'm seeing is coming from, as even removing the currentPosition() call to define desiredPosition() (example below) still just results in the motors spinning as fast as they can forward.

if (!actionComplete) 
        {
          startTime = millis();
          long desiredPositionR = (stepsPerRev*revPerSq);
          long desiredPositionL = (-stepsPerRev*revPerSq);
          StepperR.move(desiredPositionR);
          StepperL.move(desiredPositionL);
        }

Didn't seem to change anything when I added the code snippet you included. I think that if loop only needs to run once to set moveTo for both motors, because the two run()'s in the main loop will take that value and hold onto it.

What do you want it to do instead?

The if(!actionComplete){..} part is a one-shot that only happens when the state is uninitialized. I'd think of it as 'if(!initialized)'

You could test your hypothesis about things being re-initialized and re-targeted by adding a 'Serial.print("f"); inside the if() clause to see how many times you re-target the steppers.

You could simplify calculating the desiredPosition by using move() like:

 StepperR.move(stepsPerRev*revPerSq);
 StepperL.move(-stepsPerRev*revPerSq);

I think maybe this calculation is wrong and you are computing too far a move to complete in the time allotted:

Do you mean this?

float revPerSq = 50/(2*PI*3.81);

(This new rev calculation worked for me in @koppel's sim, but I increased the accelleration to complete the moves in the alloted time.)

Sample Code tested in Koppel's Wokwi
// Forum: https://forum.arduino.cc/t/using-accelstepper-to-execute-premade-set-of-movements/1248919
// Sketch by r3dex3
// At this moment, this is the sketch of post #10 from the forum.
// This Wokwi project: https://wokwi.com/projects/395587471345920001
//

#include <AccelStepper.h>

AccelStepper StepperR(AccelStepper::DRIVER, 9, 6);
AccelStepper StepperL(AccelStepper::DRIVER, 3, 2);

const int halfTime = 10000;
const int fTime = 10000;
const int bTime = 10000;
const int rTime = 10000;
const int lTime = 10000;

int currentAction = 0;
unsigned long startTime;
bool actionComplete = false;
unsigned long lastPrintTime = 0;
const unsigned long printInterval = 1000;

const int numberOfMoveTos = 7;
int test[numberOfMoveTos] = {1, 1, 1, 1, 1, 1, 1};

int stepsPerRev = 400;
float revPerSq = 50 / (2 * PI * 3.81);

void setup() {

  Serial.begin(115200);

  StepperR.setMaxSpeed(200);
  StepperL.setMaxSpeed(200);

  StepperR.setAcceleration(100);
  StepperL.setAcceleration(100);

  startTime = millis();
  lastPrintTime = millis();

  pinMode(13, OUTPUT);
}

void loop() {
  unsigned long currentTime = millis();
  unsigned long elapsedTime = currentTime - startTime;

  switch (test[currentAction])
  {
    case 1:
      {
        if (!actionComplete)
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() + (stepsPerRev * revPerSq));
          long desiredPositionL = (StepperL.currentPosition() - (stepsPerRev * revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
          Serial.print('f');
        }
        else if (currentTime - startTime >= fTime)
        {
          Serial.println("Forward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }
    case 2:
      {
        if (!actionComplete)
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (stepsPerRev * revPerSq));
          long desiredPositionL = (StepperL.currentPosition() + (stepsPerRev * revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
          Serial.print('b');
        }
        else if (currentTime - startTime >= bTime)
        {
          Serial.println("Backward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 3:
      {
        if (!actionComplete)
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() + (stepsPerRev));
          long desiredPositionL = (StepperL.currentPosition() + (stepsPerRev));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= lTime)
        {
          Serial.println("Left");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 4:
      {
        if (!actionComplete)
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (stepsPerRev));
          long desiredPositionL = (StepperL.currentPosition() - (stepsPerRev));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= rTime)
        {
          Serial.println("Right");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    case 5:
      {
        if (!actionComplete)
        {
          startTime = millis();
          long desiredPositionR = (StepperR.currentPosition() - (0.5 * stepsPerRev * revPerSq));
          long desiredPositionL = (StepperL.currentPosition() + (0.5 * stepsPerRev * revPerSq));
          StepperR.moveTo(desiredPositionR);
          StepperL.moveTo(desiredPositionL);
          actionComplete = true;
        }
        else if (currentTime - startTime >= halfTime)
        {
          Serial.println("halfForward");
          actionComplete = false;
          currentAction++;
        }
        break;
      }

    default:
      {
        digitalWrite(13, HIGH); //indicates an error (or the end of the array?)
        StepperR.stop();
        StepperL.stop();
        delay(10000);
        Serial.print("stopped");
        break;
      }
  }

  StepperR.run();
  StepperL.run();
  report();
}

void report(void) {
  static unsigned long last = 0;
  auto now = millis();
  if (now - last < 500) return;
  last = now;
  Serial.print(currentAction);
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.