Stepper acceleration

Hello all,
This is my very first post and first project with Arduino/programming at all so please forgive my idiocy :).
I am trying to build off of some code posted on a dronebotworkshop tutorial to control a stepper with limit switches (driving a linear axis with a lead screw) and want to incorporate acceleration.
First off, the Hardware;
Arduino Uno
Stepper - Nema23 Closed Loop Motor 23SSM8440-EC1000 4A 2.1Nm
Driver- TB6600
Limit Switches - inductive hall effect proximity sensors
Rail-openbuilds c-beam 1000mm with 8mm pitch lead screw.

The attached code works great as it is but I cannot figure out how to incorporate acceleration to the start and stop.

*/

// Define connections
#define HALL_SENSOR_A      2
#define HALL_SENSOR_B      3
#define DIR      10
#define STEP      11

// Variables
int pd = 4000;       // Pulse Delay period
boolean setdir = LOW; // Set Direction

// Interrupt Handlers

void limit_a (){

  // Reverse motor
  setdir = !setdir;
  
}

void limit_b (){

  // Reverse motor
  setdir = !setdir;
  
}


void setup() {
  
  // Setup the stepper controller pins as Outputs
  pinMode(DIR,OUTPUT); 
  pinMode(STEP,OUTPUT);
  
  // Setup the Hall Effect switches as Inputs
  pinMode(HALL_SENSOR_A, INPUT);
  pinMode(HALL_SENSOR_B, INPUT);
  
  // Attach interrupt pins to handlers
  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_A), limit_a, FALLING);
  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_B), limit_b, FALLING);
   
}

void loop() {
  
    digitalWrite(DIR,setdir);
    digitalWrite(STEP,HIGH);
    delayMicroseconds(pd);
    digitalWrite(STEP,LOW);
    delayMicroseconds(pd);
 
}

I have tried the accelstepper library but with limited maximum steps and step rate it wasn't able to do the job. I am planning on adding a second stepper/axis in the future but before i continue messing with things I would like to get the accel/decel figured out before i damage anything just slamming the stepper in reverse.
Ideally, i would like to the motor to accelerate the carriage from stop to a determined speed, when the carriage reaches the far side limit switch, it decelerates, changes directions and accelerates back in the other direction.
*note-i do not want to define a number of steps to move as i want to be able to control the travel distance by physically moving the hall sensor end stops and not have to modify the code. Total number of steps taken is irrelevant as actual position is provided via the encoder to a separate ultrasonic instrument.

Sorry for the long question and thanks in advance for any tips.

This Simple acceleration code may give you some ideas.

...R
Stepper Motor Basics
Simple Stepper Code

Thanks for the quick reply Robin2
I had seen your post there before, I did try to read into the issue before posting, but couldn't figure out how to implement it.

First I tried integrating it into the limit settings (when limit a or b is activated it would start the acceleration loop)

Second I tried to modify your code so that instead of counting steps down until it starts the acceleration (deceleration) that it would wait for input from sensor.

I managed to just make a mess of both and don't know if i am on the right track. Like I said I am a complete newbie to programming and out of my element. Which method would you recommend continuing with before I go any further in the wrong direction?

Thanks again.

andym102:
First I tried integrating it into the limit settings (when limit a or b is activated it would start the acceleration loop)

Second I tried to modify your code so that instead of counting steps down until it starts the acceleration (deceleration) that it would wait for input from sensor.

[...]

Which method would you recommend continuing with before I go any further in the wrong direction?

I can't really envisage how acceleration relates to a limit switch. I think of a limit switch as stopping something. And you can only initiate deceleration with a limit switch if it is safe to continue past the switch - in which case I would not call it a limit switch :slight_smile:

I think you need to describe in English how you want your system to operate. A clear statement of the requirement is essential before a successful program can be created.

It is also difficult to make useful comments without seeing the code you have tried (the complete program) as well as telling us in detail what it actually does and what you want it to do that is different. That way we can focus on the parts you need help with rather than wasting time on things that you can do.

...R

You are right, traditional limit switch would be in line with the travel axis so maybe not the best term. I am using inductive hall effect sensors, mounted perpendicular to the travel axis in such a way that the carriage can drive by without coming in contact. As soon as it comes close enough, switch closes, and the carriage can decelerate without having to worry about crashing into my sensor.

So in plain English what I'm trying to accomplish

