AccelStepper and two buttons simplest code

Hi everyone,

First of all, I know that a lot of similar subjects exist but after a lot of searching, I haven't found a really useful post.

I'm trying to use the AccelStepper library to raise and lower a heavy linear rail carriage using 2 push buttons. As long as a button is pressed, the motor accelerates and then rotates in one direction. When the button is released, the motor slows down and stops. Same for the other direction. Enable pin for the driver only when the motor needs to run. (two NC endstops in series with the corresponding buttons for safety)

The problem is that I can't get reliable movements (or movements at all). Either my code gets stuck in a loop (while loop), or the driver just doesn't manage to accelerate the motor. I'm pretty sure i'm using the library wrong... Even though this code must be the easiest thing in the world to write!

Disclaimer : motor and driver work just fine with other test codes, speeds seem high because of microstepping and gearbox. I have the right setup for the driver and enough current for it to work properly. Buttons and endstops are connected with small capacitors for bounces (+ internal pullup). (arduino nano, nema 23 + gearbox, tb6600 driver, 12V-5A power supply)

Anyway, here are my 2 main codes with 2 different approaches :
CODE 1

#include <AccelStepper.h>

#define dirPin 8
#define stepPin 9
#define enablePin 10

// Define button pins
#define buttonPinUP 2
#define buttonPinDOWN 3
// Endstops in series with corresponding button

AccelStepper myStepper(AccelStepper::DRIVER, stepPin, dirPin);

// Define variables for button state and motor direction control
// /!\ false = triggered  !!!!!!
bool buttonUpState = true;
bool buttonDownState = true;

int speed = 1500;
int maxSpeed = 2000;
int acceleration = 500;

