Velocity profiles

The trapezoid, the triangle when there is no room to reach cruise, and what jerk costs you.

U10.2Following a trajectoryUniversity30 min

Do this lesson in the simulator

Constant speed is a time law with a corner in it: zero one instant and 12 cm/s the next. That demands infinite acceleration, which no machine has, so the machine ignores you and does something else. A velocity profile is the time law written so that the machine can actually follow it.

The trapezoid

Three phases: accelerate at a until you reach v, hold v, decelerate at a until you stop.

t_acc  = v / a                  how long the ramp takes
d_acc  = v * v / (2 * a)        how far it covers

If 2 * d_acc fits inside the distance, there is a flat top in the middle, and it lasts (d - 2 * d_acc) / v. If it does not fit, there is no cruise phase at all: the profile is a triangle, the peak speed is sqrt(a * d) and it is reached exactly half way.

A trapezoidal speed profile for an 80 cm move and a triangular one for a 20 cm move0246051015time, sspeed, cm/sspeed upcruise 3.83 sslow downarea = 80 cm80 cm: a trapezoid02time, slimit 15peak 14.120 cm20 cm: a triangle
Both at 10 cm/s/s. The 80 cm move has room for a flat top at 15 cm/s. The 20 cm move does not: it turns round half way at 14.1 cm/s, which is the square root of a times d, and never reaches the limit. In each, the shaded area is the distance.

Forgetting the triangle case is the classic bug. It shows up as a short move that overshoots every time, because the profile promised a cruise phase that the distance had no room for.

The trapezoid is time optimal for a move with a speed limit and an acceleration limit. Nothing that respects both bounds gets there sooner, which is a satisfying thing to be able to say about something this simple.

from bugbot import *
connect()

D, A, V, DT = 80.0, 10.0, 15.0, 0.05
t_acc = V / A
d_acc = 0.5 * A * t_acc * t_acc
t_flat = (D - 2 * d_acc) / V
duration = 2 * t_acc + t_flat

s = 0.0
n = int(duration / DT)
for i in range(n):
    t = (i + 0.5) * duration / n
    if t < t_acc:
        v = A * t
    elif t < t_acc + t_flat:
        v = V
    else:
        v = max(0.0, V - A * (t - t_acc - t_flat))
    s += v * duration / n
    plot("v", v)
    plot("s", s)
    if i % 2 == 1:        # the chart's time axis is the robot's clock, so let it tick
        wait(2 * DT)
print("duration", round(duration, 2), "s, area under v =", round(s, 1), "cm")

Run this in the simulator

The area under the speed line is the distance. If it does not come out at the distance you asked for, the profile is wrong, and that check costs one accumulator.

The BugBot has no acceleration limit

This is worth being blunt about. The drive has no acceleration limit in it. Give it a step command and it does its best immediately, and the only thing that stops the velocity jumping is the first order lag, time constant about 0.25 s. A step to 20 cm/s is therefore an initial acceleration of roughly 20 / 0.25 = 80 cm/s/s.

Speed after a step command to 20 cm/s, against a ramp at the lesson's acceleration limit00.250.511.5201020time, sspeed, cm/sthe step:starts at 80 cm/s/sa profile ramping at 10 cm/s/s
A step to 20 cm/s (red) is smoothed only by the 0.25 s lag, so it sets off at 80 cm/s/s, eight times the 10 cm/s/s the profile (green) asks for. The dashed line is that first slope. The limit only exists if your code imposes it.

So on this robot an acceleration limit is something you impose, not something the hardware imposes on you. Why bother?

  • Traction. On a real surface, demanding more acceleration than friction can supply means slipping, and slip is the one thing dead reckoning cannot see.
  • The payload. Anything carried, and anything on top, feels whatever you demand.
  • Current. A step demand is a current spike, and on a small battery that is a voltage sag and sometimes a brownout reset.
  • Predictability. A profile the machine can follow is a profile whose error you can reason about. A profile it cannot follow leaves an error you can only measure.

Jerk

Jerk is the rate of change of acceleration. A trapezoid has infinite jerk at each corner: the acceleration goes from 0 to a in no time at all.

Infinite jerk excites everything springy in the machine: the chassis, the mounting, the payload, the arm you bolted on last week. It shows up as a wobble that starts exactly at the corner of the profile and rings for as long as the structure takes to settle.

The fix is an S-curve: limit the jerk as well, so the acceleration itself ramps. The profile gains two more phases at each end, takes slightly longer, and is dramatically smoother. Industrial motion controllers do this by default, and for good reason. For a 7 cm robot on a mat it is over-engineering, but the vocabulary is worth having, because the first time a robot arm rings like a bell at the end of every move, jerk is the answer.

Acceleration against time for the trapezoid and for an S-curve-10010timeacceleration, cm/s/sjumps: infinite jerkjumps againred: trapezoidgreen dashed: S-curve
The trapezoid's acceleration (red) switches on and off instantly at four corners, which is what makes a springy machine ring. The S-curve (green, dashed) ramps the acceleration up and down instead, and takes slightly longer to cover the same distance.

Choosing the numbers

  • v comes from the machine, reduced by whatever margin the controller needs to have authority left. Cruising at 100 percent leaves nothing to correct with.
  • a comes from traction, payload and taste. Start at v / 1 (one second to cruise) and tighten it.
  • Check the profile against the machine before driving it: peak speed under the limit, peak acceleration under the limit, area equal to the distance.

Task: a trapezoidal profile

Build a trapezoidal profile that covers 80 cm with an acceleration limit of 10 cm/s/s and a cruise speed of 15 cm/s, sampled every 0.05 s. Plot v and s at every sample, with a wait(0.1) after every second one so the chart has a time axis (the simulator runs in 0.02 s steps, so a wait(0.05) would last 0.04 s), and print peak:, duration: and distance:, the last being the area under your own speed profile.

from bugbot import *
connect()

DISTANCE, A_MAX, V_MAX, DT = 80.0, 10.0, 15.0, 0.05

Challenges

  1. Ask for 8 cm with the same limits. Does your code produce a triangle, and is the peak sqrt(a * d)?
  2. Work out the peak acceleration a step command to 20 cm/s really produces, using the 0.25 s lag.
  3. Add a jerk limit by ramping the acceleration over 0.3 s at each corner. How much longer does the move take?