Expand description
Online trajectory generation: a smooth 1 kHz command from a stream of stepped targets.
Otg turns a target position that may change at any time – every cycle, in bursts, or
not for seconds – into a per-cycle position command whose velocity, acceleration and jerk
never exceed the limits it was built with, whose acceleration is continuous (the trajectory
is C2), and which reaches the target and then stays there exactly. It is the causal
alternative to a spline, which needs future knots a low-rate commander cannot supply, and to
a low-pass filter, whose peak speed grows with the size of the step. MultiOtg runs one
per axis, optionally synchronised; CartesianOtg is the three-axis one. No allocation.
§Algorithm
Every call to Otg::step re-plans from the current state (p, v, a) to (target, 0, 0)
and then follows that plan for one cycle. The plan is the classical seven-segment,
jerk-limited profile: a time-optimal velocity transfer from (v, a) to a peak velocity
v_p with zero acceleration (jerk +j_max up to a peak acceleration, hold it, jerk
-j_max back to zero – three segments with closed-form durations), a cruise at v_p, and
the mirror transfer from v_p to rest. The only free parameter is v_p: it is ±v_max
with a cruise when the target is far enough, and otherwise a root of
f(v_p) = target - p, where f is the displacement of the two transfers. f is
increasing in v_p except for one hump next to v_rd = v + a|a| / (2 j_max), the velocity
reached by ramping the acceleration straight to zero (an unsaturated reversal there costs
2 sqrt(d / j_max) of extra time at about v_rd for a change d of peak velocity), so
the domain is split at v_rd and v_rd ± a_max² / j_max, every piece whose ends bracket
the target is bisected, and the shortest plan found wins. A target closer than the braking
distance gives a root of the opposite sign: the profile passes the target, stops and comes
back, all within the limits and without a jerk spike. This is the profile structure of
Haschke, Weitnauer and Ritter, On-line planning of time-optimal, jerk-limited
trajectories (IROS 2008), evaluated one cycle at a time the way Ruckig (Berscheid and
Kröger, RSS 2021) does, whose four jerk patterns are the pieces above. The acceleration
along the plan is piecewise linear with slope ±j_max or 0, so the trajectory is C2 and a
finite difference of the acceleration over any cycle length, including a 2 ms cycle after a
lost packet, stays within j_max; the velocity transfer keeps v_rd within ±v_max,
which is what makes the velocity bound hold through the transient of every re-plan.
The target velocity is always zero: the generator is for positional targets from a planner,
a vision loop or a hand, not for velocity tracking. The synchronisation in MultiOtg
stretches the faster axes to the slowest one’s duration by lowering their peak velocity (a
second bisection, on the scale of v_p); an axis whose profile has no peak to lower (one
that is exactly braking to its target) keeps its minimum duration.
§In a control loop
Three rules, learnt from a run on a real FER in which the first version of the bridge in
examples/nonrealtime_commander.rs was clamped by the rate limiter behind it and then
orbited at the velocity cap for twenty seconds. The limits are per axis: two axes at
full acceleration have a vector norm sqrt 2 above it, so a budget that is a norm (which is
what limit_rate_cartesian_pose bounds) needs OtgLimits::per_axis_for_norm. Step one
nominal cycle per command (DELTA_T), not the measured period: the robot and the rate
limiter check every packet against a 1 ms budget, so a 2 ms step after a lost packet is a
doubled velocity to them. And re-anchor on the robot’s echo of the position every
cycle with Otg::set_position (O_T_EE_c; not its twist, see Otg::set_state), so
that if anything behind the generator does alter a command, the next plan starts from
what was actually sent; a rate limiter that tracks a pose it has fallen behind has no
braking logic and never catches up.
use franka::otg::{Otg, OtgLimits};
let limits = OtgLimits { max_velocity: 0.3, max_acceleration: 0.5, max_jerk: 20.0 };
let mut otg = Otg::new(0.0, limits).unwrap();
otg.set_target(0.05).unwrap();
let mut t = 0.0f64;
while otg.position() != 0.05 {
otg.step(0.001);
t += 0.001;
}
assert!((t - 0.658).abs() < 0.01, "a 5 cm S-curve under these limits takes 0.658 s");Structs§
- Multi
Otg NOtgs with the same limits, stepped together, optionally synchronised so that all axes arrive at their targets at the same time (the faster axes are slowed down).- Otg
- A single-axis online trajectory generator; see the module documentation.
- OtgLimits
- The per-axis limits of an
Otg: all three must be finite and positive.
Type Aliases§
- Cartesian
Otg - A three-axis
MultiOtgfor a Cartesian position.