void setup() {
  
  myStepper.setPinsInverted(false, false, true); // setInverted can be used here if pins are inverted
  myStepper.setEnablePin(enablePin);
  myStepper.setMinPulseWidth(20);                // seems like tb6600 works better with this
  myStepper.setMaxSpeed(maxSpeed);
  myStepper.setAcceleration(acceleration);

  //pinMode(enablePin, OUTPUT);
  myStepper.disableOutputs();
  delay(200);
  myStepper.enableOutputs();

  pinMode(buttonPinUP, INPUT_PULLUP);
  pinMode(buttonPinDOWN, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  delay(200);
}

void loop() {
  buttonUpState = digitalRead(buttonPinUP);
  buttonDownState = digitalRead(buttonPinDOWN);

  if (buttonUpState == false || buttonDownState == false){
    myStepper.enableOutputs();
    digitalWrite(ledPin, HIGH);
  }

  else if(!myStepper.run()) {
    myStepper.disableOutputs();
    digitalWrite(ledPin, LOW);
  }

  while(buttonUpState == false){
    myStepper.setSpeed(-speed);
    myStepper.run();
  }

  while(buttonDownState == false){
    myStepper.setSpeed(speed);
    myStepper.run();
  }

  myStepper.stop();
}

*Works sometimes but I have no acceleration because of setSpeed() and if I test the outputs, I'm often stuck in the while instruction.

CODE 2

#include <AccelStepper.h>

#define dirPin 8
#define stepPin 9
#define enablePin 10

// Define button pins
#define buttonPinUP 2
#define buttonPinDOWN 3
// Endstops in series with corresponding button

AccelStepper myStepper(AccelStepper::DRIVER, stepPin, dirPin);

// Define variables for button state and motor direction control
// /!\ false = triggered  !!!!!!
bool buttonUpState = true;
bool buttonDownState = true;

//int speed = 1500;
int maxSpeed = 2000;
int acceleration = 500;

void setup() {
  
  myStepper.setPinsInverted(false, false, true); // setInverted can be used here if pins are inverted
  myStepper.setEnablePin(enablePin);
  myStepper.setMinPulseWidth(20);                // seems like tb6600 works better with this
  myStepper.setMaxSpeed(maxSpeed);
  myStepper.setAcceleration(acceleration);

  //pinMode(enablePin, OUTPUT);
  myStepper.disableOutputs();
  delay(200);
  myStepper.enableOutputs();

  pinMode(buttonPinUP, INPUT_PULLUP);
  pinMode(buttonPinDOWN, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  delay(200);
}

void loop() {
  buttonUpState = digitalRead(buttonPinUP);
  buttonDownState = digitalRead(buttonPinDOWN);

  /*if (buttonUpState == false || buttonDownState == false){
    myStepper.enableOutputs();
    digitalWrite(ledPin, HIGH);
  }

  else if(!myStepper.run() && ledPin == HIGH) {
    myStepper.disableOutputs();
    digitalWrite(ledPin, LOW);
  }*/

  if (! buttonUpState){
    myStepper.move(-10);
    //myStepper.run();
    digitalWrite(ledPin, HIGH);
  }

  if (! buttonDownState){
    myStepper.moveTo(10);
    //myStepper.run();
    digitalWrite(ledPin, HIGH);
  }

  else{
    myStepper.stop();
    digitalWrite(ledPin, LOW);
  }

  myStepper.run();
}

Doesn't work at all even if it looks dead easy. Should get the acceleration and maxSpeed right.

I'd be eternally grateful if you could tell me where I'm going wrong, or if you have a better approach to keep this code as simple and reliable as possible. Thanks in advance.

the accelStepper library drives the acceleration based on the number of steps in a movement.

if you ask the library to move 1000 steps Clockwise and provide some max speed and acceleration value, the library can calculate the acceleration phase and also decide at what steps it needs to start decelerating to arrive at destination with a close to null speed.

In your use case scenario, the number of steps is unknown upfront as you don't know when the user will release de button.

For the acceleration part, you could provide a very far target and the ramp up will happen but when you release the button you need to define what you want to happen - how long (how many steps) should be the slow down phase based on the current speed. You could ask the library what is the current position and speed and set a new target position ( moveTo()) which will recalculate the speed for the next step.

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

I'd suggest you use one of the numerous button library for the buttons, such as Button in easyRun or OneButton or Toggle or EasyButton or Bounce2, ... (Toggle would be "easy")

here is an example - you'll have to play a bit with the slowing down part so that it feels "natural"

click to see the code
#include <AccelStepper.h>
#include <Toggle.h>

#define dirPin 8
#define stepPin 9

// Define button pins
#define buttonPinUP 2
#define buttonPinDOWN 3

AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);
Toggle buttonUp, buttonDown;

enum {STOPPED, GOING_UP, GOING_DOWN, SLOWING} state = STOPPED;

const float maxSpeed = 400;
const float acceleration = 25;
const long veryFar = 1000000;
const long slowingRampFactor = 2; //  x speed steps to slow down

void manageButtons() {
  switch (state) {
    case STOPPED:
      buttonUp.poll();
      if (buttonUp.onPress()) {
        Serial.println("Going Up");
        stepper.move(veryFar);
        state = GOING_UP;
      } else {
        buttonDown.poll();
        if (buttonDown.onPress()) {
          Serial.println("Going Down");
          stepper.move(-veryFar);
          state = GOING_DOWN;
        }
      }
      break;

    case GOING_UP:
      buttonUp.poll();
      if (buttonUp.onRelease()) {
        long stepsToSlowDown = slowingRampFactor * stepper.speed();
        Serial.println("Up and Slowing down");
        stepper.move(stepsToSlowDown);
        state = SLOWING;
      }
      break;

    case GOING_DOWN:
      buttonDown.poll();
      if (buttonDown.onRelease()) {
        long stepsToSlowDown = slowingRampFactor * stepper.speed();
        Serial.println("Down and Slowing down");
        stepper.move(stepsToSlowDown);
        state = SLOWING;
      }
      break;

    case SLOWING:
      if (stepper.distanceToGo() == 0) {
        stepper.setCurrentPosition(0);
        state = STOPPED;
        break;
      }
  }
}

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

  stepper.setMaxSpeed(maxSpeed);
  stepper.setAcceleration(acceleration);
  stepper.setCurrentPosition(0);

  buttonUp.begin(buttonPinUP);
  buttonDown.begin(buttonPinDOWN);
  Serial.println("Ready");
}

void loop() {
  manageButtons();
  stepper.run();
}

Thanks for your reply,

Yes, I can (should) incorporate a higher value for the target so the algorithm calculates longer acceleration ramps. But the stop target must be calculated by the library (speed and acceleration of the setup). The number of steps take depends of these two values. This is why I implemented the "moveTo" and "stop" functions. This last function immediately slows down the motor to a stop, independent of any previous function.

