AdmittanceModel
AdmittanceModel turns a measured force into a motion setpoint.12 minute read
AdmittanceModel turns a measured force into a motion setpoint. It solves the
equation of motion of a virtual mass on a spring and damper, in real time, and
publishes the resulting position, velocity and acceleration for a position
controller to track. That is what makes a stiff machine feel compliant: you
push, the virtual mass moves, and the servo follows it.
Every physical coefficient is a lookup table, not a constant. Mass, damping and stiffness each vary with their own input, and two further tables scale mass and damping by position and velocity — so a machine whose apparent inertia should change across its workspace needs no gain scheduling elsewhere.
flowchart LR
i1(["measuredTorque — the external force"]) --> B["AdmittanceModel"]
i2(["actuatorFeedForwardTorque"]) --> B
i3(["actuatorVirtualMass — input to the mass table"]) --> B
i4(["inputPVA — jog and offset"]) --> B
i5(["referencePVA — integrator references"]) --> B
i6(["externalMassFactor"]) --> B
i7(["externalDampingFactor"]) --> B
i8(["connectSpringReferencePosition"]) --> B
i9(["disableDynamics"]) --> B
i10(["disable"]) --> B
B --> o1(["outputPVA — the motion setpoint"])
B --> o2(["outputTorque — spring plus damper force"])
B --> o3(["springForce"])
B --> o4(["damperForce"])
B --> o5(["massCoefficient — the effective mass"])
B --> o6(["accelerationTorque"])
B --> o7(["accelerationTorqueCorrection"])
B --> o8(["springReferencePosition"])
B --> o9(["enabled"])
The virtual system is $M\ddot{x} + D\dot{x} + Kx = F$. With the default stiffness of 0 it is first order: terminal velocity $F/D$, time constant $M/D$. With stiffness set, aim at $\omega_n = \sqrt{K/M}$ [rad/s] and $\zeta = D/(2\sqrt{KM})$ — target $\zeta$ near 1 to avoid a bouncy contact. Read the effective mass off
massCoefficient; it is the mass table’s output times the position and velocity scalings, not the number in the table.
A positive measuredTorque produces motion in the negative direction by
default. Set useNegativeCoefficients true to reverse that.
The spring produces no force until connectSpringReferencePosition is set
true. Until then the spring’s anchor follows the output, so there is nothing
to push against. If you configured stiffness and see no spring force, this is
why.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
measuredTorque |
N or N·m | unbounded | The external force the operator or environment applies. Non-numeric values are treated as zero. |
actuatorFeedForwardTorque |
N or N·m | unbounded | The actuator’s own modelled torque, added to the force target after actuatorFeedForwardGain. Non-numeric values are treated as zero. |
actuatorVirtualMass |
kg or kg·m² | unbounded | The input to the mass lookup table, not a mass. The table converts it into the mass the virtual system uses. |
inputPVA/position |
m or rad | unbounded | Added directly to outputPVA/position. An offset, not a setpoint. |
inputPVA/velocity |
m/s or rad/s | unbounded | Added directly to outputPVA/velocity — this is the jog path. It also trims the spring anchor while the anchor is connected. |
inputPVA/acceleration |
m/s² or rad/s² | unbounded | Feeds the mass-correction term only. |
referencePVA/position |
m or rad | unbounded | The position integrator’s reference. |
referencePVA/velocity |
m/s or rad/s | unbounded | The velocity integrator’s reference, and what the velocity is pinned to while the block is disabled. |
referencePVA/acceleration |
m/s² or rad/s² | - | Registered but never used. Ignore it. |
externalMassFactor |
- | 0.1 – 100 | Runtime scaling on the mass. The range is not enforced on this path — see Limits and errors. |
externalDampingFactor |
- | 0.1 – 100 | Runtime scaling on the damping. Not enforced, and a negative value is unsafe — see Limits and errors. |
connectSpringReferencePosition |
- | - | True freezes the spring’s anchor where the machine is, so the spring starts pulling back toward it. False re-anchors it to the current position every cycle, which means no spring force. The block clears this itself on a position reset. |
disableDynamics |
- | - | True pins the velocity to referencePVA/velocity and forces outputTorque to zero, while leaving the block enabled. Use it to hold the model rigidly at a commanded state. |
disable |
- | - | True does the same and also reads back on enabled. Use it for a runtime override from a supervisor; use enable for the configured intent. |
Outputs
| Path | Unit | Description |
|---|---|---|
outputPVA/position |
m or rad | The motion setpoint — the reason to use this block. Position is held while the block is disabled, not zeroed. |
outputPVA/velocity |
m/s or rad/s | Velocity setpoint, including anything added through inputPVA/velocity. |
outputPVA/acceleration |
m/s² or rad/s² | Acceleration setpoint, optionally smoothed. |
outputTorque |
N or N·m | Spring plus damper force. Chain this to the next model’s force input to build a coupled chain. Zero while disabled or while disableDynamics is set. |
springForce |
N or N·m | The spring’s contribution after its own limit, including any position-limit push-back. |
damperForce |
N or N·m | The damper’s contribution after its own limit. |
massCoefficient |
kg or kg·m² | The effective mass actually used — the table output times both scalings. Watch this, not the table. |
accelerationTorque |
N or N·m | The net accelerating force before limiting. |
accelerationTorqueCorrection |
N or N·m | What the noise canceller contributed, zero when it is off. |
springReferencePosition |
m or rad | Where the spring is currently anchored. |
enabled |
- | True when enable is true and disable is false. Note the path is enabled, not isEnabled. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
enable |
- | true | - | False pins the velocity to its reference and zeroes outputTorque. Position still holds. |
useNegativeCoefficients |
- | false | - | False negates the force, so a positive measuredTorque gives negative motion. True passes the sign through. The name reads backwards against its effect. |
measuredTorqueDeadZone |
N or N·m | 0.1 | 0 upward | Forces smaller than this produce no motion. Raise it to stop a noisy sensor creeping the machine. |
actuatorFeedForwardGain |
- | 1.0 | any | Scales actuatorFeedForwardTorque before it joins the force target. Not checked. |
massCorrection |
- | 0.0 | any | Scales an acceleration feedback term that partly cancels the virtual mass. Leave at 0 unless commissioning with support. Not checked. |
springTorqueLimit |
N or N·m | 10 | 0 upward | Caps springForce. Set it to what the machine may push back with. |
damperTorqueLimit |
N or N·m | 10 | 0 upward | Caps damperForce. |
anc/enable |
- | false | - | Switches on active noise cancellation, which projects a repeating disturbance forward and subtracts it. Leave off unless you have a periodic disturbance. |
anc/averageMaxSamples |
samples | 250 | 1 upward | Fast averaging window. Not checked. |
anc/slowAverageMaxSamples |
samples | 1000 | 100 – 2000 | Slow averaging window. Corrected silently if out of range. |
All ten are persistent and survive a restart.
Most of the configuration is below this block, in fifteen sub-trees. The
ones you will use: massLookup, dampingLookup and stiffnessLookup for the
three physical coefficients; positionCoefficientLookup and
velocityCoefficientLookup for their position and velocity scaling;
velocityIntegrator and positionIntegrator for the motion limits;
positionLimitLookup for push-back near a limit; velocityLimiterLookup for
the taper that slows the machine as it approaches one; torqueLimiter for the
accelerating force; and five low-pass filters — massLowPass,
accelerationLowPass, massLookupLowPass, dampingLookupLowPass and
stiffnessLookupLowPass — that keep table steps from stepping the
coefficients. See lookup.md, integrator.md
and low-pass-1.md.
velocityCoefficientLookup ships unconfigured, and an unconfigured table
returns zero, which is then floored at 0.01. That multiplies your mass by
0.01. Configure it — a single point returning 1.0 leaves the mass alone —
before you trust any mass number. See Limits and errors.
Setup
-
Configure
velocityCoefficientLookupfirst: one point,y= 1.0,numPoints= 1. Without this the effective mass is a fiftieth of what the mass table says. -
Configure
massLookupwith the mass or inertia you want the operator to feel. A single point is enough if it does not vary. -
Set
stiffnessLookupto zero and leaveconnectSpringReferencePositionfalse. Tune the mass and damper first; the spring comes later. -
Configure
dampingLookup. Start high — a heavily damped virtual system is safe to push on. -
Set
springTorqueLimitanddamperTorqueLimitto forces the machine may safely produce, and set the two integrators' output limits to the axis’s real travel and speed. -
Set
measuredTorqueDeadZoneabove your force sensor’s noise floor. Confirm the machine does not creep with nobody touching it. -
Set
enabletrue and checkenabledreads true. Push the machine and confirm it moves away from the push. If it moves into your hand, flipuseNegativeCoefficients.Step 7 is the first time the machine moves under operator force. Have the axis clear and a hand on the stop. A mass that is too light or damping that is too low will make it run away from a light touch.
-
Read
massCoefficientand confirm it is the mass you intended. It is the table output times both scalings, so it is the number that matters. -
Only now add the spring: configure
stiffnessLookup, then setconnectSpringReferencePositiontrue where you want the anchor.
Tuning
- Tune in this order — mass, damper, spring — and change one at a time. The three interact, and the spring is meaningless until the first two feel right.
- Set the mass by feel, then verify with
massCoefficient. Heavier feels more substantial and responds more slowly; lighter is quicker and easier to destabilise. - Set the damper from the terminal velocity you want: a steady push of $F$ settles at $F/D$. Read the terminal velocity off a trace and divide.
- Check the time constant: $M/D$, in seconds. That is how long the machine takes to reach 63% of its terminal velocity. Too short and the machine feels twitchy; too long and it feels sluggish.
- Add stiffness only when the machine should return to a position. Aim for $\zeta = D/(2\sqrt{KM})$ near 1 — below about 0.5 the contact will bounce.
- If friction or inertia varies across the workspace, use the position and velocity coefficient tables to raise mass and damping where the machine feels light. They can only raise, never lower — the position scaling is floored at 1 and the velocity scaling at 0.01.
- Use
velocityLimiterLookupto taper the speed as the machine nears a position limit, andpositionLimitLookupto push back. A hard clamp on the integrator alone arrives as a jolt. - Leave
massCorrectionat 0 and the noise canceller off unless you are commissioning with support. Both change the force balance in ways that are hard to read off a trace.
Read the time constant off any curve as the point it crosses 63% of its final value.
| Symptom | Cause | Action |
|---|---|---|
| The machine feels far lighter than the mass table says | velocityCoefficientLookup is unconfigured, so the mass is multiplied by 0.01 |
Configure it: one point, y = 1.0. Then read massCoefficient back |
| The machine moves into the push instead of away | The sign convention | Flip useNegativeCoefficients |
| The machine creeps with nobody touching it | measuredTorqueDeadZone below the sensor’s noise floor |
Raise it until the creep stops |
| The machine runs away from a light touch | Mass too low or damping too low | Raise dampingLookup first, then massLookup |
| Motion is sticky, then jumps | The dead zone is too large, or friction elsewhere is not compensated | Lower the dead zone; check the feedforward path’s friction model |
| No spring force at all, with stiffness configured | Expected: the anchor follows the output until you connect it | Set connectSpringReferencePosition true |
| The spring snapped the machine when engaged | The anchor was set while the machine was displaced | Engage it where you want the rest position to be |
| Contact bounces or oscillates | Damping too low for the stiffness | Raise dampingLookup until $\zeta$ is near 1, or lower the stiffness |
| The machine feels heavy in one part of the workspace only | Expected if positionCoefficientLookup is shaped that way |
Flatten that table, or accept it — it exists to stabilise light regions |
massCoefficient moved on its own |
Expected: the mass table is driven by actuatorVirtualMass and scaled by position and velocity |
Trace all three inputs to see which moved |
| Motion is jerky when crossing a table breakpoint | A step in a lookup table | The coefficient low-passes smooth this; lower their cut-offs, or soften the table |
| Reaching a position limit arrives as a jolt | Only the integrator’s hard clamp is set | Configure velocityLimiterLookup to taper the speed and positionLimitLookup to push back |
outputTorque went to zero and motion stopped |
Expected while disable or disableDynamics is set |
Read enabled |
| Position held but velocity went to zero on disable | Expected: velocity is pinned to its reference, position holds | This is the designed idle state |
| The machine drifted over a long session | Two integrations with no reset reachable from the parameter tree | Restart the controller, or have the application call its reset |
| A force spike appeared after a bad sensor reading | Expected: non-numeric torque readings are treated as zero, but a large finite spike passes | Lower torqueLimiter’s limits |
| Everything went non-numeric and stayed there | A non-numeric value reached inputPVA or a lookup table, which are not guarded |
Restart the controller |
| The machine became unstable after an external factor was written | A zero or negative externalDampingFactor — negative damping adds energy |
Write a value between 0.1 and 100 and restart |
| Motion is right but forces look wrong | The two limits are clipping | Read springForce and damperForce against springTorqueLimit and damperTorqueLimit |
A conservative starting point for a 1 ms task, damper-only compliance with no spring, on an axis whose travel is ±0.5:
enable = true
useNegativeCoefficients = false
measuredTorqueDeadZone = 0.5
actuatorFeedForwardGain = 1.0
massCorrection = 0.0
springTorqueLimit = 10.0
damperTorqueLimit = 10.0
anc/enable = false
velocityCoefficientLookup: numPoints = 1, x = 0, y = 1.0
massLookup: numPoints = 1, x = 0, y = 2.0
dampingLookup: numPoints = 1, x = 0, y = 40.0
stiffnessLookup: numPoints = 1, x = 0, y = 0.0
That gives an effective mass of 4 (mass 2 × position scaling 2), damping 80, and a time constant of 0.05 s. This is a starting point, not a final tuning.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
velocityCoefficientLookup configured |
Nothing | Not checked. Unconfigured it returns zero, floored to 0.01, multiplying your mass by 0.01. Nothing warns | Not reported; visible on massCoefficient |
externalMassFactor, externalDampingFactor within 0.1 – 100 |
Nothing on the input path | Not checked when written as an input. A zero or negative mass factor is caught by the mass floor; a negative damping factor is not caught anywhere and makes the virtual system unstable | Not reported |
| Effective mass ≥ 0.00001 | Fixed | A smaller or non-numeric mass is replaced by that floor, and the mass table’s gain is permanently rewritten as a side effect | Not reported; visible on massCoefficient and in the table’s gain |
springForce |
springTorqueLimit |
Clamped | Not reported; compare springForce against the limit |
damperForce |
damperTorqueLimit |
Clamped | Not reported |
| Accelerating force | torqueLimiter sub-tree |
Clamped when that limiter is enabled. It is disabled by default, so the force is unbounded until you enable it | Not reported |
outputPVA/velocity |
velocityIntegrator output limits, tapered by velocityLimiterLookup |
Clamped, and tapered toward zero near a position limit | Not reported |
outputPVA/position |
positionIntegrator output limits |
Clamped hard. Without a velocity taper the clamp arrives as a jolt | Not reported |
anc/averageMaxSamples |
Nothing | Not checked | Not reported |
actuatorFeedForwardGain, massCorrection |
Nothing | Not checked | Not reported |
| Model state | Nothing reachable | There is no reset input. The block integrates twice and cannot be re-zeroed from the parameter tree — only an application call or a restart | Not reported |
| Non-numeric inputs | Partial | measuredTorque, actuatorFeedForwardTorque and the mass-correction term are guarded and treated as zero. inputPVA and the lookup tables are not |
Not reported |
| Channel count | Fixed | One axis per instance, always | Not reported |
The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.
Verified against motorcortex-control3 3.30.0 (bc348fd).