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.

Guidefree, runs in your browser

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.

A trapezoidal profile for the 60 cm move, with the distance each phase covers, and the triangle a 12 cm move gives instead0123456704812time, sspeed, cm/s9 cm42 cm9 cmupcruise at 12 cm/sdown60 cm: a trapezium, 6.5 s012time, slimit 12peak 9.812 cm12 cm: a triangle, 2.4 s
The 60 cm move has room for a flat top: 9 cm on each ramp and 42 cm of cruise, 6.5 s in all. A 12 cm move with the same limits has not: it turns round half way at 9.8 cm/s, the square root of a times d, and never reaches the speed limit. In both, the area is the distance.

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.

Full speed until the robot has gone 60 cm: it is 1.4 cm past before the loop notices, coasts 4.0 cm more, and ends 5.4 cm past the mark.
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))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

Speed and position measured for the same 60 cm move, driven flat out and driven by a trapezoidal profile0246805101520time, sspeed, cm/sflat outon the profile024680204060time, sposition, cmthe 60 cm mark+5.4 cm6.3 cm short
The two moves measured. Flat out, the speed jumps to about 19 cm/s and is cut off at the end, so the robot runs 5.4 cm past the mark. On the profile the speed ramps up and back down and the robot stops where the speed does, 6.3 cm short of the mark because the model behind the command is 9 percent optimistic.

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 speed ramps up over 1.5 s, holds 12 cm/s, and ramps down. No jump at either end, no overshoot, and it stops 6.3 cm short.
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))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

Averaging the trapezoid over the last 0.4 s rounds all four corners, keeps the area at exactly 60.0 cm, and makes the move 0.4 s longer.
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))
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

The trapezoid and the s-curve, drawn as speed and as acceleration against time012345670612speed, cm/s01234567-808time, sacceleration, cm/s/strapezoidcorners are instant:the jerk is infinites-curveeach corner is spreadover 0.4 s, so the jerkis 20 cm/s/s/ssame area: 60 cm6.9 s instead of 6.5 speak acceleration isstill 8 cm/s/s
The same move as a trapezoid and as an s-curve. In speed the difference is small: the corners are rounded. In acceleration it is not: the trapezoid jumps from 0 to 8 cm/s/s and back four times, while the s-curve climbs in five steps of the demo's average, spread over 0.4 s. The s-curve covers the same 60 cm and takes 6.9 s instead of 6.5.

Choosing v and a

  • v comes 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.
  • a comes 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

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.

  1. U10.2 Velocity profiles Following a trajectory, University
  2. U10.1 A path and a trajectory Following a trajectory, University
  3. U10.6 How far behind Following a trajectory, University
  4. U10.5 Feedforward and feedback Following a trajectory, University
  5. 3.4 Controlling speed Control, Robot club
  6. U1.2 A command is a request The robot as a system, University
  7. U1.6 Measuring your robot The robot as a system, University
Open the lessons