-stepper motor should accelerate to speed X
-continue at speed X until it activates hall effect sensor A.
-stepper decelerates to a stop
-stepper changes direction accelerates back to speed X
-continue at speed X until it activates hall effect sensor B
-stepper changes direction accelerates back in other direction and so on.
....... Basically a linear axis driving back and forth, but instead of driving a certain amount of steps before starting to decelerate, driving until it gets input from hall effect sensor before slowing down and changing direction.

I will have to post my abomination of code when I am back at my computer, unfortunately I do not have it with me presently.

Is the position of the hall-effect sensors going to change? In other words is the length of travel going to change?

If not, why not just move a number of steps?

...R

Yes, the position of the Hall effect sensors is going to change. I need to be able to limit the travel of the carriage by moving the sensors and not having to change the coding, it will be running as a stand alone and not be hooked up to computer (or other input)

andym102:
Yes, the position of the Hall effect sensors is going to change.

Thanks.

Will wait to see your code.

...R

Okay, so still no success in getting this to work for me. Using your code it was no problem getting it to run the full length of my rail (900mm) while reaching the maximum speed required, acceleration works great, however I still have no idea what I am doing with the hall sensors.

With the code as it stands, without input from the hall sensors, it will drive the platform from 0mm to 900mm and back and forth accelerating and decelerating like it should. If I activate a hall sensor, instead of immediately having the platform slow and change directions, it allows it to finish its travel and then continues in the same direction it was travelling (so travel 0mm to 900mm, decelerate to stop, accelerate from 900mm to 1800mm.

I assume there is a blocking function working during that time, but I do not know how to get around that to have the input from the hall sensors be immediately effective.

// testing a stepper motor with a TB6600 driver

// this version uses micros() to manage timing to allow high step rates to be tested
// and illustrates a simple method for accleration and deceleration
byte HALL_SENSOR_A = 2;
byte HALL_SENSOR_B = 3;
byte directionPin = 10;
byte stepPin = 11;


unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long slowMicrosBetweenSteps = 1000; // microseconds
unsigned long fastMicrosBetweenSteps = 135;
unsigned long stepIntervalMicros;
unsigned long stepAdjustmentMicros;
int numAccelSteps = 600; // 200 step motor half stepping
long int numSteps = 45000;
long int stepsToGo;
byte direction = 1;

// Interrupt Handlers

void limit_a (){

  // Reverse motor
  direction = !direction;
  
}

void limit_b (){

  // Reverse motor
  direction = !direction;
  
}
 
void setup() {

    Serial.begin(115200);
    Serial.println("Starting Stepper Demo with acceleration");

    pinMode(directionPin, OUTPUT);
    pinMode(stepPin, OUTPUT);

      // Setup the Hall Effect switches as Inputs
  pinMode(HALL_SENSOR_A, INPUT);
  pinMode(HALL_SENSOR_B, INPUT);
   // Attach interrupt pins to handlers
  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_A), limit_a, FALLING);
  attachInterrupt(digitalPinToInterrupt(HALL_SENSOR_B), limit_b, FALLING);

    stepAdjustmentMicros = (slowMicrosBetweenSteps - fastMicrosBetweenSteps) / numAccelSteps;
    stepIntervalMicros = slowMicrosBetweenSteps;
    stepsToGo = numSteps;
    digitalWrite(directionPin, direction);
}

void loop() {

    moveMotor();

}

void moveMotor() {
    if (stepsToGo > 0) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
            stepsToGo --;
            if (stepsToGo <= numAccelSteps) {
                if (stepIntervalMicros < slowMicrosBetweenSteps) {
                    stepIntervalMicros += stepAdjustmentMicros;
                }
            }
            else {
                if (stepIntervalMicros > fastMicrosBetweenSteps) {
                    stepIntervalMicros -= stepAdjustmentMicros;
                }
            }
        }
    }
    else {
        direction = ! direction;
        digitalWrite(directionPin, direction);
            // next two lines just for the demo
        delay(200);
        Serial.println("Changing direction");
        stepsToGo = numSteps;
        prevStepMicros = micros();
    }
}

void singleStep() {

    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);

}

