Stepper Complex Motion Profile

Hi,

Trying to implement not usual motion profile with a stepper motor controlled by driver which takes step/dir signals. Specifically what I want is something like the following:

  1. Accelerate to speed A
  2. Rotate at constant speed A for a specified amount of revolutions.
  3. Accelerate to speed B
  4. Rotate at constant speed B for a specified amount of revolutions.
  5. Decelerate back to speed A.
  6. Start over.

Was trying to implement it with the AccelStepper library, but it doesn't do accel/decel from one speed to another and I can't seem to find a way round this.

Would appreciate if anybody has an idea for this.

Thanks,
Dan

It is not difficult to control a stepper without AccelStepper. See the examples in Simple Stepper Code - especially the second one.

Starting from that point it should not be difficult to create your own acceleration function. It is just a case of reducing the value of the variable millisBetweenSteps according to your acceleration system.

…R
Stepper Motor Basics

Hi Robin,

Thanks for the prompt reply and the example (as you guessed, the second one is of interest). I will try to work my way from there.

An interesting approach of not having any delay between setting the Step pin HIGH and LOW, but rather relying on the digitalWrite() function's execution time. Have you checked how long it takes for digitalWrite() to execute on a 16MHz chip?

Regards,
Dan

Well, uploaded this program and checked the execution time of the digitalWrite():

 int long executeTime;
 int long startTime;
 void setup() {

  Serial.begin(9600); // opens serial port, sets data rate to 128000 bps
  while (!Serial) {
  ; // wait for serial port to connect. 
  }
  
  pinMode (6, OUTPUT);
 }
 
 void loop() {
  startTime = micros();
  digitalWrite(6, HIGH);
  executeTime = micros() - startTime;
  Serial.println(executeTime);
  delay(1000);
 }

It floats between 8 and 12 uSec, or more correctly it sometimes prints 8 while other 12.

Regards,
Dan

micros() has a resolution of 4 µsecs - hence the 8 or 12

Measure it over several hundred steps and get an average.

...R

You can use direct digital synthesis (DDS) to generate profiled step sequences - normally
the technique is used to generate analog waveforms via table lookup, but you can map each
full cycle to a pulse output.

At its simplest you have an interrupt routine running at a fixed high frequency (lets say 20kHz),
and it does something like:

volatile long phase = 0 ;
volatile long veloc = 0 ;
volatile long accel = 0 ;

ISR (TIMER2_OVF_vect)  // some timer interrupt set to an appropriate freqency
{
  veloc += accel ;
  bool sign = phase < 0 ;    // check sign bit
  phase += veloc ;
  bool now_sign = phase < 0 ;
  if (sign != now_sign)        // sign bit flipped, generate a pulse
  {
    digitalWrite (dir_pin, veloc < 0) ;
    digitalWrite (step_pin, HIGH) ;
    delayMicroseconds (5) ;
    digitalWrite (step_pin, LOW) ;
  }
}

Then in the main program you control accel and/or veloc (with interrupts disabled) to
control the DDS loop. The maximum rate steps can be generated is the frequency the
ISR runs, the minimum rate is determined by the precision of the 32 bit long values
involved.

Veloc values scale proportional to the ISR frequency, and the accel values proportional
to the square of the ISR frequency, which complicates things.

The virtue is the main program code is relatively simple to write and doesn’t have to do
anything but decide when to change accel or veloc.

Hi Mark,

It is an interesting method you describe, but I don't fully understand the implementation. Particularly in your example I don't understand what are the variables: sign, now_sign and phase. And I don't see how accel and velocity are implemented.

By the way, if it's of interest to others, I checked the execution time of the digitalWrite() command over 1000 steps and it is 7.6 uSec.

Dan

Robin,

Tried your code. Works real well. It is able to pulse much, much faster the the AccelStepper library, which is limited to something like 2000 or 4000 steps/second on a 16Mhz chip. Was able to run at something like 25000 steps/second with your code!! Probably could do faster but needs some acceleration function defined to get to higher speeds.

Can't figure the math yet to do the accel the way I want.

Dan

Thank you for your kind words.

