Expand description
Target control: a 1 kHz loop on its own thread that follows targets a low-rate commander sets whenever it likes.
Most programs that want to move a Franka are not 1 kHz programs: a planner, a vision
loop, a script over a socket, a hand on a keyboard. They produce targets – at 10 Hz,
in bursts, with pauses – each a step the robot must never see as a step. This is the
fourth way to control the robot, next to the callbacks, ActiveControl and the
read-only stream: Robot::start_cartesian_target_control and
Robot::start_joint_target_control spawn a named thread that runs the crate’s own
control loop and hand back a handle whose set_* can be called from any thread, at any
rate, with any target. The handle’s stop brings the
command to rest on the last target, finishes the motion and returns the loop’s result.
§Two backends
The Backend of the options decides what the generator’s output becomes. The default,
Backend::Impedance, sends torques: the hybrid joint and Cartesian impedance law of
ImpedanceGains (Kp = J^T Kx J + Kq, Kd = J^T Kxd J + Kqd, the damping on the
measured velocity, or with ImpedanceOptions::velocity_feedforward on the velocity error
dq_goal - dq, plus the Coriolis term, clamped to the torque limits) tracks a joint target
q_goal – the generator’s own output on the joint interface, the solution of a
differential inverse kinematics following the generator’s pose one cycle at a time on the
Cartesian one (IkOptions) – through Robot::control_torques with the crate’s
low-pass filter (ImpedanceOptions::cutoff_frequency) and torque rate limiter. The goal
never moves a joint faster than ImpedanceOptions::joint_velocity_fraction of the arm’s limit
(max_joint_velocity, which narrows toward the position limits on the FR3): a faster step is
scaled down as a whole and the generator re-anchored on what went out. A joint measured faster
than the cap, or than FADE_BAND of its limit under the barrier’s onset if that is lower,
keeps less and less of the law’s torque along its motion, none from
ImpedanceOptions::velocity_barrier_fraction of its limit, where it meets a damping of
VELOCITY_BARRIER_GAIN on the excess, added before the clamp. Toward a joint
position limit the goal brakes to stop ImpedanceOptions::joint_position_margin inside it; a
joint measured inside the margin keeps less of the law’s torque toward the limit over
POSITION_FADE_BAND, then meets a spring (POSITION_BARRIER_STIFFNESS). There is no echo of
a torque command, so the loop anchors on the measured configuration in its first
cycle (the Cartesian interface on the model’s pose of it, where the IK’s residual is zero), and
then, every cycle, on the measured state pulled toward the previous desired by at most the
Leash: exactly the previous desired while the arm follows, so the generator runs from its
own last output and its limits are the whole budget; a bounded distance ahead of an arm that is
held back, so the spring force is bounded by the stiffness times the leash and the generator
resumes from where the arm is once it is let go.
Backend::RobotController instead streams the generator’s output as a pose or joint-position
command to the robot’s own impedance controller (controller_mode); the rest of this page
describes that path where the two differ.
§What the loop does every cycle
The three rules of the otg module: the generator’s
limits are per axis (a Cartesian budget is a norm, so it gets
OtgLimits::per_axis_for_norm(3)), it steps one nominal cycle per command
(DELTA_T) whatever the measured period, and it is
re-anchored on the last command – the robot’s echo of it (O_T_EE_c, q_d) with
Backend::RobotController, the leashed previous output with Backend::Impedance –
with set_position before every re-plan. With Backend::RobotController the
rate limiter under the same budget then runs as the backstop that must never bind
(limit_rate_cartesian_pose, limit_rate_joint_positions), and the loop’s own libfranka
limiter stays on behind it; the observer is told by how much the backstop moved the
command. The first setpoint of the motion is always the anchor itself
– on FCI v10 the first command is its own filter reference
and would otherwise go out as a jump – and the start returns only once that cycle has
run, so target and
state are valid from the first call.
The Cartesian target is a pose: three more axes of the same synchronised generator run
on the base-frame rotation vector of the orientation error, log(R_target * R_echo^T),
re-anchored at zero every cycle and composed back as exp(step) * R_echo, under a
rotational norm budget of their own; translation and rotation arrive together.
Two guards. If the measured pose strays more than max_deviation (m) or
max_angular_deviation (rad) from the start, the target freezes where the command is,
the generator brings it to rest, and the loop ends with FrankaError::Control. And a
stop never finishes on a moving command: once every axis of the generator has landed
(within Settle::tolerance of the target, slower than REST_VELOCITY, accelerating
less than REST_ACCELERATION – the orientation included, in rad) the loop stops
stepping it and holds the last command – with Backend::RobotController the robot’s
echo of it, continuous with what the robot has by construction, whatever the backstop took
off that command, sent bit for bit and past the backstop; with Backend::Impedance the
landed desired pose or joint goal, whose torques are those of rest – for
Settle::cycles cycles, then
sets motion_finished on one more of it: the sequence a robot accepts as “finished at
rest”. If the generator has not landed within STOP_TIMEOUT_CYCLES the same hold starts
from wherever the command is. That hold settles the generator, not the arm: in torque mode
an arm still closing its lag would be handed to the robot’s controller short of the goal,
so Backend::Impedance finishes only once every joint moves slower than
REST_JOINT_VELOCITY, or after STOP_TIMEOUT_CYCLES more cycles, the law kept on the
held goal meanwhile.
§Threads
The commander side is a single-writer seqlock (TargetSlot) the loop polls without
blocking; set_* serialises its callers with a mutex on the user side only. The latest
RobotState is published with Mutex::try_lock from the realtime side and read with
lock on the user side. Nothing allocates on the realtime thread after the start. The
optional observer is the exception to “nothing of yours runs at 1 kHz”: it is called on
the realtime thread every cycle with the state and what was sent, and must not allocate
or block – copying into a preallocated ring, as franka_rerun::Recorder::push does, is
what it is for.
The loop thread is raised to SCHED_FIFO like Robot::new raises its caller: to the
highest priority, or to realtime_priority when the options name one; when they name a
cpu, it also pins itself to that core, whether or not the raise succeeded. A failure of
either is fatal under RealtimeConfig::Enforce and ignored under
RealtimeConfig::Ignore.
Only one control or read operation may run on a Robot at a time, so while a target
control runs, robot.read() and the other loops fail with
FrankaError::InvalidOperation, exactly as with a callback loop on another thread;
robot.stop() preempts it, and the handle’s stop then returns the preemption as a
FrankaError::Control.
use std::sync::Arc;
use franka::{RealtimeConfig, Robot, TargetControlOptions};
let robot = Arc::new(Robot::new("172.16.0.2", RealtimeConfig::Enforce)?);
let control = robot.start_cartesian_target_control(TargetControlOptions::default())?;
let start = control.target();
for step in 1..=5 {
// A planner, a socket, a keyboard: any thread, any rate, any target.
control.set_position([start[0] + 0.01 * f64::from(step), start[1], start[2]])?;
std::thread::sleep(std::time::Duration::from_millis(300));
}
// An orientation too: a unit quaternion in [x, y, z, w] order, or a pose as O_T_EE.
let yaw = std::f64::consts::FRAC_PI_8;
let orientation = [0.0, 0.0, (yaw / 2.0).sin(), (yaw / 2.0).cos()];
control.set_target(start, orientation)?;
control.stop()?; // settles on the last target, finishes the motion, joins the threadStructs§
- Cartesian
Sent - What one cycle sent, for the observer.
- Cartesian
Target Control - The handle of a running Cartesian target control; see the module documentation.
The target is a pose; orientations are unit quaternions in
[x, y, z, w]order (the scalar part last) or the rotation block of a column-major pose as inO_T_EE. - Field
Bound - One scalar of
LiveTuning: what it is called, what it means, and how far it may go. - IkOptions
- Options of the Cartesian backend’s differential inverse kinematics: every cycle, from the
previous joint goal, up to
iterationsdamped-least-squares steps toward the pose inside the cycle’s joint box, with a posture bias through the nullspace. - Impedance
Gains - The stiffness and damping of the law, all finite and non-negative.
- Impedance
Options - Options of
Backend::Impedance;cartesianandjointare the documented starting points and thewith_*methods change one field each. - Joint
Sent - What one cycle sent, for the observer.
- Joint
Target Control - The handle of a running joint target control; see the module documentation.
- Joint
Target Control Options - Options of
Robot::start_joint_target_control;Defaultis the documented starting point and thewith_*methods change one field each. - Leash
- How far the desired state may run ahead of the measured one. There is no echo of a torque
command to re-anchor the generator on, so every cycle it is anchored on the measured state
pulled toward the previous desired by at most this: while the arm follows, that is exactly
the previous desired and nothing changes; held back (a hand, an obstacle, an unreachable
target) the desired stays within the leash, the force on the arm is bounded by the
stiffness times the leash, and on release the generator resumes from where the arm is
under its budget. The leash bounds the force, not the release: the arm closes the gap it
was held back by under the spring alone, which can outrun the budget. Once a stop holds,
the leash pulls toward the held state instead, so an arm moved during the hold meets the
same bound. The observer reports what the leash took off as
leash_alteration. - Live
Tuning - The targets a running session will accept. These are targets, not the values in force: the
loop keeps its own current values and moves them here under each field’s
policy. - Settle
- How a stop ends: once the generator is within
toleranceof the target on every axis and at rest (REST_VELOCITY,REST_ACCELERATION), the echo of the last command is held forcyclescycles andmotion_finishedset on the next one. - Slew
Gains - How far one cycle moves each word of a
LiveTuningtoward its target: the fraction of the remaining gap a slewed word closes, and the cycle time a gated word’s descent is measured in. - Target
Control Options - Options of
Robot::start_cartesian_target_control;Defaultis the documented starting point and thewith_*methods change one field each. - Target
Slot - The latest target,
Nf64s, as a single-writer seqlock: the sequence number is odd while a write is in progress and changes with every write, so a reader that sees the same even number before and after loading the values has a consistent set. Reads never block and never spin unboundedly, which is what lets the realtime loop poll it every cycle; writes are wait-free too, but there must be only one writer at a time, which the handles serialise with a mutex on the user side. - Tuning
Update - A partial change to a
LiveTuning: every field leftNonekeeps the value it has.
Enums§
- Backend
- What tracks the target: the robot’s controller or the crate’s torques.
- Tuning
Danger - How much care moving a
FieldBoundwants. Advice for the operator, enforced by whoever publishes the table – this crate clamps, and has no opinion beyond the clamp. - Tuning
Policy - How the loop is to move an accepted value into effect. Carried here rather than in the loop so that a published schema can explain a slider’s behaviour without a second table.
Constants§
- DEFAULT_
LIMIT_ FRACTION - The fraction of the robot’s joint limits
JointTargetControlOptions::defaultbudgets. - DERIVED_
GAINS_ MESSAGE - The message of the
FrankaError::InvalidOperationa live-tuning call returns on a Cartesian impedance session whose gainsLiveTuning::gainscannot rebuild from one Cartesian stiffness –ImpedanceGains::DROIDis the preset this is true of. Such a session keeps the gains it was started with and is given no tuning slot, rather than having the six derived dampings rewritten in one cycle by the first change to any other field. - DEVIATION_
MESSAGE - The message of the
FrankaError::Controlthe loop ends with after the deviation guard froze the target. - ENDED_
MESSAGE - The message of the
FrankaError::InvalidOperationaset_*returns once the loop has ended, for whatever reason;stop()has the reason. - FADE_
BAND - The narrowest band, as a fraction of a joint’s velocity limit, over which the law’s push along the joint’s motion fades out under the barrier’s onset: the fade starts at the cap or this far under the onset, whichever is lower, and never below rest.
- JOINT_
LIMIT_ INSET - How far, rad, inside the negotiated version’s joint position limits a joint target and an
impedance posture must lie:
JointTargetControl::set_jointsand bothstarts refuse a configuration outside.Backend::Impedancerefuses one insideImpedanceOptions::joint_position_marginwhere that is larger. - MAX_
POSTURE_ RATE - The most, rad/s, the posture bias moves any joint: the pull
nullspace_gain × distancetoward a far posture is scaled down as a whole to this rate, so a posture 2 rad away is approached at 0.5 rad/s, not 2. - MIN_
FEEDFORWARD_ CUTOFF - The lowest cutoff, Hz,
ImpedanceOptions::velocity_feedforward_cutoffaccepts. A first-order low-pass at 1 Hz still settles inside a second; below it the filter is a near-integrator holding a velocity the goal no longer has for longer than a session runs. - MIN_
JOINT_ DAMPING_ RATIO - The least
joint_damping[i] / sqrt(joint_stiffness[i])a running session is left at. The shipped presets sit between 0.32 (ImpedanceGains::CARTESIAN’s wrist) and 2.12 (JOINT’s), so this is under every one of them with room to spare: it forbids the undamped corner and nothing else. Like every number inLiveTuning::BOUNDSit is a derivation on paper until it is swept. - NO_
TUNING_ MESSAGE - The message of the
FrankaError::InvalidOperationa live-tuning call returns on a session whose interface or backend has none:LiveTuningdescribes the impedance law of the Cartesian interface, and nothing else runs one. - ORTHONORMAL_
TOLERANCE - How far from orthonormal (the largest entry of
|R^T R - I|, or|det R - 1|) the rotation block of a target pose may be: within this it is re-orthonormalised, beyond it refused. - POSITION_
BARRIER_ STIFFNESS - 1/rad: the position barrier’s stiffness as a multiple of the joint’s torque clamp,
K_i = POSITION_BARRIER_STIFFNESS × torque_limits_i: 1075 Nm/rad at the 86 Nm clamp, 144 at 11.5. - POSITION_
FADE_ BAND - rad: the band inside the margin over which the law’s torque toward a joint limit fades out,
from full at the margin to none at the position barrier’s onset,
margin − POSITION_FADE_BANDfrom the limit. Inside that onset a spring pushes the joint out. - RATED_
TORQUES - The FR3’s and FER’s rated joint torques, Nm, the most
torque_limitsmay allow. - REST_
ACCELERATION - … and accelerating less than this (m/s^2, rad/s^2); the micro-profiles above peak at about 0.01.
- REST_
JOINT_ VELOCITY - An arm whose every joint moves slower than this, rad/s, is at rest: what a stop in torque
mode waits for before
motion_finished, the hold having settled only the generator. - REST_
VELOCITY - A generator within
Settle::toleranceof its target moving slower than this, per axis, counts as landed (m/s, rad/s). The hold then freezes a velocity step of at most this in one cycle, a jerk of 100 per second cubed, which the joint side of a Cartesian command amplifies threefold (1 mm/s freezes as about 3840 rad/s^3 on joint 2 in the simulator, over its 3750). Not smaller: re-anchoring on a float32 echo (FCI v10) keeps a landed generator in micro-profiles of a few 1e-8 that peak at about 2e-5 per second. - SLEW_
TAU - The slew time constant of every slewed field, s.
- STOP_
TIMEOUT_ CYCLES - Cycles a stop waits for the generator to land before holding and finishing from wherever the command is: five seconds.
- UNIT_
QUATERNION_ TOLERANCE - How far from unit length a target quaternion may be before it is refused; within this it is normalised.
- VELOCITY_
BARRIER_ GAIN - Nm per rad/s of the velocity barrier: the damping a joint meets beyond
ImpedanceOptions::velocity_barrier_fractionof its limit. Stable at 1 kHz behind the 100 Hz low-pass while the law’s own joint damping plus this stays under the joint’s effective inertia per millisecond; see The impedance backend in the book.
Functions§
- impedance_
torques - The torque backend’s law, offline:
- joint_
position_ limits - The joint position limits (lower, upper), rad, of the arm speaking
version: the FER’s or the FR3’sJOINT_POSITION_LIMITS. - max_
joint_ velocity - The joint velocity limits, rad/s, of the arm speaking
versionaway from its joint position limits: the FER’sMAX_JOINT_VELOCITYor the FR3’s caps. The FR3’s limit narrows toward the position limits, which the impedance backend follows; the FER’s is flat. Its cap and barrier (ImpedanceOptions::joint_velocity_fraction,ImpedanceOptions::velocity_barrier_fraction) andJointTargetControlOptions::scaled_limitsare fractions of these.
Type Aliases§
- Cartesian
Observer - The observer’s type: called every cycle on the realtime thread, so it must not allocate or block.
- Joint
Observer - The observer’s type: called every cycle on the realtime thread, so it must not allocate or block.