You are using the hall sensor to change the value of the variable direction. That makes no sense. The direction should not change until the motor stops. It needs to change a variable (let's simply call it sensorBtriggered).

Then you code can be something like this

if (direction == 'L' and sensorBtriggered == true) {
	if (slowingDown == false) {
		stepsToGo = numAccelSteps;
		slowingDown = true;   // using this variable in case the ISR triggers a second time
	}
}

then when stepsToGo gets down to 0 it will be time to change direction. Note that I have made direction a char variable so it can be either Left or Right. You need to know the direction so as to know which sensor signals the end of the run.

This is only part of the solution because on the way back sensorB will be triggered again near the start of the run.

You need to go through all the situations that will happen - write them down, one on each line - and then formulate a strategy to deal with them.

See what you can make of it with the above as a hint.

...R

That makes sense, looking forward to messing around with that. Thank you.

To the point of it triggering sensor B on the way back close to the start of its run, in the current (physical) configuration it would actually stay triggered as long as the carriage is at that end not just momentarily trigger and then be triggered on the way back but being released when the carriage has moved away. I'm hoping this will reduce the amount of situations I'll have to account for, but I really don't know.

andym102:
it would actually stay triggered as long as the carriage is at that end

That should simplify things

...R

Well, I think I have just managed to make more of a mess of things as opposed to progress here. Now the motor will run back and forth, but the sensors have zero impact. I have no clue .

// testing a stepper motor with a Pololu A4988 driver board or equivalent

// this version uses micros() to manage timing to allow high step rates to be tested
// and illustrates a simple method for accleration and deceleration
byte sensorA = 2;
byte sensorB = 3;
byte directionPin = 10;
byte stepPin = 11;
char L = 0;
char R = 1;





unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long slowMicrosBetweenSteps = 1000; // microseconds
unsigned long fastMicrosBetweenSteps = 300;
unsigned long stepIntervalMicros;
unsigned long stepAdjustmentMicros;
unsigned long slowingDown;
long int numAccelSteps = 600; // 200 step motor half stepping
long int numSteps = 10000;
long int stepsToGo;
byte direction = 'R';


// Interrupt Handlers

void sensorAtriggered (){

if (direction == 'L' and sensorAtriggered == true) {
  if (slowingDown == false) {
    stepsToGo = numAccelSteps;
    slowingDown = true;   // using this variable in case the ISR triggers a second time
    if (stepsToGo = 0){
      direction = !direction;
    }
  }
}

}

void sensorBtriggered (){
if (direction == 'R' and sensorBtriggered == true) {
  if (slowingDown == false) {
    stepsToGo = numAccelSteps;
    slowingDown = true;   // using this variable in case the ISR triggers a second time
    if (stepsToGo = 0) {
      direction = !direction;
    }
  }
}
  
}
 
void setup() {

    Serial.begin(115200);
    Serial.println("Starting Stepper Demo with acceleration");

    pinMode(directionPin, OUTPUT);
    pinMode(stepPin, OUTPUT);

    // Setup the Hall Effect switches as Inputs
  pinMode(sensorA, INPUT);
  pinMode(sensorB, INPUT);
  
   // Attach interrupt pins to handlers
  attachInterrupt(digitalPinToInterrupt(sensorA), sensorAtriggered, FALLING);
  attachInterrupt(digitalPinToInterrupt(sensorB), sensorBtriggered, FALLING);

    stepAdjustmentMicros = (slowMicrosBetweenSteps - fastMicrosBetweenSteps) / numAccelSteps;
    stepIntervalMicros = slowMicrosBetweenSteps;
    stepsToGo = numSteps;
    slowingDown = (prevStepMicros < curMicros);
    digitalWrite(directionPin, direction);
}


void loop() {

    moveMotor();

}

void moveMotor() {
    if (stepsToGo > 0) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
            stepsToGo --;
            if (stepsToGo <= numAccelSteps) {
                if (stepIntervalMicros < slowMicrosBetweenSteps) {
                    stepIntervalMicros += stepAdjustmentMicros;
                }
            }
            else {
                if (stepIntervalMicros > fastMicrosBetweenSteps) {
                    stepIntervalMicros -= stepAdjustmentMicros;
                }
            }
        }
    }
    else {
        direction = ! direction;
        digitalWrite(directionPin, direction);
            // next two lines just for the demo
        delay(500);
        Serial.println("Changing direction");
        stepsToGo = numSteps;
        prevStepMicros = micros();
    }
}

void singleStep() {

    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);

}

All I intended you to put in your ISRs is this