Be aware that you may not have any spare CPU cycles available for anything else at the highest pulse rates.

Of course you could also use port manipulation which is much faster than digitalWrite(). But then you would need to make specific allowance for the pulse width.

...R

Using Port Manipulation and adding allowance for the pulse width would result in the same amount of clock cycles, wouldn't it :wink: Fortunately, however, I don't need it pulsing so fast - 5000 pulses/second at most I reckon, which (with a 7.6 uSec pulse width) would mean a 3.8% duty cycle, and correspondingly 96% of free CPU time.

Dan

EDIT:
Didn't consider in the above that the digitalWrite() executes twice per step, so a D.C. of 7.6% and correspondingly 92% of free CPU time. It is still correct, though, that the pulse width is 7.6 uSec.

Well, I am kind of stuck with trying to define the accel function. Since I need to correlate between the speed (pulses per second) which can be an int, and time between pulses (in microseconds), it requires converting the speed to pulse per uSeconds, and hence using floats which I want to avoid as it is my understanding that they may not be accurate and it takes much more CPU time to do operations with them.

I need to define the following (for a start at least):

Speed 1 (in steps per unit of time)
Speed 2 (in steps per unit of time)
Accel from speed 1 to 2 (in steps per sqr unit of time)
Time between pulses for Speed 1 (in microseconds)
Time between pulses for Speed 2 (in microseconds)
Dynamic time between pulses for accel (in microseconds)

And I don't know how tie these all together.

Later I want to use potentiometers to to control all the above.

Dan

Without seeing some code (or pseudo code) it is hard to understand what you are having a problem with.

I would avoid using floats if at all possible. They are less accurate and very slow. An unsigned long permits an accuracy of 1 part in 4 billion. With careful attention to the order of calculations it should be possible to do everything with whole numbers. Some experimenting with a calculator or spreadsheet is called for.

Another thought - can you do some of the preparatory calculations yourself and just put the "results" into the program - for example expressing speed in steps per second so the Arduino is relieved from the more complex calculations.

For the future, the potentiometers will give you integer values.

...R

OK, here's an example code of what I am trying to do:

#define highSpeedSector 1000 // the length of move at high speed in steps
#define lowSpeedSector 1000 // the length of move at low speed in steps
#define accelSector 170        //the length in steps over which the accel should occur - speed linearly
                                        //increases over this whole range

#define pulseWidth 7.6 // the execution time of the digitalWrite() command
int highSpeed = 80; //steps per second
int lowSpeed = 40; // steps per second
int stepperAcc;  
int stepperPosition = 0;
//Calculate the acceleration to spread along the accelSector:
int stepperAccel = (((highSpeed-lowSpeed)/2)*((highSpeed+lowSpeed)/accelSector)); // in steps/Sec^2

unsigned long microsBetweenStepsHiSpeed = (1/highSpeed)*1000000 - pulseWidth; //converted to uSec
unsigned long microsBetweenStepsLoSpeed = (1/lowSpeed)*1000000 - pulseWidth; //converted to uSec
unsigned long curMicros;
unsigned long prevStepMicros = 0;

void loop() {
    curMicros = micros();
    moveLowSpeed(); // rotates the stepper at constant speed LOW, over lowSpeedSector
    accel(); // accelerates from speed LOW to speed HIGH over accelSector
    moveHighSpeed();// rotates the stepper at constant speed HIGH, over highSpeedSector
    decel();// decelerates from speed HIGH to speed LOW over accelSector
}

Note that accel and speeds are units per Sec (or Sec^2), while time between pulses is in microseconds.

Dan

This sort of thing is pointless
unsigned long microsBetweenStepsHiSpeed = (1/highSpeed)*1000000 - pulseWidth; //converted to uSec
Work it out on a calculator and put in the answer. If necessary add the formula as a comment.

I don't know if this will be recalculated while the program is running (i.e. if it will appear elsewhere in the program)?
int stepperAccel = (((highSpeed-lowSpeed)/2)*((highSpeed+lowSpeed)/accelSector));
If not, the previous comment applies.
If yes then you should run through a worked example to be sure that each stage of the calculation fits within whatever data size you have chosen. It may be a good idea to do the calculation in a few distinct steps. Why not use a long rather than an int? You could then convert the result to an int if required.

