Stepper motor step signal timing calculation

While we use stepper motors in many devices, the timing of the step signals is not always well understood. Most of us can see how a fixed speed is a bad idea unless the speed of motion is really slow, but it is fixed speed what is really to generate with simple code. You just can use the Arduino Blink example to send a fixed frequency of pulses to a stepper motor driver. If too slow, just reduce the delay value.

But for most real-life applications variable speed controls will perform better. These systems will speed up the motor to a given maximum speed and it will slow it down to stop at the desired step count. But understanding the math behind this process is sometimes not obvious.

Let us start with the simple case of the so-called trapezoidal speed motion profile. Here motion takes place in three phases: acceleration, constant speed (cruising) and deceleration. But the point is how can our code create such a behavior.

Stepper motor drivers usually accept two signals to control motor motion: a direction signal, that will make the motor rotor CW or CCW depending on whether it is LOW or HIGH, and a second signal called step that will make the motor move one step more every time a pulse is applied to it.

For a given moment, and after setting the right direction for it, the tricky part is to provide the exact number of pulses required (N) so each one is properly timed according to each motion phase. But in order to provide the right timing, we need to understand that each step pulse makes the motor move one discrete angle. That means each pulse causes a fixed amount of displacement no matter the timing between the pulses.

The easy part

When the motor is coasting at a constant speed, pulses are happening at a fixed rate, so the time interval between them remains constant. For example, for a speed of 100 steps/second, the time in between pulses is 1/100 seconds or 10 milliseconds. Providing a new pulse after this exact time will keep the motor spinning at a constant speed.

The ugly part

So for the simple case of a constant acceleration (a), and assuming we express the acceleration in steps/second2, the first step should happen at the time that will be equal to the motion of a single step. If you recall from High School physics, the distance traveled from a standstill can be calculated as d=(a*t2)/2, so if distance==1 then the time will be t=(2*1/a). For the second step, the time will be t=(2*2/a) and for pulse 'i' the time will be t=(2*i/a).

But please note that for our code, more than the absolute value of the time when a certain step happens, what we need is the time interval we will use for our timer. So, for the first step pulse, we can just use the value for distance of one step, that is (2*1/a) but for the second step, what we use is the time difference between the moment of the first and the second pulse, that is  (2*2/a)- (2*1/a). And for the pulse 'i', the time interval will be  (2*(i)/a)-(2*(i-1)/a).

This is what is kind of fast forwarded in AVR446, that otherwise contains a lot of useful information for coding stepper timing on a microcontroller. (BTW, You may see that many links to AVR application notes are now gone due to the acquisition of Atmel by Microchip). That is why they tell you there that the timing of a pulse is proportional to (i)-(i-1).

Processing time considerations

Unfortunately, if you are calculating the next interval time value in between pulses, the amount of time available for calculations quickly diminishes as the motor accelerates. If your code runs on an 8-bit microcontroller processing time can become an issue.

To speed things up, an approximation can be made so each new time interval between pulses during acceleration (or deceleration) phase can be calculated with the following expression:
Which represents the timer value calculated based on the timer value of the previous step. So only a couple of shifts (product by 2 or 4) and a division are needed and therefore the calculation can be done quite quickly and using integers. 

This is exactly what I used for my own code in here.

Going further

But what if you do not use a constant acceleration, as you want to use some type of s-curve motion profile? Well, the acceleration solution above is of no use then, and you need to look somewhere else. 

What we have is that a given acceleration will shape the way speed changes over time while acceleration and that, in turn, will affect how distance is traveled as the motor accelerates, shaping the time intervals in between pulses in a particular way.

But things can get messy quickly. For example: let us assume our acceleration is a(t)=1-cos(t) then, the speed expression will be the integral of the acceleration over time so s(t)=t-sin(t). We have traded the speed ramp for a smoother transition, but let us have a look at the timing of the step pulses.

The expression of the traveled distance over time will then be d(t)=t2/2+cos(t)-1 but when we try to determine the time that will correspond to distance==1 we realize there is no way we can find the inverse function in the form of t=...

wxMaxima to the rescue

Do not panic, there must be a way of figuring out when exactly a distance of 1 step has been covered. But given we only have the equation of know the distance traveled after a certain amount of time, let us try to get some math tool to do the heavy lifting: I do use, like and recommend wxMaxima and wolframalpha.com, both free to use and quite powerful. 

So we just need to solve the equation where d=1 for the variable t. And while the solve command won't help us here (as we are dealing with a transcendental expression), we can obtain an approximate result with the find_root command:
We even could use a loop to obtain the time corresponding to each integer value of the distance, and the time between a pulse and the next one is the difference between the time of this step and the time of the next step.  But this process may not be very convenient if, for example, we want to change the acceleration.

Turning numbers into timer values

Once we have time values for d=1, d=2, ... we have to turn them into timer values for the steps to take place at the right time. However, there is a catch. No matter how many values we have precalculated, a given movement may require more or fewer steps than that value, so we need a way to adapt our discrete set of values to a general case.

On the other hand, another way to think about the acceleration is to consider it can be represented by a certain amount of time (the time while acceleration takes place). So the problem we need to solve here is to find out a way to properly spread a number if steps (M) across a time period (T). 

The reference time in our acceleration pattern goes from 0 to 2Π and the maximum distance traveled after that time is 2Π2

So when the distance to cover is M steps, then we will calculate the time that corresponds to each distance=2*PI*PI*i/M where 'i' goes from 0 to M. The difference between each pair of adjacent values P=t(i)-t(i-1) is the delay between pulse i-1 and pulse i. We just need to turn these values into timer counts.

However, as the total time the acceleration phase lasts is T and the time value used for the table was 2Π then we need to scale that value to P*T/2/PI. Though this value is still in seconds. Now, depending on the timer frequency, the integer value for the timer count will be k*P*T/2/PI. For example, k=1000000, if timer count frequency is 1Mhz, as one count, is then one microsecond.

 From distance to time

I intentionally left unexplained how the code is to obtain the time that corresponds to a distance. The solution that works for me requires two different steps. 

The first part is to obtain a lookup table of distance and time pairs. If obtained using the d(t) expression above, the pairs can be equally spaced in time easily. But for best results, we want the values to be equally spaced in the distance axis (you could do that using the wxMaxima route for example).

The second part is that, for 'i' step to lookup for the time that corresponds to distance 2*PI*PI*i/M and subtract it the value for the previous step (i-1). Then this time value is scaled properly according to the acceleration time and converted into a timer count. Once that time has expired, the pulse is fired and the process repeated till M pulses have been generated. Next, we start the coast phase, where speed is just maintained, till deceleration starts.

Using a lookup table takes care of removing any complexity in the expression of the curves out of the real-time operation of the code, as the linear interpolation can be done very quickly each step.

Comments

Popular posts from this blog

VFD control with Arduino using RS485 link

How to get sinusoidal s-curve for a stepper motor

Importing OpenSCAD designs into Onshape