void limit_a (){
  sensorAtriggered = true;
}

void limit_b (){
  sensorBtriggered = true;
}

The rest of the code that checks if (sensorAtriggered == true) etc should go in the section that controls the motors

Keep it simple :slight_smile:

...R

That makes more sense.
Thanks again.

You say keep it simple, but it may as well be written in Chinese for me at this point. :confused:

Maybe I'll have to invest in the Arduino for Dummies literature or is there any other reference material you would recommend for someone just starting out?

andym102:
Maybe I'll have to invest in the Arduino for Dummies literature or is there any other reference material you would recommend for someone just starting out?

If you are a complete beginner then what you are trying to do is going to feel complex.

If I can grossly oversimplify, it seems to me the biggest hurdle for beginners is realising how the computer is utterly stupid so the programmer has to think of every little thing. In ordinary life we don't need to do that.

I don't have advice on beginner literature as I was familiar with programming long before I got an Arduino. I have looked briefly at the Arduino for Dummies book in a bookshop and it seemed good. I had Sailing for Dummies years ago and it was really excellent.

...R

Well on a scale of 1 to 10 I'd place my programming competence around 0. I'm usually happy if I can program the clock on the microwave, so will definitely have to start with the basics.

So, I've moved things around a bit and am not currently receiving any warnings or complaints on compiling but, they sensors are still providing the desired outcome.

I am not sure if;
-defining sensorAtriggered (sensorBtriggered) as unsigned long is correct
-placement of the endStop_a and endStop_b is correct in the loop
-definition of slowingDown is correct
-I screwed up anywhere else along the way or am even making progress to a functioning code haha.

// testing a stepper motor with a Pololu A4988 driver board or equivalent

// this version uses micros() to manage timing to allow high step rates to be tested
// and illustrates a simple method for accleration and deceleration
byte sensorA = 2;
byte sensorB = 3;
byte directionPin = 10;
byte stepPin = 11;
char L = 0;
char R = 1;





unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long slowMicrosBetweenSteps = 1000; // microseconds
unsigned long fastMicrosBetweenSteps = 300;
unsigned long stepIntervalMicros;
unsigned long stepAdjustmentMicros;
unsigned long slowingDown;
unsigned long sensorAtriggered;
unsigned long sensorBtriggered;
long int numAccelSteps = 600; // 200 step motor half stepping
long int numSteps = 10000;
long int stepsToGo;
byte direction = 'R';



// Interrupt Handlers

void limit_a (){
  sensorAtriggered = true;
  }

void limit_b (){
  sensorBtriggered = true;
}

 
void setup() {

    Serial.begin(115200);
    Serial.println("Starting Stepper Demo with acceleration");

    pinMode(directionPin, OUTPUT);
    pinMode(stepPin, OUTPUT);

    // Setup the Hall Effect switches as Inputs
  pinMode(sensorA, INPUT);
  pinMode(sensorB, INPUT);
  
   // Attach interrupt pins to handlers
  attachInterrupt(digitalPinToInterrupt(sensorA), limit_a, FALLING);
  attachInterrupt(digitalPinToInterrupt(sensorB), limit_b, FALLING);

    stepAdjustmentMicros = (slowMicrosBetweenSteps - fastMicrosBetweenSteps) / numAccelSteps;
    stepIntervalMicros = slowMicrosBetweenSteps;
    stepsToGo = numSteps;
    slowingDown = (prevStepMicros < curMicros);
    digitalWrite(directionPin, direction);
}


void loop() {

    moveMotor();
}

void moveMotor() {
    if (stepsToGo > 0) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
            stepsToGo --;
            if (stepsToGo <= numAccelSteps) {
                if (stepIntervalMicros < slowMicrosBetweenSteps) {
                    stepIntervalMicros += stepAdjustmentMicros;
                }
            }
            else {
                if (stepIntervalMicros > fastMicrosBetweenSteps) {
                    stepIntervalMicros -= stepAdjustmentMicros;
                }
            }
        }
    }
    else {
        direction = ! direction;
        digitalWrite(directionPin, direction);
            // next two lines just for the demo
        delay(500);
        Serial.println("Changing direction");
        stepsToGo = numSteps;
        prevStepMicros = micros();
    }
}
void endStop_a (){

if (direction == 'L' and sensorAtriggered== true) {
  if (slowingDown == false) {
    stepsToGo = numAccelSteps;
        if (stepsToGo = 0){
      direction = !direction;
    }
  }
}

}