You need to be careful using 7.6 as the pulse width. It won't work in integer maths.

I suspect it is not necessary because you should be able to arrange the pulse timing to take the pulse width into account if you need that degree of accuracy. In other words arrange things so that microsBetweenSteps takes account of the pulse width.

...R

I see your points, Robin. Trying to work it out...

How does it harm to do the calculation in the program while assigning the variable before the main loop? Thing is I may want to change the variable value comprising the formula.

Dan

dan13:
How does it harm to do the calculation in the program

It will only do harm if it gives the wrong answer, perhaps due to the limitations of fixed length integer maths. It is easier to be sure of the correct value with a calculator.

...R

Need some help, trying to avoid using float type variables. Here is my code:

#define highSpeedSector 1000
#define lowSpeedSector  1000
#define accelSector  170
#define decelSector 170
#define pulseWidth 7.6 // the execution time of the digitalWrite() command
#define highSpeed  140 //steps per second
#define lowSpeed 80 // steps per second

int stepperPosition = 0;
int n=1;
int currentSpeed = 0;
int initialSpeed = 0;
//Calculate the acceleration to spread along the accelSector
long stepperAccel = ((highSpeed-lowSpeed)/2)*((highSpeed+lowSpeed)/accelSector); //pulses/Sec^2

byte directionPin = 13;
byte stepPin = 12;
byte analogInPin = A0;    // select the input pin for the potentiometer

unsigned long curMicros;
unsigned long prevStepMicros = 0;
unsigned long microsBetweenStepsHiSpeed = (1000000/highSpeed) - pulseWidth; // converted to microseconds
unsigned long microsBetweenStepsLoSpeed = (1000000/lowSpeed) - pulseWidth; // converted to microseconds
unsigned long microsBetweenStepsCurrent;

int analogReadCounter = 3000;
int potValue = 0;  // variable to store the value coming from the potentiometer

void setup() {

  Serial.begin(9600);
  
  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  
}

void loop() {
	
	curMicros = micros();
        if (stepperPosition == 0) {
          initialSpeed = lowSpeed;
          microsBetweenStepsCurrent = 1000000/initialSpeed;
          n=1;
        }
        accel();
          
        
        if (stepperPosition == accelSector) {
          initialSpeed = highSpeed;
          microsBetweenStepsCurrent = 1000000/initialSpeed;
        }
        moveHiSpeed();
        
        if (stepperPosition == highSpeedSector + accelSector) {
          initialSpeed = highSpeed;
          microsBetweenStepsCurrent = 1000000/initialSpeed;
          n=1;
        }
        decel();
        
        if (stepperPosition == accelSector + highSpeedSector + accelSector) {
          initialSpeed = lowSpeed;
          microsBetweenStepsCurrent = 1000000/initialSpeed;
        }
        moveLoSpeed();
        
        if (stepperPosition == accelSector + highSpeedSector + accelSector + lowSpeedSector)
          stepperPosition = 0;
  
}

void accel() {
      if ( stepperPosition < accelSector) {
        if (curMicros - prevStepMicros >= microsBetweenStepsCurrent) {
           currentSpeed = initialSpeed + stepperAccel * n;
           microsBetweenStepsCurrent = 1000000/(currentSpeed); //converted to microseconds
           prevStepMicros += microsBetweenStepsCurrent;
           digitalWrite(stepPin, HIGH);
           digitalWrite(stepPin, LOW);
           stepperPosition ++;
           n++;
           
        }
      }
}  
        
      
void moveHiSpeed() {
      if (stepperPosition < highSpeedSector + accelSector && stepperPosition >= accelSector) {
	if (curMicros - prevStepMicros >= microsBetweenStepsHiSpeed) {
           prevStepMicros += microsBetweenStepsHiSpeed;
	   digitalWrite(stepPin, HIGH);
	   digitalWrite(stepPin, LOW);
           stepperPosition ++;
        }
      }
}