The toggle is not an option. I kinda need fast reaction from the user and the programm when it detects that the button is released. Plus, I have two endstops in series with the appropriate buttonfor safety. These have some space for the carriage to runout a bit. If one is activated, I need to stop (decelerate and stop) and be capable to turn in the other direction.

thanks for showing me this little simulation, I think it will be helpfull for debugging further.

In your code you check (badly, don't use boolean operators, compare with == LOW) if the button IS in a pressed state rather than capturing the state change (button was just pressed) and you don't take into account the bouncing.

your arduino is much faster than the user... unless you block the loop, polling the button to maintain its state will be perceived as real time by the user. Check my wokwi, I used the Toggle library. You won't see any of a difference with your direct read digitalRead(buttonPinUP) and the library manages the bouncing for you and performs edge detection (the moment when the button changes state)


so when you release the button, you are at a certain speed V (step . s-1) and it might or might not be the maximum speed.

you have a deceleration of A (step . s-2)

To determine the number of steps traveled before stopping, you can use the kinematic equation

Vf2 = Vi2 - 2as

where:

  • Vf is the final velocity (0 m/s as you come to a stop),
  • Vi is the initial velocity (you can ask the library what's the current speed when the button is released),
  • a is the acceleration (braking acceleration but positive number )
  • s is the number of steps traveled.

By substituting the values and isolating s you get s = Vi2 / 2a

➜ you could set a new target position from the current one with that number of steps.

That is exactly what myStepper.stop() does.

1 Like

Did not know that thx

  • then it’s what to be used !

IT WORKS !!!!

Sorry for the late response, busy weekend...

I implemented the Toggle library and gave it a try with the onPress and onRelease functions like you suggested and it worked just fine.
By the way, for the bouncing, I have some tiny capacitors on the hardware side that should do the job.

I thought you meant a real "toggle" as user input (press once get motor started, press once more to stop it). But it makes more sense when understanding the library.

This is my code now :

#include <AccelStepper.h>
#include <Toggle.h>

#define dirPin 6
#define stepPin 7
#define enablePin 8

// Define button pins
// Endstops in series with corresponding button
Toggle btnUP(2);
Toggle btnDOWN(3);

AccelStepper myStepper(AccelStepper::DRIVER, stepPin, dirPin);

int maxSpeed = 2000;
int acceleration = 2000;

bool btnPressed = false;  //variable true if one button was pressed, default false 

void setup() {
  
  myStepper.setPinsInverted(false, false, true); // setInverted can be used here if pins are inverted
  myStepper.setEnablePin(enablePin);
  myStepper.setMinPulseWidth(20);                // seems like tb6600 works better with this
  myStepper.setMaxSpeed(maxSpeed);
  myStepper.setAcceleration(acceleration);

  pinMode(enablePin, OUTPUT);
  myStepper.enableOutputs();
  delay(50);
  myStepper.disableOutputs();
}

void loop() {

  btnUP.poll();
  btnDOWN.poll();

  if (btnUP.onRelease() || btnDOWN.onRelease()){
    myStepper.stop();
    btnPressed = false;
  }

  else if (btnUP.onPress()){
    myStepper.enableOutputs();
    myStepper.move(40000);
    btnPressed = true;
  }

  else if (btnDOWN.onPress()){
    myStepper.enableOutputs();
    myStepper.move(-40000);
    btnPressed = true;
  }

  if (btnPressed == false && !myStepper.isRunning()){
    myStepper.disableOutputs();
  }

  myStepper.run();
}

40000 steps is the number of steps for the entire linear rail lenght so I'm certain to move it completely up or down, independently of its current position.

I know the code is not optimal if both buttons are pressed at once, but it is simply impossible to achieve this on the hardware side because they are mechanically connected.

Another small issue is at startup. When I first connect the power supply, the motor rotates a few steps in one (unpredictable) direction (10 or so).
I guess I'll have to check the setup() for some errors...

Anyway, thanks for your advices :wink:

what does the power circuit look like?

Well, it's just a 12V-3A connected into the tb6600 driver (with 100µF 25V capacitor in parallel) and it also powers the arduino nano through 2 diodes (so the arduino gets approximately 10,6V instead of the max. limit of 12V).

if you have an empty sketch in the arduino, does the motor rotate when you power it?

I can not find a reason for this to be true, however; reading clarity was mentioned.

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