void endStop_b (){
if (direction == 'R' and sensorBtriggered == true) {
  if (slowingDown == false) {
    stepsToGo = numAccelSteps;
    if (stepsToGo = 0) {
      direction = !direction;
    }
  }
}
  
}
void singleStep() {

    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);

}

Try this simpler version.

// testing a stepper motor with a Pololu A4988 driver board or equivalent

// this version uses micros() to manage timing to allow high step rates to be tested
// and illustrates a simple method for accleration and deceleration
byte sensorA = 2;
byte sensorB = 3;
byte directionPin = 10;
byte stepPin = 11;
char L = 0;
char R = 1;





unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long slowMicrosBetweenSteps = 1000; // microseconds
unsigned long fastMicrosBetweenSteps = 300;
unsigned long stepIntervalMicros;
unsigned long stepAdjustmentMicros;

boolean slowingDown = false;
volatile boolean sensorAtriggered; // volatile because they are changed by the ISR
volatile boolean sensorBtriggered;

long int numAccelSteps = 600; // 200 step motor half stepping
long int numSteps = 10000;
long int stepsToGo;
byte direction = 'R';



    // Interrupt Handlers

void limit_a (){
    sensorAtriggered = true;
    }

void limit_b (){
    sensorBtriggered = true;
}

 
void setup() {

    Serial.begin(115200);
    Serial.println("Starting Stepper Demo with acceleration");

    pinMode(directionPin, OUTPUT);
    pinMode(stepPin, OUTPUT);

        // Setup the Hall Effect switches as Inputs
    pinMode(sensorA, INPUT);
    pinMode(sensorB, INPUT);
    
        // Attach interrupt pins to handlers
    attachInterrupt(digitalPinToInterrupt(sensorA), limit_a, FALLING);
    attachInterrupt(digitalPinToInterrupt(sensorB), limit_b, FALLING);

    stepAdjustmentMicros = (slowMicrosBetweenSteps - fastMicrosBetweenSteps) / numAccelSteps;
    stepIntervalMicros = slowMicrosBetweenSteps;
    stepsToGo = numSteps;
    // DELETE this line slowingDown = (prevStepMicros < curMicros);
    digitalWrite(directionPin, direction);
}


void loop() {

    moveMotor();
}

void moveMotor() {
    if (stepsToGo > 0) {
        if (micros() - prevStepMicros >= stepIntervalMicros) {
            prevStepMicros += stepIntervalMicros;
            singleStep();
            stepsToGo --;
            
            if (slowingDown == false) {
                if (sensorAtriggered  == true or sensorBtriggered == true) {
                    stepsToGo = numAccelSteps;
                    slowingDown = true;
                }
            }
            
            if (stepsToGo <= numAccelSteps) { // need to slow down
                if (stepIntervalMicros < slowMicrosBetweenSteps) {
                    stepIntervalMicros += stepAdjustmentMicros;
                }
            }
            else { // stepsToGo is greater than numAccelSteps - hence accelerating
                if (stepIntervalMicros > fastMicrosBetweenSteps) {
                    stepIntervalMicros -= stepAdjustmentMicros;
                }
            }
        }
    }
    else {
        direction = ! direction;
        digitalWrite(directionPin, direction);
            // next two lines just for the demo
        delay(500);
        Serial.println("Changing direction");
        stepsToGo = numSteps;
        prevStepMicros = micros();
        
        slowingDown = false;
        sensorAtriggered = false;
        sensorBtriggered = false;
        
    }
}

void singleStep() {

    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);

}

Note that variables that take true or false values should be defined as boolean.

Note also that I am assuming your value for numSteps is much greater than the number of steps from the start to the end sensor.

...R

I most definitely wouldn't have ended up there on my own, I really appreciate all of your help. I'm looking forward to trying it out and building onto it. You are right, if I stick with 1/2 microstepping it will be 45000 steps, I think I would prefer 1/4 stepping to smooth things out a bit but that will be a matter of fine tuning trial and error.

The rest of the setup is hopefully not to complicated, adding a push button to start a homing sequence on startup, push button for start / stop loop, and adding a second stepper axis to index a few mm when the first stepper reaches the end of its run.

Certainly learning a lot in the process which is great.