void decel() {
      if (stepperPosition < accelSector + highSpeedSector + accelSector && stepperPosition >= highSpeedSector + accelSector) {
        if (curMicros - prevStepMicros >= microsBetweenStepsCurrent) {
           currentSpeed = initialSpeed - stepperAccel * n;
           microsBetweenStepsCurrent = 1000000/(currentSpeed); //converted to microseconds
           prevStepMicros += microsBetweenStepsCurrent;
           digitalWrite(stepPin, HIGH);
           digitalWrite(stepPin, LOW);
           stepperPosition ++;
           n++;
        }
      }
} 

void moveLoSpeed() {
      if (stepperPosition < accelSector + highSpeedSector + accelSector + lowSpeedSector && stepperPosition >= accelSector + highSpeedSector + accelSector) {
	if (curMicros - prevStepMicros >= microsBetweenStepsLoSpeed) {
	   prevStepMicros += microsBetweenStepsLoSpeed;
	   digitalWrite(stepPin, HIGH);
	   digitalWrite(stepPin, LOW);
           stepperPosition ++;
	}
      } 
}

Motion is divided into 4 phases:

  1. Acceleration
  2. Move at high
  3. Deceleration
  4. Move at low speed

Problem is in the the accel() and decel(), I am calculating the current speed, and inverse of which is then taken (and multiplied by 1e-6) to obtain the current microsBetweenStepsCurrent. The problem is that the variables: currentSpeed, initialSpeed and stepperAccel - all have seconds in them, so in order for the calculation of currentSpeed being correct, the product:

stepperAccel * n

needs to be converted proportionally to seconds, like this:

(stepperAccel * n) * microsBetweenStepsCurrent/1e+6

How can I do this without float? Any idea?

Dan

I would completely separate the step-interval calculation from the actual steps.

I would have a simple step function that causes steps at a rate set in the step-interval variable. The same function would cause ALL motor moves. Apart from anything else if you avoid duplicating code it greatly reduces the scope for errors.

Then I would have some code to update the value in that variable depending on whether you are in an accelerating phase or a constant speed phase.

As far as calculating a step interval is concerned (which is your main question) have you tried to work through some typical calculations with a calculator - rounding the values after each calculation. If so please post an example.

...R

dan13:
Hi Mark,

It is an interesting method you describe, but I don't fully understand the implementation. Particularly in your example I don't understand what are the variables: sign, now_sign and phase. And I don't see how accel and velocity are implemented.

Dan

phase is the phase - if you don't understand phase its time to go out and do a little reading really.
I sample the sign of the phase variable before and after incrementing it - then I can tell when it changes,
thus I can tell when its time to output a step. I actually output steps on both sign changes so thats
every 180 degrees of phase.

In the digital discrete domain your phase variable is normally arranged so that it wraps every 2pi
radians (360 degrees) since 2's complement arithmetic wraps exactly as you want. Thus the most
significant bits of a phase variable are the interesting ones.

The frequency variable actually represents frequency divided by the sample rate, ie the phase
increment needed per sample.

Hi Robin,

Yes, I tried using numbers. Here's an example:

#define accelSector 170
#define highSpeed 140
#define lowSpeed 80

calculating stepperAccel:

stepperAccel = ((highSpeed-lowSpeed)/2)*((highSpeed+lowSpeed)/accelSector)

(highSpeed-lowSpeed)/2 = (140-80)/2 = 30
(highSpeed + lowSpeed)/accelSector = (140+80)/170 = 1.29 ---> rounding to whole number ---> 1
and the product of these two ---> stepperAccel = 30*1 = 30

calculating further:
currentSpeed = initialSpeed + (stepperAccel * n)microsBetweenStepsCurrent/1e+6
initialSpeed = 80
stepperAccel
n = 30*1 = 30
microsBetweenStepsCurrent = 1000000/initialSpeed = 1000000/80 = 12500
microsBetweenStepsCurrent/1e+6 = 12500/1000000 = 0.0125 ---> can't round this - would yield ZERO.

combining this all:
currentSpeed = 80 + 30*0.0125 = 80.375

Dan