Motion profiles: trapezoidal and s-curve
How a trapezoidal profile splits a move into speeding up, cruising and slowing down, how to work out the time and distance of each phase, and what an s-curve adds. Runnable programs drive the same 60 cm move flat out and on a profile and chart the speed and the position of each.
A motion profile is a plan for speed over time: speed up, cruise, slow down, arrive. Machines that move a load to a place and stop use one, from 3D printers and CNC machines to lifts, camera cranes and warehouse robots. The plan is worked out before the move starts, from the distance, a speed limit and an acceleration limit, and the controller's job is then to follow it. On this page a small robot makes the same 60 cm move with and without a profile, and each demo below is a real program you can change and run.
The chart under each robot shows two lines: the speed the flow sensor underneath measures, in cm/s, and the position in cm from where the move started.
The three phases
speed up at a until the speed reaches v
cruise at v for as long as the distance needs
slow down at a until the speed is zero
Two numbers set the shape:
t_acc = v / a how long a ramp takes
d_acc = v² / (2 × a) how far the robot goes during it
Both ramps together cover 2 × d_acc. Whatever is left is done at the cruise speed, so the flat top lasts (d − 2 × d_acc) / v. The whole move takes 2 × t_acc plus that.
For the move on this page, 60 cm with a speed limit of 12 cm/s and an acceleration limit of 8 cm/s/s:
t_acc = 12 / 8 = 1.5 s d_acc = 12² / (2 × 8) = 9 cm
cruise = (60 − 18) / 12 = 3.5 s over 42 cm
total = 1.5 + 3.5 + 1.5 = 6.5 s
The speed line is a trapezium, and the area under it is the distance. That is the check worth making every time: if the area is not the distance you asked for, the profile is wrong.
If the move is too short for the ramps to fit, there is no cruise phase at all. The profile becomes a triangle, and the peak speed is the square root of a × d, reached exactly half way. Forgetting that case is the classic bug: the code promises a cruise phase the distance has no room for, and every short move overshoots.
A step command is hard on a machine
Here is the move with no profile at all: full speed until the robot has gone 60 cm, then stop.
The program
from bugbot import *
connect()
DISTANCE = 60.0
start = position()[1]
while position()[1] - start < DISTANCE:
drive(100, 0, 0)
plot("speed", flow()[1])
plot("position", position()[1] - start)
wait(0.1)
stop()
for tick in range(15): # watch it coast
plot("speed", flow()[1])
plot("position", position()[1] - start)
wait(0.1)
print("asked for", DISTANCE, "cm, ended at", round(position()[1] - start, 1))
Two things go wrong at the two corners. At the start the command goes from nothing to everything in one tick. The only thing keeping the speed from jumping is the drive's own lag of about a quarter of a second, so the robot sets off at roughly 20 / 0.25 = 80 cm/s/s. On a small robot on a mat that is survivable. On anything with a load on top, it is spilt coffee, slipping wheels and a current spike through the battery.
At the end it overshoots by 5.4 cm. The loop only looks at the position every 0.1 s, so the robot is already 1.4 cm past the mark when the program notices, and then it coasts another 4.0 cm while the speed dies away. Neither of those is a tuning problem. They are what you get for asking a machine with mass and lag to change speed instantly.
Worth being blunt about one thing: this robot's drive has no acceleration limit in it. Give it a step and it does its best straight away. The limit exists only because your code imposes it, which is true of most small robots and of many big machines too.
The same move on a trapezoidal profile
Now the program works out the profile first, and then sends the speed the profile asks for at each tick. Nothing here looks at where the robot actually is: the command is 5 × v, because 100 percent is 20 cm/s, so each cm/s is 5 percent.
The program
from bugbot import *
connect()
DISTANCE, V, A = 60.0, 12.0, 8.0 # cm, cm/s, cm/s/s
DT = 0.1
t_acc = V / A # 1.5 s to reach cruise
d_acc = V * V / (2 * A) # 9 cm used by each ramp
t_flat = (DISTANCE - 2 * d_acc) / V # 3.5 s of cruise
total = 2 * t_acc + t_flat
print("ramp", t_acc, "s and", d_acc, "cm, cruise", t_flat, "s, total", total, "s")
def speed_at(t):
if t < t_acc:
return A * t # speeding up
if t < t_acc + t_flat:
return V # cruising
if t < total:
return V - A * (t - t_acc - t_flat) # slowing down
return 0.0
start = position()[1]
t = 0.0
while t < total + 1.0:
v = speed_at(t)
drive(5 * v, 0, 0) # 100 % is 20 cm/s, so 5 % per cm/s
plot("speed", flow()[1])
plot("position", position()[1] - start)
wait(DT)
t = t + DT
stop()
print("asked for", DISTANCE, "cm, ended at", round(position()[1] - start, 1))
The speed line now has the shape of the plan. It leaves in a ramp instead of a jump, and it arrives at zero instead of being cut off, so there is nothing left to coast. The position line is a smooth S: shallow at both ends, straight in the middle.
It stops 6.3 cm short, and every centimetre of that is explainable. The motors do nothing under 15 percent, which is 3 cm/s, so nothing happens for the first 0.4 s of the ramp and again at the end of it, and the plan walks off without the robot for about 1.1 cm in total. The rest is the model: the command assumes 20 cm/s at full scale and this robot gives 18.2, 9 percent less, so 0.91 of the remaining 58.9 cm is 53.6. The robot finished at 53.7.
An open loop profile is always going to end up somewhere near, not exactly there. The fix is to measure the robot's real gain, and to close a loop around the profile so that whatever the model gets wrong is corrected as the move runs. That is feedforward and feedback, and the same move done that way finishes within half a centimetre.
Jerk, and the s-curve
The trapezoid is time optimal: nothing that respects the same two limits gets there sooner. But look at its acceleration. It is zero, then 8, then zero, then minus 8, and it changes between them instantly. The rate of change of acceleration is called jerk, and a trapezoid has infinite jerk at four corners.
Infinite jerk rings anything springy in the machine: the frame, the mounting, the arm you bolted on, the load. The wobble starts exactly at the corner of the profile and carries on until the structure settles.
The fix is to limit the jerk too, which gives an s-curve: the acceleration itself ramps up and down, so the speed line's corners become curves. There are several ways to build one. The shortest is to average the trapezoid over a window of time, because the average of a corner is a curve, and averaging does not change the area.
The program
from bugbot import *
connect()
DISTANCE, V, A = 60.0, 12.0, 8.0
DT, TJ = 0.1, 0.4 # the tick, and how long a corner takes
t_acc = V / A
d_acc = V * V / (2 * A)
t_flat = (DISTANCE - 2 * d_acc) / V
total = 2 * t_acc + t_flat
def trapezoid(t):
if t < 0:
return 0.0
if t < t_acc:
return A * t
if t < t_acc + t_flat:
return V
if t < total:
return V - A * (t - t_acc - t_flat)
return 0.0
def s_curve(t):
# the average of the trapezoid over the last TJ seconds: every
# corner becomes a curve TJ long, and the area is unchanged
return sum(trapezoid(t - TJ * i / 4) for i in range(5)) / 5
start = position()[1]
t, area = 0.0, 0.0
while t < total + TJ + 0.6:
v = s_curve(t)
drive(5 * v, 0, 0)
plot("trapezoid", trapezoid(t))
plot("s-curve", v)
plot("speed", flow()[1])
area = area + v * DT
wait(DT)
t = t + DT
stop()
print("the s-curve covers", round(area, 1), "cm in", round(total + TJ, 1), "s")
print("ended at", round(position()[1] - start, 1))
The two planned lines are drawn on the chart so you can see the difference the smoothing makes, with the robot's own speed underneath them. The corners are gone, the area is still exactly 60.0 cm, and the move takes 6.9 s instead of 6.5: the whole cost of the smoothing is the length of one corner. The peak acceleration is unchanged at 8 cm/s/s, and the jerk is now 8 / 0.4 = 20 cm/s/s/s instead of infinite.
For a 7 cm robot on a mat this is over-engineering, and the chart's measured line is too noisy to show the difference. Industrial motion controllers do it by default, because the first time a robot arm rings like a bell at the end of every move, jerk is the answer.
Choosing v and a
vcomes from the machine, minus a margin. If the profile cruises at 100 percent of what the drive can do, a controller wrapped around it has nothing left to correct with. The profile on this page cruises at 12 of the robot's 18 cm/s.acomes from traction, from whatever is being carried, and from how much current you are willing to draw. One second to reach cruise speed is a reasonable place to start, and here that is 12 cm/s/s. The demo uses 8, which is gentler.- Check the plan before you drive it. Peak speed under the limit, peak acceleration under the limit, area equal to the distance.
Where this is taught
- Velocity profiles is the full lesson: the trapezoid, the triangle, jerk, and what a step command asks of the drive.
- A path and a trajectory is the idea underneath: a path is a shape, a trajectory is that shape with a clock attached, and a profile is the clock.
- Controlling speed is the GCSE way in, where the speed comes from the distance still to go.
- Measuring your robot and A command is a request give you the top speed, the dead band and the lag that the profile has to live inside.
- Feedforward and feedback closes the loop around the profile, and the feedforward guide takes this same move down to half a centimetre.
- How far behind measures the centimetres between the plan and the robot, which is what a profile is finally judged on.
Questions
What is a trapezoidal motion profile?
A plan for speed over time with three phases: a straight ramp up at a fixed acceleration, a flat cruise at a fixed speed, and a straight ramp down. Drawn against time it makes a trapezium, and the area under it is the distance the move covers.
How do you calculate a trapezoidal profile?
From the distance d, the speed limit v and the acceleration limit a. Each ramp takes t_acc = v / a and covers d_acc = v² / (2a). If 2 × d_acc is less than d, the cruise phase lasts (d − 2 × d_acc) / v and the move takes 2 × t_acc plus that. If it is not, there is no cruise phase and the profile is a triangle.
What happens when the move is too short to reach cruise speed?
The profile becomes a triangle: accelerate to the middle, then decelerate. The peak speed is √(a × d) and it happens exactly half way. Any code that assumes a cruise phase will overshoot on short moves.
What is the difference between a trapezoidal and an s-curve profile?
The trapezoid switches its acceleration on and off instantly, so the jerk is infinite at four corners. An s-curve ramps the acceleration up and down instead, which rounds the corners of the speed line. The s-curve is smoother and takes slightly longer to cover the same distance.
What is jerk in motion control?
The rate of change of acceleration. High jerk excites anything springy in a machine, so the load wobbles and the structure rings after the move. Limiting jerk is what turns a trapezoid into an s-curve.
Why not just command full speed and stop when you arrive?
Because the machine cannot change speed instantly, and because your loop only looks every so often. On this page that pairing puts the robot 5.4 cm past the mark: 1.4 cm because the loop noticed late, and 4.0 cm of coasting after the motors stopped. A step command also means a current spike, and slipping wheels on a surface with real friction.
Does a motion profile need feedback?
Not to make the plan, but yes to arrive accurately. Driven open loop, the profile on this page finishes 6.3 cm short because of the motors' dead band and a model that was 9 percent out. Feeding the profile's speed forward and putting a feedback loop around the position cuts that to half a centimetre.
Where are motion profiles used?
Anywhere a machine moves a load to a place and stops: CNC machines and 3D printers, pick and place machines, lifts, camera cranes, warehouse robots, and the joints of industrial arms. Any motion controller you buy will have a trapezoidal profile and usually an s-curve built in.
Learn it step by step
These lessons build the same ideas one at a time, each with tasks the simulator marks.
- U10.2 Velocity profiles Following a trajectory, University
- U10.1 A path and a trajectory Following a trajectory, University
- U10.6 How far behind Following a trajectory, University
- U10.5 Feedforward and feedback Following a trajectory, University
- 3.4 Controlling speed Control, Robot club
- U1.2 A command is a request The robot as a system, University
- U1.6 Measuring your robot The robot as a system, University