<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title> – Physical models and compensation</title>
    <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/</link>
    <description>Recent content in Physical models and compensation on </description>
    <generator>Hugo -- gohugo.io</generator>
    
	  <atom:link href="/docs/developing-control-applications/control-blocks/physical-models-and-compensation/index.xml" rel="self" type="application/rss+xml" />
    
    
      
        
      
    
    
    <item>
      <title>Docs: AdmittanceModel</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/admittance-model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/admittance-model/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

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

      </description>
    </item>
    
    <item>
      <title>Docs: ImpedanceModel</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/impedance-model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/impedance-model/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;ImpedanceModel&lt;/code&gt; is the opposite of &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/admittance-model/&#34;&gt;&lt;code&gt;AdmittanceModel&lt;/code&gt;&lt;/a&gt;:
motion goes in, force comes out. Given how far the machine has been displaced
from a desired state, it produces the reaction force a virtual mass, spring and
damper would exert. Use it to make a force-controlled machine behave like a
defined mechanical system.&lt;/p&gt;
&lt;p&gt;It stays stable against a stiff environment, because it has &lt;strong&gt;no internal
state&lt;/strong&gt; — no integration, nothing to wind up. Its output depends only on this
cycle&amp;rsquo;s inputs.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputPVA — where the machine is&amp;quot;]) --&amp;gt; B[&amp;quot;ImpedanceModel&amp;quot;]
    i2([&amp;quot;measuredPVA — where it should be&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;externalTorque&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputTorque — the reaction force&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Force $= K,\Delta x + D,\Delta\dot{x} + M,\Delta\ddot{x}$ plus
&lt;code&gt;externalTorque&lt;/code&gt;, where each $\Delta$ is &lt;code&gt;inputPVA&lt;/code&gt; minus &lt;code&gt;measuredPVA&lt;/code&gt;. Each coefficient is a gain times a
lookup table, so all three can be non-linear. Aim at
$\omega_n = \sqrt{K/M}$ [rad/s] and $\zeta = D/(2\sqrt{KM})$ — target $\zeta$
near 1 so contact does not bounce.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stiffness ships engaged at 100&lt;/strong&gt;, unlike the admittance block whose spring
defaults to off. An unconfigured instance with any displacement produces force
straight away.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enabling is a step, not a ramp.&lt;/strong&gt; The block is algebraic, so the full force
appears on the first cycle. Bring it in by raising the gains from zero instead.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputPVA/position&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Where the machine actually is. &lt;strong&gt;It also drives the mass table&lt;/strong&gt;, so the virtual mass can vary with position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputPVA/velocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputPVA/acceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s² or rad/s²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Only matters when the mass term is non-zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measuredPVA/position&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Where the machine should be — the rest position the spring pulls toward. Despite the name, this is the reference, not a measurement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measuredPVA/velocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The velocity the damper measures against.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measuredPVA/acceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s² or rad/s²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Added straight to the output, unscaled. Use it to chain another model&amp;rsquo;s force in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces &lt;code&gt;outputTorque&lt;/code&gt; to zero. Use it for a runtime override; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The reaction force: spring plus damper plus inertia plus &lt;code&gt;externalTorque&lt;/code&gt;. &lt;strong&gt;Zero while disabled.&lt;/strong&gt; It appears at full value on the first enabled cycle — there is no ramp.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces &lt;code&gt;outputTorque&lt;/code&gt; to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stiffnessGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Scales the stiffness table. Higher resists displacement more. &lt;strong&gt;Not checked — a negative value pushes the machine away from the rest position instead of toward it.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dampingGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Scales the damping table. Higher resists motion more. &lt;strong&gt;Not checked — a negative value drives the machine instead of damping it.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Scales the mass table. Higher makes the machine resist acceleration more. Forced away from zero, but not away from negative.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All four are persistent and survive a restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The three coefficients live below this block&lt;/strong&gt;, as lookup tables:
&lt;code&gt;stiffnessLookup&lt;/code&gt; driven by the relative position, &lt;code&gt;dampingLookup&lt;/code&gt; by the
relative velocity, and &lt;code&gt;massLookup&lt;/code&gt; by the &lt;strong&gt;absolute&lt;/strong&gt; input position. Each
ships as a single point — stiffness 100, damping 10, mass 1 — so a constant
coefficient needs no table work. See &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/lookup/&#34;&gt;&lt;code&gt;lookup.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The effective coefficients are not published.&lt;/strong&gt; What the block actually uses
is the gain times the table output, and neither is visible on an output. To
check one, read the table&amp;rsquo;s own output in its sub-tree and multiply by the gain
yourself.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set all three gains to 0. With no coefficients the output is only
&lt;code&gt;externalTorque&lt;/code&gt;, so nothing unexpected reaches the actuator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;inputPVA&lt;/code&gt; from the machine&amp;rsquo;s measured state and &lt;code&gt;measuredPVA&lt;/code&gt; from the
state you want it to hold. Confirm both on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm the displacement is what you expect: subtract the two positions on a
trace. That difference is what the spring will act on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true. &lt;code&gt;outputTorque&lt;/code&gt; should
still be zero, because the gains are zero.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;stiffnessGain&lt;/code&gt; from 0 toward 1 in steps. &lt;code&gt;outputTorque&lt;/code&gt; should oppose
the displacement — pushing the machine back toward &lt;code&gt;measuredPVA&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 puts real force into the actuator, at full value.&lt;/strong&gt; The block has
no ramp: whatever gain you write takes effect on the next cycle, against
whatever displacement exists at that moment. Raise it in small steps with
the machine near its rest position.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the sign. If the force pushes the machine &lt;strong&gt;away&lt;/strong&gt; from
&lt;code&gt;measuredPVA&lt;/code&gt;, your two PVA inputs are swapped.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;dampingGain&lt;/code&gt; next, then &lt;code&gt;massGain&lt;/code&gt; only if you need the machine to
resist acceleration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set the stiffness from the process: how hard should the machine resist being
pushed off position? That is $K$ in newtons per metre.&lt;/li&gt;
&lt;li&gt;Set the damping for stability, not for feel. Compute
$\zeta = D/(2\sqrt{KM})$ and aim near 1. Below about 0.5 contact bounces.&lt;/li&gt;
&lt;li&gt;Set the mass last, and usually leave it small. It resists acceleration and
amplifies any noise on &lt;code&gt;inputPVA/acceleration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use the tables rather than the gains for anything non-linear — a spring that
stiffens with displacement, or a damper that softens at speed. The gains are
one number each; the tables are a curve.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;massLookup&lt;/code&gt; if the machine&amp;rsquo;s apparent inertia changes across its
workspace. It is driven by absolute position, unlike the other two.&lt;/li&gt;
&lt;li&gt;Verify each coefficient by reading its table output in the sub-tree and
multiplying by the gain. Nothing publishes the product.&lt;/li&gt;
&lt;li&gt;Nothing here needs re-checking after a task-rate change. The block does not
integrate and has no task-rate-dependent limit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/impedance-model-stiffness-3ea7cc22.svg&#34; alt=&#34;Reaction force against displacement at three stiffness gains with a tablevalue of 100: gain 2 gives 200 N/m, gain 1 gives 100 N/m, and gain 0.5 gives50 N/m.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the effective stiffness off the slope of any line.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The machine is pushed away from the rest position instead of toward it&lt;/td&gt;
&lt;td&gt;The two PVA inputs are swapped, or a gain is negative&lt;/td&gt;
&lt;td&gt;Check step 3 of Setup, then check every gain is positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Force appeared as soon as the block was enabled&lt;/td&gt;
&lt;td&gt;Expected: the block is algebraic and has no ramp, and stiffness defaults to 100&lt;/td&gt;
&lt;td&gt;Set the gains to 0 before enabling and raise them from there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine runs away instead of settling&lt;/td&gt;
&lt;td&gt;A negative &lt;code&gt;dampingGain&lt;/code&gt; — negative damping adds energy&lt;/td&gt;
&lt;td&gt;Set it positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contact bounces or oscillates&lt;/td&gt;
&lt;td&gt;Damping too low for the stiffness&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;dampingGain&lt;/code&gt; until $\zeta$ is near 1, or lower the stiffness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contact feels dead and heavy&lt;/td&gt;
&lt;td&gt;Damping too high, or the mass term too large&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;dampingGain&lt;/code&gt;, then &lt;code&gt;massGain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The force is noisy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt; is amplifying noise on the acceleration input&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;massGain&lt;/code&gt;, or filter the acceleration upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The force does not match the stiffness you configured&lt;/td&gt;
&lt;td&gt;Expected: the effective stiffness is the gain times the table output&lt;/td&gt;
&lt;td&gt;Read the table&amp;rsquo;s output in its sub-tree and multiply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Force with no displacement at all&lt;/td&gt;
&lt;td&gt;&lt;code&gt;externalTorque&lt;/code&gt; is non-zero and passes straight through&lt;/td&gt;
&lt;td&gt;Trace that input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine feels stiffer in one part of its travel&lt;/td&gt;
&lt;td&gt;Expected if &lt;code&gt;massLookup&lt;/code&gt; or a coefficient table is shaped that way&lt;/td&gt;
&lt;td&gt;Flatten the table, or accept it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing the stiffness table changed the force at a different displacement than expected&lt;/td&gt;
&lt;td&gt;The stiffness table is driven by the relative position, so its own input moves with the displacement&lt;/td&gt;
&lt;td&gt;Shape the table against the displacements you actually work at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing the mass table did nothing at standstill&lt;/td&gt;
&lt;td&gt;Expected: the mass term multiplies relative acceleration, which is zero at rest&lt;/td&gt;
&lt;td&gt;Check it during a move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputTorque&lt;/code&gt; went to zero&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false or &lt;code&gt;disable&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A non-numeric value appeared and then cleared itself&lt;/td&gt;
&lt;td&gt;Expected: the block holds no state, so it recovers as soon as the inputs are clean&lt;/td&gt;
&lt;td&gt;Fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The block drifted over a long session&lt;/td&gt;
&lt;td&gt;Not possible — there is no state to drift&lt;/td&gt;
&lt;td&gt;Look upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need this on several axes&lt;/td&gt;
&lt;td&gt;Not possible — one axis per instance&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a 1 N·m per 0.01 rad spring with critical damping, mass
term off:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable        = true
stiffnessGain = 1.0
dampingGain   = 1.0
massGain      = 0.0
stiffnessLookup: numPoints = 1, x = 0, y = 100.0
dampingLookup:   numPoints = 1, x = 0, y = 20.0
massLookup:      numPoints = 1, x = 0, y = 1.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Raise the gains from 0 as Setup step 5 describes. This is a starting point, not
a final tuning.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stiffnessGain&lt;/code&gt;, &lt;code&gt;dampingGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked, including negative.&lt;/strong&gt; A negative stiffness pushes the machine away from its rest position; a negative damping drives it instead of damping it. Both make the virtual system an energy source&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forced away from zero&lt;/td&gt;
&lt;td&gt;A value within 0.001 of zero is replaced by ±0.001. &lt;strong&gt;Negative values are still accepted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective coefficients&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;The gain times the table output. &lt;strong&gt;Neither product is published&lt;/strong&gt; — read the table output in its sub-tree and multiply&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the coefficients and displacement produce. Bound it in the actuator&amp;rsquo;s own limiter&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enabling&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;The full force appears on the first enabled cycle. There is no ramp&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability&lt;/td&gt;
&lt;td&gt;Structural&lt;/td&gt;
&lt;td&gt;The block has no state and does not integrate, so it cannot diverge on its own. There is no task-rate limit to respect and nothing to rescale after a task-rate change&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block state&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Nothing to reset, nothing survives a stop&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One axis per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: MassSpringDamperModel</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/mass-spring-damper-model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/mass-spring-damper-model/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;MassSpringDamperModel&lt;/code&gt; simulates a point mass on a spring and damper, coupled
to a neighbouring model. Its purpose is &lt;strong&gt;chains&lt;/strong&gt;: connect several instances
and you have a flexible structure, a series-elastic joint, or a multi-body
compliance model built from copies of one block.&lt;/p&gt;
&lt;p&gt;It does both compliance directions in one block. By default it behaves like an
&lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/admittance-model/&#34;&gt;admittance&lt;/a&gt; — force in, motion out. Set &lt;code&gt;enableImpedance&lt;/code&gt;
and it behaves like an &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/impedance-model/&#34;&gt;impedance&lt;/a&gt; — motion in, force out —
and adds an inertia term to its output force.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputForce — external force on the mass&amp;quot;]) --&amp;gt; B[&amp;quot;MassSpringDamperModel&amp;quot;]
    i2([&amp;quot;inputState — external state, impedance mode only&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;connectedForce — force from the next model&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;connectedState — state of the previous model&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputState — this mass&#39;s motion&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputForce — coupling force&amp;quot;])
    B --&amp;gt; o3([&amp;quot;mass — effective mass&amp;quot;])
    B --&amp;gt; o4([&amp;quot;damping — effective damping&amp;quot;])
    B --&amp;gt; o5([&amp;quot;stiffness — effective stiffness&amp;quot;])
    B --&amp;gt; o6([&amp;quot;relativePosition — spring extension&amp;quot;])
    B --&amp;gt; o7([&amp;quot;relativeVelocity&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The mass obeys $M\ddot{x} =$ &lt;code&gt;inputForce&lt;/code&gt; $+$ &lt;code&gt;connectedForce&lt;/code&gt; $-$
&lt;code&gt;outputForce&lt;/code&gt;, and the coupling force is $K,\Delta x + D,\Delta\dot{x}$,
plus $M,\Delta\ddot{x}$ in impedance mode. Aim at $\omega_n = \sqrt{K/M}$ [rad/s] and
$\zeta = D/(2\sqrt{KM})$ — target $\zeta$ near 1. &lt;strong&gt;Keep the task period
below $2/\omega_n$&lt;/strong&gt; or the simulation grows without bound; nothing checks
this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;To build a chain:&lt;/strong&gt; connect this model&amp;rsquo;s &lt;code&gt;outputForce&lt;/code&gt; to the previous
model&amp;rsquo;s &lt;code&gt;connectedForce&lt;/code&gt;, and the previous model&amp;rsquo;s &lt;code&gt;outputState&lt;/code&gt; to this one&amp;rsquo;s
&lt;code&gt;connectedState&lt;/code&gt;. The spring anchors on &lt;code&gt;connectedState&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable and no disable.&lt;/strong&gt; &lt;code&gt;disableDynamics&lt;/code&gt; pins the motion
to &lt;code&gt;connectedState&lt;/code&gt; but &lt;strong&gt;still publishes a coupling force&lt;/strong&gt;, so a chain keeps
transmitting while it is set.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The external force pushing on this mass. Forces smaller than &lt;code&gt;inputForceDeadZone&lt;/code&gt; are ignored.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m, m/s, m/s²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;An externally measured state, as &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;xDot&lt;/code&gt; and &lt;code&gt;xDDot&lt;/code&gt; together. &lt;strong&gt;Only read in impedance mode&lt;/strong&gt; — in the default admittance mode it is ignored entirely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectedForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The coupling force from the next model in the chain. Added directly to the net force.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connectedState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m, m/s, m/s²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The previous model&amp;rsquo;s state. It is the spring&amp;rsquo;s anchor &lt;strong&gt;and&lt;/strong&gt; the reference both integrators return to.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m, m/s, m/s²&lt;/td&gt;
&lt;td&gt;This mass&amp;rsquo;s position, velocity and acceleration, together. Chain it to the next model&amp;rsquo;s &lt;code&gt;connectedState&lt;/code&gt;. Starts at zero and is &lt;strong&gt;not&lt;/strong&gt; cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The coupling force the spring and damper transmit. Chain it to the previous model&amp;rsquo;s &lt;code&gt;connectedForce&lt;/code&gt;. &lt;strong&gt;Not zeroed by &lt;code&gt;disableDynamics&lt;/code&gt;.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;kg or kg·m²&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;effective&lt;/strong&gt; mass — &lt;code&gt;massGain&lt;/code&gt; times the mass table&amp;rsquo;s output. Watch this, not the table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;damping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·s/m&lt;/td&gt;
&lt;td&gt;The effective damping.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stiffness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N/m&lt;/td&gt;
&lt;td&gt;The effective stiffness.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;relativePosition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;The spring&amp;rsquo;s extension or compression. In admittance mode this is &lt;code&gt;outputState&lt;/code&gt; minus &lt;code&gt;connectedState&lt;/code&gt;; in impedance mode &lt;code&gt;inputState&lt;/code&gt; minus &lt;code&gt;connectedState&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;relativeVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;The velocity across the damper, from the same source as above.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enableImpedance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False gives admittance: the coupling force is spring + damper, computed from this block&amp;rsquo;s own motion. True gives impedance: the coupling force is computed from &lt;code&gt;inputState&lt;/code&gt; instead, &lt;strong&gt;and gains an inertia term&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.001 upward&lt;/td&gt;
&lt;td&gt;Scales the mass table. Values below 0.001 are corrected up, so the mass gain can never be zero or negative.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stiffnessGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Scales the stiffness table. &lt;strong&gt;Its sign is forced positive&lt;/strong&gt;, so a negative value acts as its magnitude.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dampingGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Scales the damping table. Its sign is forced positive too.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disableDynamics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True pins &lt;code&gt;outputState&lt;/code&gt; to &lt;code&gt;connectedState&lt;/code&gt; every cycle, so the mass stops moving on its own. It does &lt;strong&gt;not&lt;/strong&gt; stop the coupling force.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputForceDeadZone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Forces smaller than this do not push the mass. Raise it to stop a noisy force reading creeping the model.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All six are persistent and survive a restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The three coefficients live below this block&lt;/strong&gt;, as lookup tables:
&lt;code&gt;massLookup&lt;/code&gt; driven by this mass&amp;rsquo;s own position, &lt;code&gt;dampingLookup&lt;/code&gt; by the
relative velocity, and &lt;code&gt;stiffnessLookup&lt;/code&gt; by the relative position. Each ships
as a single point at 1.0. The two integrators — &lt;code&gt;velocityIntegrator&lt;/code&gt; and
&lt;code&gt;positionIntegrator&lt;/code&gt; — carry the motion limits. See &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/lookup/&#34;&gt;&lt;code&gt;lookup.md&lt;/code&gt;&lt;/a&gt;
and &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator/&#34;&gt;&lt;code&gt;integrator.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never let &lt;code&gt;massLookup&lt;/code&gt; return zero.&lt;/strong&gt; The mass divides the net force, and a
zero mass makes the simulation non-numeric. A table that is not strictly
increasing in &lt;code&gt;x&lt;/code&gt; also returns zero, so check monotonicity as well as values.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;code&gt;massLookup&lt;/code&gt; with the mass you want this point to have. A single
point is enough if it does not vary with position. &lt;strong&gt;Confirm it never
returns zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;code&gt;dampingLookup&lt;/code&gt; and &lt;code&gt;stiffnessLookup&lt;/code&gt;. Start with a low stiffness
and generous damping — a soft, well-damped model is safe to experiment with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compute $\omega_n = \sqrt{K/M}$ and check the task period is below
$2/\omega_n$, comfortably. A stiff spring on a slow task will not simulate;
it will diverge.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3 is the check nothing does for you.&lt;/strong&gt; There is no warning and no
clamp. Too stiff a spring for the task rate makes &lt;code&gt;outputState&lt;/code&gt; grow
without bound, and the only way out is a restart.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the integrators&#39; output limits to the travel and speed this mass may
have.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;enableImpedance&lt;/code&gt; false for a first pass. Link &lt;code&gt;inputForce&lt;/code&gt; and watch
&lt;code&gt;outputState&lt;/code&gt; respond.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read &lt;code&gt;mass&lt;/code&gt;, &lt;code&gt;damping&lt;/code&gt; and &lt;code&gt;stiffness&lt;/code&gt; back and confirm they are the values
you intended. They are the gain times the table output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To chain: link this model&amp;rsquo;s &lt;code&gt;outputForce&lt;/code&gt; to the previous model&amp;rsquo;s
&lt;code&gt;connectedForce&lt;/code&gt;, and that model&amp;rsquo;s &lt;code&gt;outputState&lt;/code&gt; to this one&amp;rsquo;s
&lt;code&gt;connectedState&lt;/code&gt;. Build the chain one link at a time and check each is
stable before adding the next.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set the mass first, from the physical mass this point represents.&lt;/li&gt;
&lt;li&gt;Set the stiffness from the compliance you are modelling. For a series-elastic
joint that is the spring&amp;rsquo;s real rate.&lt;/li&gt;
&lt;li&gt;Set the damping for stability: compute $\zeta = D/(2\sqrt{KM})$ and aim near
&lt;ol&gt;
&lt;li&gt;Below about 0.5 the model rings, and in a chain that ringing couples
between links.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Re-check step 3 of Setup after every stiffness or mass change. The stability
bound moves with both.&lt;/li&gt;
&lt;li&gt;Use the tables rather than the gains for anything non-linear — a progressive
spring, or a damper that softens at speed.&lt;/li&gt;
&lt;li&gt;Choose the mode deliberately, and not mid-run. Switching &lt;code&gt;enableImpedance&lt;/code&gt;
changes which inputs matter: admittance mode ignores &lt;code&gt;inputState&lt;/code&gt;, and
impedance mode stops using this block&amp;rsquo;s own position for the coupling force.&lt;/li&gt;
&lt;li&gt;In a chain, tune from one end. Each link&amp;rsquo;s &lt;code&gt;connectedState&lt;/code&gt; is the previous
link&amp;rsquo;s output, so an unstable link destabilises everything after it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/mass-spring-damper-model-step-deb660a2.svg&#34; alt=&#34;Position from a 10 N step with mass 1 and damping 2, at three stiffnesses:stiffness 5 rings and peaks at 2.4, stiffness 20 peaks at 0.74 and settles at0.5, and stiffness 100 settles at 0.1.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the damping ratio off the overshoot on any curve.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputState&lt;/code&gt; grew without bound&lt;/td&gt;
&lt;td&gt;The task period is too slow for the stiffness and mass&lt;/td&gt;
&lt;td&gt;Work step 3 of Setup, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything went non-numeric&lt;/td&gt;
&lt;td&gt;&lt;code&gt;massLookup&lt;/code&gt; returned zero — either its value is zero or its &lt;code&gt;x&lt;/code&gt; values are not strictly increasing&lt;/td&gt;
&lt;td&gt;Fix the table, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The mass does not move at all&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disableDynamics&lt;/code&gt; is set, or &lt;code&gt;inputForce&lt;/code&gt; is inside the dead zone&lt;/td&gt;
&lt;td&gt;Clear &lt;code&gt;disableDynamics&lt;/code&gt;; lower &lt;code&gt;inputForceDeadZone&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The mass still transmits force while &lt;code&gt;disableDynamics&lt;/code&gt; is set&lt;/td&gt;
&lt;td&gt;Expected: that parameter pins the motion, not the force&lt;/td&gt;
&lt;td&gt;Set the stiffness and damping gains to 0 as well&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputForce&lt;/code&gt; does not respond to this mass&amp;rsquo;s own motion&lt;/td&gt;
&lt;td&gt;Expected in impedance mode: the coupling force is computed from &lt;code&gt;inputState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear &lt;code&gt;enableImpedance&lt;/code&gt;, or link &lt;code&gt;inputState&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputState&lt;/code&gt; seems to be ignored&lt;/td&gt;
&lt;td&gt;Expected in admittance mode, which is the default&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;enableImpedance&lt;/code&gt; if you meant impedance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The force gained an extra term when the mode changed&lt;/td&gt;
&lt;td&gt;Expected: impedance mode adds an inertia term to the coupling force&lt;/td&gt;
&lt;td&gt;This is the mode&amp;rsquo;s purpose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The model rings after a force step&lt;/td&gt;
&lt;td&gt;Damping too low for the stiffness&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;dampingGain&lt;/code&gt; until $\zeta$ is near 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The model is sluggish&lt;/td&gt;
&lt;td&gt;Damping too high, or the mass too large&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;dampingGain&lt;/code&gt;, then the mass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A chain oscillates although each link looks fine alone&lt;/td&gt;
&lt;td&gt;Links couple, and a lightly damped link excites its neighbours&lt;/td&gt;
&lt;td&gt;Raise damping across the chain, and tune from one end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The effective coefficients are not what the table says&lt;/td&gt;
&lt;td&gt;Expected: each is the gain times the table output&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;mass&lt;/code&gt;, &lt;code&gt;damping&lt;/code&gt; and &lt;code&gt;stiffness&lt;/code&gt; — all three are published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A negative stiffness or damping gain had no effect on the sign&lt;/td&gt;
&lt;td&gt;Expected: both are forced positive&lt;/td&gt;
&lt;td&gt;Use the table values if you need a sign change, though a negative spring is unphysical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt; reads back as 0.001 after writing 0&lt;/td&gt;
&lt;td&gt;Expected: it is floored&lt;/td&gt;
&lt;td&gt;Set a real value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The simulation kept running after a controller stop and start&lt;/td&gt;
&lt;td&gt;Expected: the state is not cleared at start&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;disableDynamics&lt;/code&gt; for a cycle to pin it back to &lt;code&gt;connectedState&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The mass feels heavier at one end of its travel&lt;/td&gt;
&lt;td&gt;Expected if &lt;code&gt;massLookup&lt;/code&gt; is shaped that way — it is driven by this mass&amp;rsquo;s own position&lt;/td&gt;
&lt;td&gt;Flatten the table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A non-numeric value appeared and will not clear&lt;/td&gt;
&lt;td&gt;There is no reset input, and the integrators hold it&lt;/td&gt;
&lt;td&gt;Restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a soft, well-damped single mass on a 1 ms task —
$\omega_n$ = 4.5 rad/s, $\zeta$ = 1.1, comfortably inside the stability bound:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enableImpedance    = false
massGain           = 1.0
stiffnessGain      = 1.0
dampingGain        = 1.0
disableDynamics    = false
inputForceDeadZone = 0.1
massLookup:      numPoints = 1, x = 0, y = 1.0
stiffnessLookup: numPoints = 1, x = 0, y = 20.0
dampingLookup:   numPoints = 1, x = 0, y = 10.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task period against the model&amp;rsquo;s natural frequency&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; The task period must stay below 2 divided by $\sqrt{K/M}$. Too stiff a spring for the task rate makes the state grow without bound; only a restart clears it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective mass above zero&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; &lt;code&gt;massGain&lt;/code&gt; is floored at 0.001, but the mass &lt;strong&gt;table&lt;/strong&gt; can still return zero — including when its &lt;code&gt;x&lt;/code&gt; values are not strictly increasing, which returns zero by design. The result is a division by zero&lt;/td&gt;
&lt;td&gt;Not reported; visible on &lt;code&gt;mass&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stiffnessGain&lt;/code&gt;, &lt;code&gt;dampingGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sign forced&lt;/td&gt;
&lt;td&gt;Both are used as their magnitude, so a negative value cannot invert the force&lt;/td&gt;
&lt;td&gt;Not reported; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;massGain&lt;/code&gt; ≥ 0.001&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Corrected up, which also excludes negatives&lt;/td&gt;
&lt;td&gt;Not reported; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. Bound it downstream, and remember it is &lt;strong&gt;not&lt;/strong&gt; zeroed by &lt;code&gt;disableDynamics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The two integrators&#39; output limits&lt;/td&gt;
&lt;td&gt;Clamped by whatever those sub-trees are set to&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-numeric inputs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not guarded anywhere. A non-numeric value latches into the integrators and there is &lt;strong&gt;no reset input&lt;/strong&gt; to clear it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disableDynamics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pins the state to &lt;code&gt;connectedState&lt;/code&gt;. That is the only reset, and it is a parameter rather than a linkable input&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One mass per instance. A chain is several instances&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: FrictionCompensation</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model-3.30/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model-3.30/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version c3-version--archived&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/&#34;&gt;3.32–3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model-3.30/&#34; selected&gt;3.30&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;
    Superseded. The current release is
    &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;FrictionCompensation&lt;/code&gt; computes the friction force an axis is fighting at a
given velocity, so a feedforward path can cancel it before the feedback loop
has to. It is what turns a machine that sticks and then jumps into one that
moves smoothly from rest.&lt;/p&gt;
&lt;p&gt;It offers &lt;strong&gt;three friction models&lt;/strong&gt;, each from a published paper, chosen with
one parameter. They differ in how they behave at zero velocity, and that
difference is the whole reason to pick one over another.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — velocity&amp;quot;]) --&amp;gt; B[&amp;quot;FrictionCompensation&amp;quot;]
    i2([&amp;quot;outputScalingFactor&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — friction force&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The shared curve is $g(v) = F_c + (F_s - F_c)e^{-(v/v_s)^2}$: friction falls
from &lt;code&gt;stictionFrictionForce&lt;/code&gt; at rest to &lt;code&gt;coulombFrictionForce&lt;/code&gt; once the
velocity is well past &lt;code&gt;stribeckVelocity&lt;/code&gt;. That drop is why a machine breaks
away and then runs light. Viscous damping adds
&lt;code&gt;viscousFrictionDamping&lt;/code&gt; × velocity on top. Below a velocity of
&lt;code&gt;coulombFrictionForce&lt;/code&gt; × &lt;code&gt;preSlidingDisplacement&lt;/code&gt; / &lt;code&gt;stictionFrictionForce&lt;/code&gt; —
0.001 at the defaults — models 0 and 1 produce no friction force at all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The default model is 1, &lt;code&gt;STATIC_SIGN&lt;/code&gt;, and it is the wrong one for
feedforward.&lt;/strong&gt; It steps by twice the stiction force as the axis reverses. For
feedforward use &lt;strong&gt;model 2, &lt;code&gt;CONTINUOUS_TANH&lt;/code&gt;&lt;/strong&gt;, which is smooth through zero.
Model 1 is the default only for backward compatibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The block starts disabled&lt;/strong&gt; — &lt;code&gt;enable&lt;/code&gt; defaults to false.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The velocity to compute friction for — normally the velocity &lt;em&gt;reference&lt;/em&gt;, not the measurement, since this is a feedforward term. One element per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0 – 1&lt;/td&gt;
&lt;td&gt;Fades the output force. It is clamped and &lt;strong&gt;rate-limited in place&lt;/strong&gt;, so reading this path back gives the rate-limited value, not what you wrote. Inside a feedforward controller this path is written every cycle from a position table and a link into it has no effect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces the output to zero and clears the model&amp;rsquo;s internal state. Use it for a runtime override; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The friction force at the given velocity, after scaling. One element per channel. &lt;strong&gt;Zero while disabled&lt;/strong&gt; — not a pass-through, because a friction model&amp;rsquo;s idle value is no force.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. Note it can read true while &lt;code&gt;outputScalingFactor&lt;/code&gt; has faded the output to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0, 1 or 2&lt;/td&gt;
&lt;td&gt;Which friction model to use. See the table below. &lt;strong&gt;Set this explicitly&lt;/strong&gt; — leaving it unset gives model 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces the output to zero and clears the internal state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Friction once the axis is moving. The floor of the curve. Corrected to 0 if negative.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;at least &lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Friction at rest — the force needed to break away. &lt;strong&gt;Corrected up to the Coulomb force if you set it lower.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·s/m or N·m·s/rad&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 upward in practice&lt;/td&gt;
&lt;td&gt;Friction proportional to speed. &lt;strong&gt;Not checked — a negative value makes friction drive the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;1e-6 – 0.01&lt;/td&gt;
&lt;td&gt;How quickly friction falls from stiction to Coulomb. Corrected silently if out of range.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;0.002&lt;/td&gt;
&lt;td&gt;1e-9 – 0.01&lt;/td&gt;
&lt;td&gt;How far the axis deflects before it breaks away. Models 0 and 1 only. It also sets the zero-velocity dead band — see the blockquote.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contactDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·s/m&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Bristle contact damping, model 0 only. &lt;strong&gt;Leave at 0 for feedforward&lt;/strong&gt; — non-zero adds transient forces during velocity changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;0.001&lt;/td&gt;
&lt;td&gt;1e-6 upward&lt;/td&gt;
&lt;td&gt;How sharply model 2 rises through zero. Smaller is sharper and closer to model 1. Model 2 only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;10.0&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;How fast &lt;code&gt;outputScalingFactor&lt;/code&gt; may change. The default fades fully in 0.1 s on any task rate. &lt;strong&gt;Not checked — 0 freezes the scaling factor where it is.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;model&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What the machine does&lt;/th&gt;
&lt;th&gt;Parameters that matter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt; &lt;code&gt;LUGRE_DYNAMIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Models the deflection before breakaway, so stick-slip and hysteresis at reversal are reproduced. Has internal state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt;, &lt;code&gt;contactDamping&lt;/code&gt;, plus the shared curve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt; &lt;code&gt;STATIC_SIGN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classical Stribeck curve. &lt;strong&gt;Steps discontinuously across zero velocity&lt;/strong&gt; — use it for steady-state analysis, not feedforward&lt;/td&gt;
&lt;td&gt;The shared curve, &lt;code&gt;preSlidingDisplacement&lt;/code&gt; for the dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt; &lt;code&gt;CONTINUOUS_TANH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smooth everywhere, including through zero. &lt;strong&gt;The right choice for friction feedforward&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;coulombVelocity&lt;/code&gt;, plus the shared curve&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All parameters are persistent and survive a controller restart. &lt;strong&gt;No parameters
exist below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A legacy &lt;code&gt;useStaticFriction&lt;/code&gt; parameter is still read from older configuration
files and mapped onto &lt;code&gt;model&lt;/code&gt; at startup, with a warning in the log. It is not
saved back. Update your configuration to set &lt;code&gt;model&lt;/code&gt; directly.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;model&lt;/code&gt; to 2 if this is a feedforward path, which is the usual case. Set
it explicitly — an unset &lt;code&gt;model&lt;/code&gt; gives model 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false while you measure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure Coulomb friction: move the axis at a steady moderate speed with the
loop closed and no friction compensation, and read the force the feedback
controller supplies. That is &lt;code&gt;coulombFrictionForce&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure stiction: command a slowly rising force from rest and note the force
at which the axis breaks away. That is &lt;code&gt;stictionFrictionForce&lt;/code&gt;. It must be at
least the Coulomb force, and the block will correct it upward if it is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure viscous damping: repeat step 3 at two or three speeds. The slope of
force against speed is &lt;code&gt;viscousFrictionDamping&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;stribeckVelocity&lt;/code&gt; at 0.01 and &lt;code&gt;preSlidingDisplacement&lt;/code&gt; at 0.002 for a
first pass. Both are refinements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true. Check the sign: the
output must &lt;strong&gt;oppose&lt;/strong&gt; motion — positive force for positive velocity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 adds real force to the actuator.&lt;/strong&gt; If the sign is wrong the
compensation adds to friction instead of cancelling it, and the axis will
accelerate away. Have the axis clear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the feedback controller&amp;rsquo;s own output has dropped at steady speed. That
drop is what the compensation bought you.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Measure, do not dial. Every parameter here is a physical property of your
axis, and steps 3 to 5 of Setup measure them directly. Tuning by watching
the error is how you end up over-compensating.&lt;/li&gt;
&lt;li&gt;Get the Coulomb force right first — it dominates everywhere except near
rest.&lt;/li&gt;
&lt;li&gt;Then stiction. Too much and the axis lurches from rest; too little and it
still sticks.&lt;/li&gt;
&lt;li&gt;Then viscous damping, at the top of your speed range where it matters most.&lt;/li&gt;
&lt;li&gt;Refine &lt;code&gt;stribeckVelocity&lt;/code&gt; only if the transition from rest to moving feels
wrong. Lower it to make friction drop off sooner.&lt;/li&gt;
&lt;li&gt;With model 2, use &lt;code&gt;coulombVelocity&lt;/code&gt; to set how sharply the force rises
through zero. Smaller is sharper and cancels more friction near rest, but a
very small value approaches model 1&amp;rsquo;s discontinuity and reintroduces
chatter.&lt;/li&gt;
&lt;li&gt;With model 0, &lt;code&gt;preSlidingDisplacement&lt;/code&gt; is the real deflection before
breakaway. Leave &lt;code&gt;contactDamping&lt;/code&gt; at 0 for feedforward.&lt;/li&gt;
&lt;li&gt;Check both directions of travel. Friction is rarely symmetric, and this
block models it as symmetric — if the two differ a lot, this block will
compensate the average.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;outputScalingFactor&lt;/code&gt; to fade compensation in and out — for example to
reduce it where the axis is well supported. Its rate limit is in units per
second and holds its meaning across task rates.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/friction-model-stribeck-91b280b9.svg&#34; alt=&#34;Friction force against velocity for the three models: model 1 stepsdiscontinuously across zero to plus or minus 20 N, model 2 rises smoothlythrough zero, and model 0&amp;rsquo;s sliding steady state matches model 1 away fromrest.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the stiction force off the peak near zero and the Coulomb force off the
flat part beyond it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The axis chatters or buzzes around zero velocity&lt;/td&gt;
&lt;td&gt;Model 1&amp;rsquo;s discontinuity at zero&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;model&lt;/code&gt; to 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis lurches from rest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Re-measure it with Setup step 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis still sticks at rest&lt;/td&gt;
&lt;td&gt;Stiction too low, or the dead band is swallowing the command&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;stictionFrictionForce&lt;/code&gt;; if that does not help, lower &lt;code&gt;preSlidingDisplacement&lt;/code&gt; to narrow the dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No friction force at all at very low speed&lt;/td&gt;
&lt;td&gt;Expected: models 0 and 1 have a dead band below the velocity in the blockquote&lt;/td&gt;
&lt;td&gt;Use model 2, which has no dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis accelerates away instead of moving smoothly&lt;/td&gt;
&lt;td&gt;The sign is wrong, or friction is over-compensated&lt;/td&gt;
&lt;td&gt;Check the sign first; then halve every force parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis runs faster the harder it is pushed, with no friction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt; is negative&lt;/td&gt;
&lt;td&gt;Set it to zero or positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation is right at low speed and too strong at high speed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Re-measure with Setup step 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation is right at high speed and too weak near rest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; too low, or &lt;code&gt;stribeckVelocity&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Raise stiction; lower the Stribeck velocity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; reads back higher than written&lt;/td&gt;
&lt;td&gt;Expected: it is corrected up to the Coulomb force&lt;/td&gt;
&lt;td&gt;Raise the Coulomb force, or accept the correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt; or &lt;code&gt;preSlidingDisplacement&lt;/code&gt; reads back different&lt;/td&gt;
&lt;td&gt;Outside the accepted band&lt;/td&gt;
&lt;td&gt;Work within the value you read back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output faded to zero but &lt;code&gt;isEnabled&lt;/code&gt; reads true&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; has been driven to 0&lt;/td&gt;
&lt;td&gt;Read that path; inside a feedforward controller it is written from a position table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; will not move&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set a positive rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A scaling change arrived as a slow ramp&lt;/td&gt;
&lt;td&gt;Expected: it is rate-limited&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output ramps up over a moment after enabling, with model 0&lt;/td&gt;
&lt;td&gt;Expected: the deflection starts from zero and loads up&lt;/td&gt;
&lt;td&gt;Use model 1 or 2 if that transient matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The block behaves like model 1 when you configured model 2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt; was left unset, so it resolved to 1&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;model&lt;/code&gt; = 2 explicitly and check the log for a deprecation warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A warning about &lt;code&gt;useStaticFriction&lt;/code&gt; at startup&lt;/td&gt;
&lt;td&gt;An old configuration file still sets the retired parameter&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;model&lt;/code&gt; directly and remove it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction differs between directions and the block cannot match both&lt;/td&gt;
&lt;td&gt;The model is symmetric by construction&lt;/td&gt;
&lt;td&gt;Compensate the average, or handle the asymmetry upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every channel gets the same friction&lt;/td&gt;
&lt;td&gt;Expected: all parameters are shared across channels&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output went non-numeric&lt;/td&gt;
&lt;td&gt;A non-numeric velocity reached &lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source, then disable and re-enable to clear the state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for feedforward on a rotary axis on a 1 ms task, with
measured values substituted at steps 3 to 5 of Setup:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;model                   = 2
enable                  = true
coulombFrictionForce    = 10.0
stictionFrictionForce   = 20.0
viscousFrictionDamping  = 1.0
stribeckVelocity        = 0.01
coulombVelocity         = 0.001
preSlidingDisplacement  = 0.002
contactDamping          = 0.0
outputScalingFactorRate = 10.0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; ≥ &lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Stiction is raised to match the Coulomb force. This is what keeps model 2&amp;rsquo;s curve the right way up&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombFrictionForce&lt;/code&gt; ≥ 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A negative value is replaced by 0&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt; within 1e-6 – 0.01&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt; within 1e-9 – 0.01&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge. The bound is the same for a linear or rotary axis&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contactDamping&lt;/code&gt; ≥ 0, &lt;code&gt;coulombVelocity&lt;/code&gt; ≥ 1e-6&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; A negative value makes friction drive the axis instead of resisting it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; 0 freezes the scaling factor at its present value — including at 0, which silences the output while &lt;code&gt;isEnabled&lt;/code&gt; still reads true. A negative value is unsafe&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Clamped, then rate-limited, &lt;strong&gt;and written back to the input path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported; read the path back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the model computes. Bound it in the actuator&amp;rsquo;s own limiter&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;, or &lt;code&gt;enable&lt;/code&gt; false&lt;/td&gt;
&lt;td&gt;A disabled cycle clears the internal state completely&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channels&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Every channel uses the same parameters. One instance cannot model two different axes&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block logs one warning, at startup only: that a configuration file still
sets the retired &lt;code&gt;useStaticFriction&lt;/code&gt; parameter, naming the model it was mapped
to. Every other failure above shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: FrictionCompensation</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/&#34; selected&gt;3.32–3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model-3.30/&#34;&gt;3.30&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;Current release&lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;FrictionCompensation&lt;/code&gt; computes the friction force an axis is fighting at a
given velocity, so a feedforward path can cancel it before the feedback loop
has to. It is what turns a machine that sticks and then jumps into one that
moves smoothly from rest.&lt;/p&gt;
&lt;p&gt;It offers &lt;strong&gt;three friction models&lt;/strong&gt;, each from a published paper, chosen with
one parameter. They differ in how they behave at zero velocity, and that
difference is the whole reason to pick one over another.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — velocity&amp;quot;]) --&amp;gt; B[&amp;quot;FrictionCompensation&amp;quot;]
    i2([&amp;quot;outputScalingFactor&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — friction force&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The shared curve is $g(v) = F_c + (F_s - F_c)e^{-(v/v_s)^2}$: friction falls
from &lt;code&gt;stictionFrictionForce&lt;/code&gt; at rest to &lt;code&gt;coulombFrictionForce&lt;/code&gt; once the
velocity is well past &lt;code&gt;stribeckVelocity&lt;/code&gt;. That drop is why a machine breaks
away and then runs light. Viscous damping adds
&lt;code&gt;viscousFrictionDamping&lt;/code&gt; × velocity on top. Below a velocity of
&lt;code&gt;coulombFrictionForce&lt;/code&gt; × &lt;code&gt;preSlidingDisplacement&lt;/code&gt; / &lt;code&gt;stictionFrictionForce&lt;/code&gt; —
0.001 at the defaults — models 0 and 1 produce no friction force at all.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The default model is 1, &lt;code&gt;STATIC_SIGN&lt;/code&gt;, and it is the wrong one for
feedforward.&lt;/strong&gt; It steps by twice the stiction force as the axis reverses. For
feedforward use &lt;strong&gt;model 2, &lt;code&gt;CONTINUOUS_TANH&lt;/code&gt;&lt;/strong&gt;, which is smooth through zero.
Model 1 is the default only for backward compatibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The block starts disabled&lt;/strong&gt; — &lt;code&gt;enable&lt;/code&gt; defaults to false.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The velocity to compute friction for — normally the velocity &lt;em&gt;reference&lt;/em&gt;, not the measurement, since this is a feedforward term. One element per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0 – 1&lt;/td&gt;
&lt;td&gt;Fades the output force. &lt;strong&gt;The block never writes this path&lt;/strong&gt;, so reading it back gives exactly what you wrote — write it once and leave it, and the fade still completes. The value actually multiplying the output is that command clamped to 0 – 1 and moved towards by at most &lt;code&gt;outputScalingFactorRate&lt;/code&gt; per second; &lt;strong&gt;it is not published&lt;/strong&gt;, so read the fade off &lt;code&gt;output&lt;/code&gt; itself. Inside a feedforward controller this path is written every cycle from a position table and a link into it has no effect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces the output to zero and clears the model&amp;rsquo;s internal state. Use it for a runtime override; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The friction force at the given velocity, after scaling. One element per channel. &lt;strong&gt;Zero while disabled&lt;/strong&gt; — not a pass-through, because a friction model&amp;rsquo;s idle value is no force.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. Note it can read true while &lt;code&gt;outputScalingFactor&lt;/code&gt; has faded the output to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The applied scaling factor is deliberately not a published signal. &lt;code&gt;output&lt;/code&gt;
carries its effect, and &lt;code&gt;outputScalingFactor&lt;/code&gt; carries the command.&lt;/p&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0, 1 or 2&lt;/td&gt;
&lt;td&gt;Which friction model to use. See the table below. &lt;strong&gt;Set this explicitly&lt;/strong&gt; — leaving it unset gives model 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces the output to zero and clears the internal state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Friction once the axis is moving. The floor of the curve. Corrected to 0 if negative.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;at least &lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Friction at rest — the force needed to break away. &lt;strong&gt;Corrected up to the Coulomb force if you set it lower.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·s/m or N·m·s/rad&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 upward in practice&lt;/td&gt;
&lt;td&gt;Friction proportional to speed. &lt;strong&gt;Not checked — a negative value makes friction drive the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;1e-6 – 0.01&lt;/td&gt;
&lt;td&gt;How quickly friction falls from stiction to Coulomb. Corrected silently if out of range.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;0.002&lt;/td&gt;
&lt;td&gt;1e-9 – 0.01&lt;/td&gt;
&lt;td&gt;How far the axis deflects before it breaks away. Models 0 and 1 only. It also sets the zero-velocity dead band — see the blockquote.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contactDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·s/m&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Bristle contact damping, model 0 only. &lt;strong&gt;Leave at 0 for feedforward&lt;/strong&gt; — non-zero adds transient forces during velocity changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;0.001&lt;/td&gt;
&lt;td&gt;1e-6 upward&lt;/td&gt;
&lt;td&gt;How sharply model 2 rises through zero. Smaller is sharper and closer to model 1. Model 2 only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;10.0&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;How fast &lt;code&gt;outputScalingFactor&lt;/code&gt; may change. The default fades fully in 0.1 s on any task rate. &lt;strong&gt;Not checked — 0 freezes the scaling factor where it is.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;model&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What the machine does&lt;/th&gt;
&lt;th&gt;Parameters that matter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt; &lt;code&gt;LUGRE_DYNAMIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Models the deflection before breakaway, so stick-slip and hysteresis at reversal are reproduced. Has internal state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt;, &lt;code&gt;contactDamping&lt;/code&gt;, plus the shared curve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt; &lt;code&gt;STATIC_SIGN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classical Stribeck curve. &lt;strong&gt;Steps discontinuously across zero velocity&lt;/strong&gt; — use it for steady-state analysis, not feedforward&lt;/td&gt;
&lt;td&gt;The shared curve, &lt;code&gt;preSlidingDisplacement&lt;/code&gt; for the dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt; &lt;code&gt;CONTINUOUS_TANH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smooth everywhere, including through zero. &lt;strong&gt;The right choice for friction feedforward&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;coulombVelocity&lt;/code&gt;, plus the shared curve&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All parameters are persistent and survive a controller restart. &lt;strong&gt;No parameters
exist below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A legacy &lt;code&gt;useStaticFriction&lt;/code&gt; parameter is still read from older configuration
files and mapped onto &lt;code&gt;model&lt;/code&gt; at startup, with a warning in the log. It is not
saved back. Update your configuration to set &lt;code&gt;model&lt;/code&gt; directly.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;model&lt;/code&gt; to 2 if this is a feedforward path, which is the usual case. Set
it explicitly — an unset &lt;code&gt;model&lt;/code&gt; gives model 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false while you measure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure Coulomb friction: move the axis at a steady moderate speed with the
loop closed and no friction compensation, and read the force the feedback
controller supplies. That is &lt;code&gt;coulombFrictionForce&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure stiction: command a slowly rising force from rest and note the force
at which the axis breaks away. That is &lt;code&gt;stictionFrictionForce&lt;/code&gt;. It must be at
least the Coulomb force, and the block will correct it upward if it is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure viscous damping: repeat step 3 at two or three speeds. The slope of
force against speed is &lt;code&gt;viscousFrictionDamping&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;stribeckVelocity&lt;/code&gt; at 0.01 and &lt;code&gt;preSlidingDisplacement&lt;/code&gt; at 0.002 for a
first pass. Both are refinements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true. Check the sign: the
output must &lt;strong&gt;oppose&lt;/strong&gt; motion — positive force for positive velocity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 adds real force to the actuator.&lt;/strong&gt; If the sign is wrong the
compensation adds to friction instead of cancelling it, and the axis will
accelerate away. Have the axis clear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the feedback controller&amp;rsquo;s own output has dropped at steady speed. That
drop is what the compensation bought you.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Measure, do not dial. Every parameter here is a physical property of your
axis, and steps 3 to 5 of Setup measure them directly. Tuning by watching
the error is how you end up over-compensating.&lt;/li&gt;
&lt;li&gt;Get the Coulomb force right first — it dominates everywhere except near
rest.&lt;/li&gt;
&lt;li&gt;Then stiction. Too much and the axis lurches from rest; too little and it
still sticks.&lt;/li&gt;
&lt;li&gt;Then viscous damping, at the top of your speed range where it matters most.&lt;/li&gt;
&lt;li&gt;Refine &lt;code&gt;stribeckVelocity&lt;/code&gt; only if the transition from rest to moving feels
wrong. Lower it to make friction drop off sooner.&lt;/li&gt;
&lt;li&gt;With model 2, use &lt;code&gt;coulombVelocity&lt;/code&gt; to set how sharply the force rises
through zero. Smaller is sharper and cancels more friction near rest, but a
very small value approaches model 1&amp;rsquo;s discontinuity and reintroduces
chatter.&lt;/li&gt;
&lt;li&gt;With model 0, &lt;code&gt;preSlidingDisplacement&lt;/code&gt; is the real deflection before
breakaway. Leave &lt;code&gt;contactDamping&lt;/code&gt; at 0 for feedforward.&lt;/li&gt;
&lt;li&gt;Check both directions of travel. Friction is rarely symmetric, and this
block models it as symmetric — if the two differ a lot, this block will
compensate the average.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;outputScalingFactor&lt;/code&gt; to fade compensation in and out — for example to
reduce it where the axis is well supported. Its rate limit is in units per
second and holds its meaning across task rates. A single write is enough;
the fade runs to completion on its own.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/friction-model-stribeck-91b280b9.svg&#34; alt=&#34;Friction force against velocity for the three models: model 1 stepsdiscontinuously across zero to plus or minus 20 N, model 2 rises smoothlythrough zero, and model 0&amp;rsquo;s sliding steady state matches model 1 away fromrest.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the stiction force off the peak near zero and the Coulomb force off the
flat part beyond it.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The axis chatters or buzzes around zero velocity&lt;/td&gt;
&lt;td&gt;Model 1&amp;rsquo;s discontinuity at zero&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;model&lt;/code&gt; to 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis lurches from rest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Re-measure it with Setup step 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis still sticks at rest&lt;/td&gt;
&lt;td&gt;Stiction too low, or the dead band is swallowing the command&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;stictionFrictionForce&lt;/code&gt;; if that does not help, lower &lt;code&gt;preSlidingDisplacement&lt;/code&gt; to narrow the dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No friction force at all at very low speed&lt;/td&gt;
&lt;td&gt;Expected: models 0 and 1 have a dead band below the velocity in the blockquote&lt;/td&gt;
&lt;td&gt;Use model 2, which has no dead band&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis accelerates away instead of moving smoothly&lt;/td&gt;
&lt;td&gt;The sign is wrong, or friction is over-compensated&lt;/td&gt;
&lt;td&gt;Check the sign first; then halve every force parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis runs faster the harder it is pushed, with no friction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt; is negative&lt;/td&gt;
&lt;td&gt;Set it to zero or positive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation is right at low speed and too strong at high speed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Re-measure with Setup step 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation is right at high speed and too weak near rest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; too low, or &lt;code&gt;stribeckVelocity&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Raise stiction; lower the Stribeck velocity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; reads back higher than written&lt;/td&gt;
&lt;td&gt;Expected: it is corrected up to the Coulomb force&lt;/td&gt;
&lt;td&gt;Raise the Coulomb force, or accept the correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt; or &lt;code&gt;preSlidingDisplacement&lt;/code&gt; reads back different&lt;/td&gt;
&lt;td&gt;Outside the accepted band&lt;/td&gt;
&lt;td&gt;Work within the value you read back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output faded to zero but &lt;code&gt;isEnabled&lt;/code&gt; reads true&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; has been driven to 0&lt;/td&gt;
&lt;td&gt;Read that path; inside a feedforward controller it is written from a position table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output will not fade after writing &lt;code&gt;outputScalingFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set a positive rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A scaling change arrived as a slow ramp&lt;/td&gt;
&lt;td&gt;Expected: it is rate-limited&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; reads back exactly what was written while the force is still ramping&lt;/td&gt;
&lt;td&gt;Expected: the path is the command, and the block does not write it&lt;/td&gt;
&lt;td&gt;Watch &lt;code&gt;output&lt;/code&gt; to see the fade; there is no signal carrying the applied factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; was written above 1 or below 0 and the force did not follow&lt;/td&gt;
&lt;td&gt;Expected: the command is clamped to 0 – 1 on the way to the output, and the path keeps the raw value&lt;/td&gt;
&lt;td&gt;Write a value inside the range so the path and the effect agree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output ramps up over a moment after enabling, with model 0&lt;/td&gt;
&lt;td&gt;Expected: the deflection starts from zero and loads up&lt;/td&gt;
&lt;td&gt;Use model 1 or 2 if that transient matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The block behaves like model 1 when you configured model 2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt; was left unset, so it resolved to 1&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;model&lt;/code&gt; = 2 explicitly and check the log for a deprecation warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A warning about &lt;code&gt;useStaticFriction&lt;/code&gt; at startup&lt;/td&gt;
&lt;td&gt;An old configuration file still sets the retired parameter&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;model&lt;/code&gt; directly and remove it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction differs between directions and the block cannot match both&lt;/td&gt;
&lt;td&gt;The model is symmetric by construction&lt;/td&gt;
&lt;td&gt;Compensate the average, or handle the asymmetry upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every channel gets the same friction&lt;/td&gt;
&lt;td&gt;Expected: all parameters are shared across channels&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output went non-numeric&lt;/td&gt;
&lt;td&gt;A non-numeric velocity reached &lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source, then disable and re-enable to clear the state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for feedforward on a rotary axis on a 1 ms task, with
measured values substituted at steps 3 to 5 of Setup:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;model                   = 2
enable                  = true
coulombFrictionForce    = 10.0
stictionFrictionForce   = 20.0
viscousFrictionDamping  = 1.0
stribeckVelocity        = 0.01
coulombVelocity         = 0.001
preSlidingDisplacement  = 0.002
contactDamping          = 0.0
outputScalingFactorRate = 10.0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stictionFrictionForce&lt;/code&gt; ≥ &lt;code&gt;coulombFrictionForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Stiction is raised to match the Coulomb force. This is what keeps model 2&amp;rsquo;s curve the right way up&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coulombFrictionForce&lt;/code&gt; ≥ 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A negative value is replaced by 0&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stribeckVelocity&lt;/code&gt; within 1e-6 – 0.01&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preSlidingDisplacement&lt;/code&gt; within 1e-9 – 0.01&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge. The bound is the same for a linear or rotary axis&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contactDamping&lt;/code&gt; ≥ 0, &lt;code&gt;coulombVelocity&lt;/code&gt; ≥ 1e-6&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Replaced by the nearest edge&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;viscousFrictionDamping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; A negative value makes friction drive the axis instead of resisting it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactorRate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; 0 freezes the scaling factor at its present value — including at 0, which silences the output while &lt;code&gt;isEnabled&lt;/code&gt; still reads true. A negative value is unsafe&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputScalingFactor&lt;/code&gt; within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The command is clamped, then approached at &lt;code&gt;outputScalingFactorRate&lt;/code&gt;. &lt;strong&gt;The input path itself is never modified&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported, and the applied factor is not published; read the fade off &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the model computes. Bound it in the actuator&amp;rsquo;s own limiter&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;, or &lt;code&gt;enable&lt;/code&gt; false&lt;/td&gt;
&lt;td&gt;A disabled cycle clears the internal state completely&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channels&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Every channel uses the same parameters. One instance cannot model two different axes&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block logs one warning, at startup only: that a configuration file still
sets the retired &lt;code&gt;useStaticFriction&lt;/code&gt; parameter, naming the model it was mapped
to. Every other failure above shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.32.1 (340db23).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: BacklashCompensation</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/backlash-compensation/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/backlash-compensation/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;BacklashCompensation&lt;/code&gt; produces a position offset that follows the direction of
travel, to take up the lost motion in a gearbox. When an axis reverses, the
motor must cross the backlash gap before the load moves; this block adds the
offset in the direction the axis is going, so the position loop pre-loads the
correct flank of the gear.&lt;/p&gt;
&lt;p&gt;The offset &lt;strong&gt;slews&lt;/strong&gt; between its two values rather than stepping, so a reversal
does not put a position discontinuity into the loop.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — velocity, or any signed direction signal&amp;quot;]) --&amp;gt; B[&amp;quot;BacklashCompensation&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the position offset&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The offset is &lt;code&gt;+positionCorrection&lt;/code&gt; while the input is positive and
&lt;code&gt;-positionCorrection&lt;/code&gt; while it is negative. At standstill it &lt;strong&gt;holds&lt;/strong&gt; the
last direction, because the flank in contact does not change until the axis
moves the other way. The transition takes at least
$2 \times$ &lt;code&gt;positionCorrection&lt;/code&gt; $/$ &lt;code&gt;rateLimit&lt;/code&gt; seconds, and settles with a
time constant of $1/$&lt;code&gt;omega&lt;/code&gt; — 3.2 ms at the default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Three parameters must be set before this block does anything.&lt;/strong&gt; &lt;code&gt;enable&lt;/code&gt;
defaults to false, &lt;code&gt;positionCorrection&lt;/code&gt; to 0 and &lt;strong&gt;&lt;code&gt;rateLimit&lt;/code&gt; to 0, which
freezes the output&lt;/strong&gt;. All three are needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To switch compensation off, disable the block — do not zero
&lt;code&gt;positionCorrection&lt;/code&gt;.&lt;/strong&gt; A zero correction freezes the offset where it is rather
than removing it. See Limits and errors.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The direction signal. Inside an actuator loop this is the velocity target. Only its sign matters, after the dead zone. One element per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces every output to zero &lt;strong&gt;immediately, without slewing&lt;/strong&gt;. Use it for a runtime override; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;The position offset, one element per channel. It starts at zero after a controller start, and is &lt;strong&gt;not&lt;/strong&gt; cleared by a stop — so it holds a stale offset until the block runs again. Zero while disabled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. It does &lt;strong&gt;not&lt;/strong&gt; mean the block is producing an offset — a zero &lt;code&gt;positionCorrection&lt;/code&gt; or &lt;code&gt;rateLimit&lt;/code&gt; leaves it inert.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces every output to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionCorrection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;per channel&lt;/td&gt;
&lt;td&gt;The offset magnitude — normally half the measured backlash. &lt;strong&gt;A zero value makes the block skip that channel entirely&lt;/strong&gt;, freezing its output rather than clearing it. Not checked, so a large value is a large position shift.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rateLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0, per channel&lt;/td&gt;
&lt;td&gt;How fast the offset may slew. &lt;strong&gt;Zero freezes the output&lt;/strong&gt;, which is the default. Set it before anything else.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deadzone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s or rad/s&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward, per channel&lt;/td&gt;
&lt;td&gt;Input magnitudes below this count as standstill, so the offset holds. Raise it above the noise on your velocity signal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;314&lt;/td&gt;
&lt;td&gt;above 0, below 2/task period [s]&lt;/td&gt;
&lt;td&gt;How sharply the offset settles once it is within the rate limit. &lt;strong&gt;Shared by all channels&lt;/strong&gt;, unlike the three above. Not checked.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;returnToZero&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False holds the last direction&amp;rsquo;s offset at standstill, which is physically correct. True returns the offset to zero at standstill.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All six are persistent and survive a restart. &lt;strong&gt;No parameters exist below this
block.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Measure the backlash: drive the axis one way until the load moves, reverse
slowly, and record how far the motor turns before the load moves again.
That distance is the gap.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;positionCorrection&lt;/code&gt; to &lt;strong&gt;half&lt;/strong&gt; the gap, in motor units, for each
channel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;rateLimit&lt;/code&gt; to a slew rate the loop can absorb. Divide twice the
correction by the time you are willing to spend crossing it — 0.1 s is a
reasonable starting point.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3 is mandatory, not optional.&lt;/strong&gt; &lt;code&gt;rateLimit&lt;/code&gt; defaults to 0 and a
zero slew rate freezes the output. Without it the block is silently inert,
and &lt;code&gt;isEnabled&lt;/code&gt; will still read true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;deadzone&lt;/code&gt; above the noise on your velocity signal, so the offset does
not chatter between its two values at standstill.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;returnToZero&lt;/code&gt; false. Holding the last direction is correct: the gear
flank in contact does not change until the axis moves the other way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. Drive the axis one way and confirm &lt;code&gt;output&lt;/code&gt; slews to
&lt;code&gt;+positionCorrection&lt;/code&gt;, then reverse and confirm it slews to the negative
value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch the position loop&amp;rsquo;s error through a reversal, with and without the
block. The error at reversal should be smaller with it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Get the magnitude from the measurement in Setup step 1, not by feel. Too
much offset is as bad as none — it pre-loads the wrong flank.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;rateLimit&lt;/code&gt; against the position loop. Too fast and the offset itself
is a disturbance the loop must reject; too slow and the compensation arrives
after the reversal is over.&lt;/li&gt;
&lt;li&gt;Time the reversal: twice the correction divided by the rate limit. Compare
that against how long your axis actually spends reversing.&lt;/li&gt;
&lt;li&gt;Raise &lt;code&gt;deadzone&lt;/code&gt; until the offset stops chattering at standstill, and no
further. A large dead zone delays the compensation at low speed, where
backlash matters most.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;omega&lt;/code&gt; alone unless the settle at the end of the slew is visibly
slow. It only shapes the last part of the transition. Keep it below
2 divided by the task period.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;omega&lt;/code&gt; after a task-rate change — its safe ceiling scales with the
task period.&lt;/li&gt;
&lt;li&gt;If the axis has different backlash in each direction, this block cannot
represent it: it applies the same magnitude both ways. Compensate the
average.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/backlash-compensation-reversal-4e874d5e.svg&#34; alt=&#34;Offset through a direction reversal at three rate limits, with a correctionof 0.5: rate 20 snaps across almost at once, rate 5 takes about 0.2 s, and rate2 takes about 0.5 s.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the crossing time off any curve as the span between the two flat levels.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; stays at zero with &lt;code&gt;isEnabled&lt;/code&gt; true&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rateLimit&lt;/code&gt; is 0, or &lt;code&gt;positionCorrection&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set both; &lt;code&gt;rateLimit&lt;/code&gt; defaults to 0 and freezes the output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; froze at a value and will not change&lt;/td&gt;
&lt;td&gt;&lt;code&gt;positionCorrection&lt;/code&gt; was set back to 0, which skips the channel instead of clearing it&lt;/td&gt;
&lt;td&gt;Disable the block to clear the offset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset did not return to zero when compensation was switched off&lt;/td&gt;
&lt;td&gt;Same cause&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;enable&lt;/code&gt; false, not &lt;code&gt;positionCorrection&lt;/code&gt; = 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset chatters between its two values at standstill&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deadzone&lt;/code&gt; below the noise on the velocity signal&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;deadzone&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset swings at standstill even with a dead zone set&lt;/td&gt;
&lt;td&gt;&lt;code&gt;returnToZero&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false — holding the last direction is correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position jumped when the block was disabled&lt;/td&gt;
&lt;td&gt;Expected: disabling steps the output to zero without slewing&lt;/td&gt;
&lt;td&gt;Disable only at standstill, or accept the step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position jumped after a controller restart&lt;/td&gt;
&lt;td&gt;Expected: the offset is not cleared by a stop and holds a stale value&lt;/td&gt;
&lt;td&gt;Disable and re-enable once at start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position loop fights the offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rateLimit&lt;/code&gt; too high, so the offset itself is a disturbance&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;rateLimit&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compensation arrives too late at reversal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rateLimit&lt;/code&gt; too low, or &lt;code&gt;deadzone&lt;/code&gt; too large&lt;/td&gt;
&lt;td&gt;Raise the rate limit; lower the dead zone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The error at reversal got worse, not better&lt;/td&gt;
&lt;td&gt;Too much correction, so the wrong flank is pre-loaded&lt;/td&gt;
&lt;td&gt;Halve &lt;code&gt;positionCorrection&lt;/code&gt; and re-measure the backlash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset oscillates or grows during the transition&lt;/td&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; at or above 2 divided by the task period&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One channel adopted another channel&amp;rsquo;s direction&lt;/td&gt;
&lt;td&gt;A stationary channel can pick up a moving channel&amp;rsquo;s offset sign when &lt;code&gt;returnToZero&lt;/code&gt; is false&lt;/td&gt;
&lt;td&gt;Use one instance per channel, or set &lt;code&gt;returnToZero&lt;/code&gt; true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backlash differs by direction and the block cannot match it&lt;/td&gt;
&lt;td&gt;The magnitude is the same both ways by construction&lt;/td&gt;
&lt;td&gt;Compensate the average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A non-numeric value appeared on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A non-numeric velocity reached &lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disable the block for a cycle to clear it, then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for an axis with 1 mm of measured backlash on a 1 ms task,
crossing in about 0.1 s:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable             = true
positionCorrection = 0.0005
rateLimit          = 0.01
deadzone           = 0.001
omega              = 314.0
returnToZero       = false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work Setup step 1 with your own
axis.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rateLimit&lt;/code&gt; above 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked, and it defaults to 0.&lt;/strong&gt; A zero slew rate freezes the output permanently while &lt;code&gt;isEnabled&lt;/code&gt; still reads true&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionCorrection&lt;/code&gt; non-zero&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A zero value makes the block &lt;strong&gt;skip that channel&lt;/strong&gt;, so its output holds its last value instead of returning to zero&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionCorrection&lt;/code&gt; magnitude&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. The offset shifts both the measured position and the position target, so a large value is a large shift&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; below 2/task period [s]&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. At or above the bound the transition oscillates or grows instead of settling. 2000 on a 1 ms task&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Bounded in practice by &lt;code&gt;positionCorrection&lt;/code&gt;, but nothing enforces that&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disabling&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Steps the output to zero &lt;strong&gt;without slewing&lt;/strong&gt;, unlike a direction reversal&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offset across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared at start. There is no reset input — disabling is the only way to clear it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-numeric input&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not guarded. A disabled cycle clears the result&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel independence&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;The held direction at standstill is &lt;strong&gt;shared between channels&lt;/strong&gt;, so on a multi-channel instance a stationary channel can adopt a moving one&amp;rsquo;s sign. Use one instance per channel where this matters&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed once the controller starts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Gearbox</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/gearbox/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/gearbox/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Gearbox&lt;/code&gt; applies a transmission ratio in both directions, and applies it
&lt;strong&gt;the right way round for each kind of signal&lt;/strong&gt;: speeds and positions scale by
the ratio, torques and forces scale by its reciprocal. That is what a real
gearbox does, and getting it wrong by hand is the usual way a drivetrain
conversion goes astray.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputLoadSide&amp;quot;]) --&amp;gt; B[&amp;quot;Gearbox&amp;quot;]
    i2([&amp;quot;inputMotorSide&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;nLoadSide&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;nMotorSide&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputMotorSide&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputLoadSide&amp;quot;])
    B --&amp;gt; o3([&amp;quot;gain&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which channels are which is fixed when the controller is built&lt;/strong&gt;, by
position: the first group is kinematic (position, velocity), the rest are
mechanic (torque, force). &lt;strong&gt;The split is not visible in the tree&lt;/strong&gt; — you
cannot read it back, so check with whoever configured the machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;By default every channel is kinematic.&lt;/strong&gt; The mechanic count defaults to
zero, so unless it was set at build time nothing gets the reciprocal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A tooth count of 0 is refused, safely.&lt;/strong&gt; Write one and the block keeps the
previous value rather than dividing by zero. It does not tell you it did.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your instance may present its channels by name&lt;/strong&gt; instead of as arrays, at
&lt;code&gt;LoadToMotor/&amp;lt;name&amp;gt;/input&lt;/code&gt; and &lt;code&gt;MotorToLoad/&amp;lt;name&amp;gt;/output&lt;/code&gt;. Both forms behave
identically; only the paths differ.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;load-side unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Values on the load side, converted to the motor side. One per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor-side unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Values on the motor side, converted to the load side.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On a named instance these appear as &lt;code&gt;LoadToMotor/&amp;lt;name&amp;gt;/input&lt;/code&gt; and
&lt;code&gt;MotorToLoad/&amp;lt;name&amp;gt;/input&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor-side unit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputLoadSide&lt;/code&gt; converted. Kinematic channels are multiplied by &lt;code&gt;gain&lt;/code&gt;, mechanic channels divided by it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;load-side unit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputMotorSide&lt;/code&gt; converted, the other way round.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nLoadSide ÷ nMotorSide&lt;/code&gt;, recomputed every cycle. &lt;strong&gt;Read this to confirm the ratio you configured.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;The tooth count on the load side. Need not be a whole number — write the ratio directly if you prefer, with &lt;code&gt;nMotorSide&lt;/code&gt; at 1. &lt;strong&gt;A 0 is refused and the previous value kept.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;The tooth count on the motor side. Same treatment.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both are persistent and survive a controller restart. &lt;strong&gt;No parameters exist
below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A reduction of 10 to 1 — the motor turning ten times per output turn — is
&lt;code&gt;nLoadSide = 1&lt;/code&gt;, &lt;code&gt;nMotorSide = 10&lt;/code&gt;, giving a &lt;code&gt;gain&lt;/code&gt; of 0.1. A load-side speed of
1 then becomes a motor-side speed of 0.1&amp;hellip; which is backwards for a reduction,
so check &lt;code&gt;gain&lt;/code&gt; against a known speed before trusting the sense.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Establish which of your channels are kinematic and which are mechanic. This
was fixed when the controller was built and cannot be read back.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;nLoadSide&lt;/code&gt; and &lt;code&gt;nMotorSide&lt;/code&gt; from the drivetrain.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;gain&lt;/code&gt; back and confirm it is the ratio you expect.&lt;/li&gt;
&lt;li&gt;Put a known value on &lt;code&gt;inputLoadSide&lt;/code&gt; for a &lt;strong&gt;kinematic&lt;/strong&gt; channel and confirm
&lt;code&gt;outputMotorSide&lt;/code&gt; is that value times &lt;code&gt;gain&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Do the same for a &lt;strong&gt;mechanic&lt;/strong&gt; channel and confirm it is divided instead.
&lt;strong&gt;If it is multiplied, that channel is on the wrong side of the split&lt;/strong&gt; and
the controller needs rebuilding.&lt;/li&gt;
&lt;li&gt;Send a value through both directions and confirm it returns unchanged.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There is nothing dynamic to tune. Both parameters take effect on the next
cycle.&lt;/li&gt;
&lt;li&gt;Get the two tooth counts from the drivetrain drawing, not from a measured
ratio — the exact integers avoid rounding in long-running position
integrations.&lt;/li&gt;
&lt;li&gt;Verify the direction of the ratio with a slow move before running anything
fast. A ratio inverted by mistake is a factor of &lt;code&gt;gain&lt;/code&gt; squared out.&lt;/li&gt;
&lt;li&gt;Verify the kinematic and mechanic split by checking a torque channel
explicitly. This is the one thing this block does that a plain gain does
not, and the one thing you cannot see in the tree.&lt;/li&gt;
&lt;li&gt;Confirm the round trip. Load to motor and back should return the input
exactly.&lt;/li&gt;
&lt;li&gt;Nothing here depends on the task rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/gearbox-reciprocal-dec75c7d.svg&#34; alt=&#34;Output for a unit input against the ratio. A kinematic channel follows theratio; a mechanic channel follows its reciprocal, so they cross at a ratio ofone.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The two curves crossing at 1 is the whole point: at a ratio of one a gearbox
does nothing to either kind of signal.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A torque channel scaled the same way as a speed channel&lt;/td&gt;
&lt;td&gt;That channel is on the kinematic side of the split&lt;/td&gt;
&lt;td&gt;The split is fixed at build time — the controller needs rebuilding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything scaled the same way&lt;/td&gt;
&lt;td&gt;The mechanic channel count is 0, which is the default&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The conversion is inverted&lt;/td&gt;
&lt;td&gt;The two tooth counts are the wrong way round&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gain&lt;/code&gt; back and check it against a known speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tooth count I wrote did not take&lt;/td&gt;
&lt;td&gt;It was 0, which is refused&lt;/td&gt;
&lt;td&gt;Write a non-zero value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tooth count of 0 gave no warning&lt;/td&gt;
&lt;td&gt;Expected: the block keeps the previous value silently&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything became invalid&lt;/td&gt;
&lt;td&gt;A tooth count that is not a valid number passes the zero check and poisons &lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rewrite both counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every channel inverted&lt;/td&gt;
&lt;td&gt;A tooth count is negative, which is accepted&lt;/td&gt;
&lt;td&gt;Check both signs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The round trip does not return the input&lt;/td&gt;
&lt;td&gt;Something changed the ratio between the two directions&lt;/td&gt;
&lt;td&gt;Check nothing else writes the tooth counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I cannot tell which channels are kinematic&lt;/td&gt;
&lt;td&gt;The split is not published&lt;/td&gt;
&lt;td&gt;Ask whoever configured the machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The channel paths are names, not arrays&lt;/td&gt;
&lt;td&gt;Your instance was built with named channels&lt;/td&gt;
&lt;td&gt;Both forms behave identically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two named channels move together&lt;/td&gt;
&lt;td&gt;Two channels were given the same name at build time, which silently merges them&lt;/td&gt;
&lt;td&gt;The controller needs rebuilding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need to switch it off&lt;/td&gt;
&lt;td&gt;Not possible — there is no enable&lt;/td&gt;
&lt;td&gt;Set both tooth counts equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need a non-linear transmission&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;Lookup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a 10:1 reduction:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nLoadSide  = 1
nMotorSide = 10
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then read &lt;code&gt;gain&lt;/code&gt; back and confirm it is 0.1.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ratio&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nLoadSide&lt;/code&gt;, &lt;code&gt;nMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain = nLoadSide ÷ nMotorSide&lt;/code&gt;, recomputed every cycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kinematic channels&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Multiplied by &lt;code&gt;gain&lt;/code&gt; going to the motor side, divided coming back&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mechanic channels&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Divided&lt;/strong&gt; by &lt;code&gt;gain&lt;/code&gt; going to the motor side, multiplied coming back&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The kinematic/mechanic split&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not published.&lt;/strong&gt; Cannot be read or changed from the tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tooth count of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused&lt;/strong&gt; — the previous value is kept, so the block can never divide by zero&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A negative tooth count&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accepted. Inverts every channel&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; reads negative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. They pass the zero test and poison every channel&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Round trip&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The two directions are exact inverses&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel order&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;The split is by &lt;strong&gt;position&lt;/strong&gt;, so inserting a channel changes which side its neighbours fall on&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;There is no enable, disable or isEnabled&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Cannot be changed from the tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This block &lt;strong&gt;logs nothing during operation&lt;/strong&gt;. It may log at startup if the
tooth counts are zero. Every other condition above shows as a value on a trace,
or not at all.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Transformation</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation-3.32/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version c3-version--archived&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation-3.32/&#34; selected&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;
    Superseded. The current release is
    &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Transformation&lt;/code&gt; is the conversion stage between an actuator&amp;rsquo;s engineering
units and a drive&amp;rsquo;s raw units. It converts setpoints going out, converts and
conditions measurements coming back, and holds the gear ratio that relates the
two sides.&lt;/p&gt;
&lt;p&gt;It contains a transducer, and on the measurement side a linearisation table, an
IIR filter and a low-pass filter.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputSoftwareSide&amp;quot;]) --&amp;gt; B[&amp;quot;Transformation&amp;quot;]
    i2([&amp;quot;inputHardwareSide&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;gearRatio&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;referenceLoadSide&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputSoftwareSide&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputHardwareSide&amp;quot;])
    B --&amp;gt; s1([&amp;quot;transducer&amp;quot;])
    B --&amp;gt; s2([&amp;quot;actualLP1Filter&amp;quot;])
    B --&amp;gt; s3([&amp;quot;actualFilter, actualLinearizeLookup&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost everything you configure is in the sub-trees.&lt;/strong&gt; The sensor
resolution and referencing are under &lt;code&gt;transducer&lt;/code&gt;; the measurement filtering
is under &lt;code&gt;actualLP1Filter&lt;/code&gt; and &lt;code&gt;actualFilter&lt;/code&gt;; the linearisation table is
under &lt;code&gt;actualLinearizeLookup&lt;/code&gt;. This block itself has only two parameters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The filtered measurement and its velocity are not published.&lt;/strong&gt; The block
computes both and passes them to its parent in software, but neither appears
in the tree — so you can see &lt;code&gt;outputSoftwareSide&lt;/code&gt; before the low-pass, and
you cannot see the result after it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearRatio&lt;/code&gt; of 0 is not refused&lt;/strong&gt; and produces an invalid measurement path.
Never write 0 to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not every instance has every sub-tree.&lt;/strong&gt; A block configured for one
direction only gets the linearisation table and the IIR filter; a block that
converts both ways does not. If &lt;code&gt;actualLinearizeLookup&lt;/code&gt; is missing from your
tree, that is why.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The setpoint to send to the drive. Multiplied by &lt;code&gt;gearRatio&lt;/code&gt;, then converted to raw units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The raw sensor reading. Converted to actuator units, then divided by &lt;code&gt;gearRatio&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the machine is, in actuator units. &lt;strong&gt;On a both-ways instance this is unfiltered&lt;/strong&gt;; on a measurement-only instance it has been through the linearisation table and the IIR filter. Either way it has &lt;strong&gt;not&lt;/strong&gt; been through the low-pass.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The setpoint for the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks-side unit per actuator unit&lt;/td&gt;
&lt;td&gt;see below&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Multiplies the outgoing setpoint and divides the incoming measurement. &lt;strong&gt;A 0 is not refused and makes the measurement path invalid.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;actuator unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The position used when referencing. &lt;strong&gt;This is in actuator units&lt;/strong&gt;, while the transducer&amp;rsquo;s own reference parameters below are in motor-side units — do not confuse the two.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;The parameters that matter most are in the sub-trees:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transducer/ticksPerRevolution&lt;/code&gt;, &lt;code&gt;…/gainNum&lt;/code&gt;, &lt;code&gt;…/gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The sensor conversion. See the &lt;code&gt;Transducer&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transducer/offset&lt;/code&gt;, &lt;code&gt;transducer/referencing/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Referencing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The measurement low-pass cut-off, in radians per second.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualFilter/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The IIR filter&amp;rsquo;s numerator and denominator — used as a notch, typically. Measurement-only instances.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLinearizeLookup/x&lt;/code&gt;, &lt;code&gt;…/y&lt;/code&gt;, &lt;code&gt;…/numPoints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The sensor linearisation table, &lt;strong&gt;six points&lt;/strong&gt;. Measurement-only instances.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set up the transducer first — &lt;code&gt;transducer/ticksPerRevolution&lt;/code&gt; and the two
gain factors. Verify it on its own before touching this block.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;gearRatio&lt;/code&gt; from the drivetrain between the sensor and the actuator&amp;rsquo;s
output. &lt;strong&gt;Never write 0.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Move the machine a known distance by hand and confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt;
changes by that amount &lt;strong&gt;in actuator units&lt;/strong&gt;. This checks the transducer and
the gear ratio together.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;actualLP1Filter/omega&lt;/code&gt; — go to Tuning.&lt;/li&gt;
&lt;li&gt;If your instance has one, set the linearisation table under
&lt;code&gt;actualLinearizeLookup&lt;/code&gt; from measurements of the sensor&amp;rsquo;s error.&lt;/li&gt;
&lt;li&gt;If your instance has one, set the IIR filter under &lt;code&gt;actualFilter&lt;/code&gt; to notch
out any structural frequency you need removed. Leave it as a pass-through
until you know you need it.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;referenceLoadSide&lt;/code&gt; and reference the machine, remembering this value is
in &lt;strong&gt;actuator&lt;/strong&gt; units.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Get the conversion exactly right before any filtering. A gain error looks
like a scaling problem at every speed; a filter problem only appears when
things move.&lt;/li&gt;
&lt;li&gt;Verify the conversion over a long move, not a short one.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;actualLP1Filter/omega&lt;/code&gt; from the noise on your measurement and the
bandwidth your control loop needs. Lower removes more noise and adds more
lag.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You cannot see the low-pass&amp;rsquo;s output in the tree&lt;/strong&gt;, so judge it by its
effect on the loop rather than by watching the signal: too low a cut-off
shows as sluggish or oscillatory position control.&lt;/li&gt;
&lt;li&gt;Use the IIR filter for a specific structural frequency, not for general
smoothing — that is the low-pass&amp;rsquo;s job. Design it as a discrete-time filter
and enter the coefficients directly.&lt;/li&gt;
&lt;li&gt;Set the linearisation table only after the gain and offset are right. It
corrects the shape of the sensor&amp;rsquo;s error, not its scale.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the low-pass cut-off is in
radians per second and does not move, but the IIR coefficients are
discrete-time and &lt;strong&gt;do&lt;/strong&gt; depend on the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/transformation-measurement-chain-1fcacde0.svg&#34; alt=&#34;The measurement path: a noisy converted input, the low-pass output, and thecompensated output the block passes on. The compensation recovers most of thedelay the filter adds.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The block corrects the low-pass&amp;rsquo;s lag by one sample before handing the result
on, which is why the measurement the controller sees is less delayed than the
filter alone would give.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; is 0, which is not refused&lt;/td&gt;
&lt;td&gt;Set a non-zero ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is proportionally wrong&lt;/td&gt;
&lt;td&gt;The gear ratio or the transducer conversion&lt;/td&gt;
&lt;td&gt;Check the transducer alone first, then the ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine moves the wrong way&lt;/td&gt;
&lt;td&gt;A sign error in the gear ratio or the transducer gain&lt;/td&gt;
&lt;td&gt;Use a negative value on one of them, not both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The setpoint and the measurement disagree by a constant&lt;/td&gt;
&lt;td&gt;The transducer offset, or referencing has not been done&lt;/td&gt;
&lt;td&gt;Reference the machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I cannot see the filtered measurement&lt;/td&gt;
&lt;td&gt;It is not published&lt;/td&gt;
&lt;td&gt;Judge the filter by its effect on the control loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLinearizeLookup&lt;/code&gt; is missing from my tree&lt;/td&gt;
&lt;td&gt;Your instance converts both ways, and only measurement-only instances have it&lt;/td&gt;
&lt;td&gt;Nothing — it is fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualFilter&lt;/code&gt; is missing&lt;/td&gt;
&lt;td&gt;Same reason&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is noisy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt; is too high&lt;/td&gt;
&lt;td&gt;Lower it, and accept more lag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position control became sluggish or unstable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt; is too low for the loop&lt;/td&gt;
&lt;td&gt;Raise it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The IIR filter did nothing after a task-rate change&lt;/td&gt;
&lt;td&gt;Its coefficients are discrete-time and depend on the rate&lt;/td&gt;
&lt;td&gt;Recalculate and re-enter them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing put the machine in the wrong place&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt; is in &lt;strong&gt;actuator&lt;/strong&gt; units, unlike the transducer&amp;rsquo;s own reference&lt;/td&gt;
&lt;td&gt;Check which one you set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stopped responding after referencing&lt;/td&gt;
&lt;td&gt;The transducer holds its output until the positions agree&lt;/td&gt;
&lt;td&gt;See the &lt;code&gt;Transducer&lt;/code&gt; page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need to bypass the conversion&lt;/td&gt;
&lt;td&gt;Not possible — there is no enable, and the two sides are in different units&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gearRatio&lt;/code&gt; to 1 and check the transducer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a direct-driven axis:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearRatio               = 1.0
referenceLoadSide       = 0.0
actualLP1Filter/omega   = 200.0
transducer/…            = &amp;lt;see the Transducer page&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; and the transducer&lt;/td&gt;
&lt;td&gt;The setpoint is multiplied by the ratio, the measurement divided by it&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;, &lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The measurement path becomes invalid&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked at this level. The sub-modules behave differently: the lookup freezes, the filters propagate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurement filtering&lt;/td&gt;
&lt;td&gt;The three sub-trees&lt;/td&gt;
&lt;td&gt;Linearisation, then the IIR filter, then the low-pass — but &lt;strong&gt;only a measurement-only instance has the first two&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filtered outputs&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Computed and not published.&lt;/strong&gt; The filtered measurement and its velocity go to the parent in software only&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter lag&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The block advances the filtered signal by one sample using the filter&amp;rsquo;s own derivative, recovering most of the low-pass&amp;rsquo;s delay&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt; units&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Actuator units&lt;/strong&gt;, unlike the transducer&amp;rsquo;s own reference parameters&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-modules present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Depends on which directions the instance converts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;There is no enable, disable or isEnabled, despite what older documentation said&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One axis per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This block &lt;strong&gt;logs nothing itself&lt;/strong&gt;. The transducer below it logs during
referencing. Every other condition above shows as a value on a trace, or not at
all.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Transformation</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transformation-3.32/&#34;&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;Current release&lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Transformation&lt;/code&gt; is the conversion stage between an actuator&amp;rsquo;s engineering
units and a drive&amp;rsquo;s raw units. It converts setpoints going out, converts and
conditions measurements coming back, and holds the gear ratio that relates the
two sides.&lt;/p&gt;
&lt;p&gt;It contains a transducer, and on the measurement side a linearisation table, an
IIR filter and a low-pass filter, applied in that order.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputSoftwareSide&amp;quot;]) --&amp;gt; B[&amp;quot;Transformation&amp;quot;]
    i2([&amp;quot;inputHardwareSide&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;gearRatio&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;referenceLoadSide&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputSoftwareSide&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputHardwareSide&amp;quot;])
    B --&amp;gt; s1([&amp;quot;transducer&amp;quot;])
    B --&amp;gt; s2([&amp;quot;actualFilter, actualLP1Filter&amp;quot;])
    B --&amp;gt; s3([&amp;quot;actualLinearizeLookup&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost everything you configure is in the sub-trees.&lt;/strong&gt; The sensor
resolution and referencing are under &lt;code&gt;transducer&lt;/code&gt;; the measurement filtering
is under &lt;code&gt;actualLP1Filter&lt;/code&gt; and &lt;code&gt;actualFilter&lt;/code&gt;; the linearisation table is
under &lt;code&gt;actualLinearizeLookup&lt;/code&gt;. This block itself has only two parameters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The filtered measurement and its velocity are not published.&lt;/strong&gt; The block
computes both and passes them to its parent in software, but neither appears
in the tree — so you can see &lt;code&gt;outputSoftwareSide&lt;/code&gt; before the low-pass, and
you cannot see the result after it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearRatio&lt;/code&gt; of 0 is not refused&lt;/strong&gt; and produces an invalid measurement path.
Never write 0 to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not every instance has every sub-tree.&lt;/strong&gt; The linearisation table is created
only for a block that converts one way, towards software; a block that
converts both ways does not have it. If &lt;code&gt;actualLinearizeLookup&lt;/code&gt; is missing
from your tree, that is why. &lt;code&gt;actualFilter&lt;/code&gt; and &lt;code&gt;actualLP1Filter&lt;/code&gt; are present
on every block that has a measurement side at all, whichever direction it
converts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;the-measurement-chain&#34;&gt;The measurement chain&lt;/h2&gt;
&lt;p&gt;The measurement side runs in a fixed order:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;inputHardwareSide → transducer → actualLinearizeLookup → actualFilter → actualLP1Filter
                                 (one-way blocks only)     (IIR)         (low-pass)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt; is published &lt;strong&gt;after &lt;code&gt;actualFilter&lt;/code&gt; and before
&lt;code&gt;actualLP1Filter&lt;/code&gt;&lt;/strong&gt;, so the leaf you can trace shows the IIR&amp;rsquo;s effect but not
the low-pass&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;actualFilter&lt;/code&gt; is a full IIR of up to order 6&lt;/strong&gt; — two notches plus a
low-pass — and it is the right place for a structural resonance or a
mains-frequency pickup, because it acts on the measurement before
&lt;code&gt;actualLP1Filter&lt;/code&gt; derives a velocity from it. It is &lt;strong&gt;pass-through by default&lt;/strong&gt;
(order 1, &lt;code&gt;num[0]&lt;/code&gt; = &lt;code&gt;den[0]&lt;/code&gt; = 1), so an unconfigured block conditions nothing
here.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A switched-off &lt;code&gt;actualFilter&lt;/code&gt; is a bypass, not a mute.&lt;/strong&gt; On its own an
&lt;code&gt;IIRFilter&lt;/code&gt; outputs &lt;strong&gt;zero&lt;/strong&gt; when it is disabled, when &lt;code&gt;order&lt;/code&gt; is 0, or when
&lt;code&gt;den[0]&lt;/code&gt; is 0 — and a measured position that reads zero is far worse than an
unfiltered one. This block therefore guards it: whenever the filter is not
actually filtering, the unfiltered measurement is passed to the low-pass
instead. You can disable &lt;code&gt;actualFilter&lt;/code&gt; safely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The setpoint to send to the drive. Multiplied by &lt;code&gt;gearRatio&lt;/code&gt;, then converted to raw units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The raw sensor reading. Converted to actuator units, then divided by &lt;code&gt;gearRatio&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the machine is, in actuator units. &lt;strong&gt;On a both-ways instance this is unfiltered&lt;/strong&gt;; on a measurement-only instance it has been through the linearisation table and the IIR filter. Either way it has &lt;strong&gt;not&lt;/strong&gt; been through the low-pass.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The setpoint for the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks-side unit per actuator unit&lt;/td&gt;
&lt;td&gt;see below&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Multiplies the outgoing setpoint and divides the incoming measurement. &lt;strong&gt;A 0 is not refused and makes the measurement path invalid.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;actuator unit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The position used when referencing. &lt;strong&gt;This is in actuator units&lt;/strong&gt;, while the transducer&amp;rsquo;s own reference parameters below are in motor-side units — do not confuse the two.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;The parameters that matter most are in the sub-trees:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transducer/ticksPerRevolution&lt;/code&gt;, &lt;code&gt;…/gainNum&lt;/code&gt;, &lt;code&gt;…/gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The sensor conversion. See the &lt;code&gt;Transducer&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transducer/offset&lt;/code&gt;, &lt;code&gt;transducer/referencing/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Referencing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The measurement low-pass cut-off, in radians per second.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualFilter/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The IIR filter&amp;rsquo;s numerator and denominator — used as a notch, typically. Measurement-only instances.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLinearizeLookup/x&lt;/code&gt;, &lt;code&gt;…/y&lt;/code&gt;, &lt;code&gt;…/numPoints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The sensor linearisation table, &lt;strong&gt;six points&lt;/strong&gt;. Measurement-only instances.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set up the transducer first — &lt;code&gt;transducer/ticksPerRevolution&lt;/code&gt; and the two
gain factors. Verify it on its own before touching this block.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;gearRatio&lt;/code&gt; from the drivetrain between the sensor and the actuator&amp;rsquo;s
output. &lt;strong&gt;Never write 0.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Move the machine a known distance by hand and confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt;
changes by that amount &lt;strong&gt;in actuator units&lt;/strong&gt;. This checks the transducer and
the gear ratio together.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;actualLP1Filter/omega&lt;/code&gt; — go to Tuning.&lt;/li&gt;
&lt;li&gt;If your instance has one, set the linearisation table under
&lt;code&gt;actualLinearizeLookup&lt;/code&gt; from measurements of the sensor&amp;rsquo;s error.&lt;/li&gt;
&lt;li&gt;If your instance has one, set the IIR filter under &lt;code&gt;actualFilter&lt;/code&gt; to notch
out any structural frequency you need removed. Leave it as a pass-through
until you know you need it.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;referenceLoadSide&lt;/code&gt; and reference the machine, remembering this value is
in &lt;strong&gt;actuator&lt;/strong&gt; units.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Get the conversion exactly right before any filtering. A gain error looks
like a scaling problem at every speed; a filter problem only appears when
things move.&lt;/li&gt;
&lt;li&gt;Verify the conversion over a long move, not a short one.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;actualLP1Filter/omega&lt;/code&gt; from the noise on your measurement and the
bandwidth your control loop needs. Lower removes more noise and adds more
lag.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You cannot see the low-pass&amp;rsquo;s output in the tree&lt;/strong&gt;, so judge it by its
effect on the loop rather than by watching the signal: too low a cut-off
shows as sluggish or oscillatory position control.&lt;/li&gt;
&lt;li&gt;Use the IIR filter for a specific structural frequency, not for general
smoothing — that is the low-pass&amp;rsquo;s job. Design it as a discrete-time filter
and enter the coefficients directly.&lt;/li&gt;
&lt;li&gt;Set the linearisation table only after the gain and offset are right. It
corrects the shape of the sensor&amp;rsquo;s error, not its scale.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the low-pass cut-off is in
radians per second and does not move, but the IIR coefficients are
discrete-time and &lt;strong&gt;do&lt;/strong&gt; depend on the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/transformation-measurement-chain-1fcacde0.svg&#34; alt=&#34;The measurement path: a noisy converted input, the low-pass output, and thecompensated output the block passes on. The compensation recovers most of thedelay the filter adds.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The block corrects the low-pass&amp;rsquo;s lag by one sample before handing the result
on, which is why the measurement the controller sees is less delayed than the
filter alone would give.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; is 0, which is not refused&lt;/td&gt;
&lt;td&gt;Set a non-zero ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is proportionally wrong&lt;/td&gt;
&lt;td&gt;The gear ratio or the transducer conversion&lt;/td&gt;
&lt;td&gt;Check the transducer alone first, then the ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine moves the wrong way&lt;/td&gt;
&lt;td&gt;A sign error in the gear ratio or the transducer gain&lt;/td&gt;
&lt;td&gt;Use a negative value on one of them, not both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The setpoint and the measurement disagree by a constant&lt;/td&gt;
&lt;td&gt;The transducer offset, or referencing has not been done&lt;/td&gt;
&lt;td&gt;Reference the machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I cannot see the filtered measurement&lt;/td&gt;
&lt;td&gt;It is not published&lt;/td&gt;
&lt;td&gt;Judge the filter by its effect on the control loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualLinearizeLookup&lt;/code&gt; is missing from my tree&lt;/td&gt;
&lt;td&gt;Your instance converts both ways, and only measurement-only instances have it&lt;/td&gt;
&lt;td&gt;Nothing — it is fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualFilter&lt;/code&gt; is missing&lt;/td&gt;
&lt;td&gt;Same reason&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The measurement is noisy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt; is too high&lt;/td&gt;
&lt;td&gt;Lower it, and accept more lag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position control became sluggish or unstable&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actualLP1Filter/omega&lt;/code&gt; is too low for the loop&lt;/td&gt;
&lt;td&gt;Raise it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The IIR filter did nothing after a task-rate change&lt;/td&gt;
&lt;td&gt;Its coefficients are discrete-time and depend on the rate&lt;/td&gt;
&lt;td&gt;Recalculate and re-enter them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing put the machine in the wrong place&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt; is in &lt;strong&gt;actuator&lt;/strong&gt; units, unlike the transducer&amp;rsquo;s own reference&lt;/td&gt;
&lt;td&gt;Check which one you set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stopped responding after referencing&lt;/td&gt;
&lt;td&gt;The transducer holds its output until the positions agree&lt;/td&gt;
&lt;td&gt;See the &lt;code&gt;Transducer&lt;/code&gt; page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need to bypass the conversion&lt;/td&gt;
&lt;td&gt;Not possible — there is no enable, and the two sides are in different units&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gearRatio&lt;/code&gt; to 1 and check the transducer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a direct-driven axis:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearRatio               = 1.0
referenceLoadSide       = 0.0
actualLP1Filter/omega   = 200.0
transducer/…            = &amp;lt;see the Transducer page&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; and the transducer&lt;/td&gt;
&lt;td&gt;The setpoint is multiplied by the ratio, the measurement divided by it&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;, &lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The measurement path becomes invalid&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked at this level. The sub-modules behave differently: the lookup freezes, the filters propagate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurement filtering&lt;/td&gt;
&lt;td&gt;The three sub-trees&lt;/td&gt;
&lt;td&gt;Linearisation, then the IIR filter, then the low-pass — but &lt;strong&gt;only a measurement-only instance has the linearisation table&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actualFilter&lt;/code&gt; switched off&lt;/td&gt;
&lt;td&gt;Guarded by this block&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;unfiltered&lt;/strong&gt; measurement is passed to the low-pass. A bare &lt;code&gt;IIRFilter&lt;/code&gt; would output 0 here&lt;/td&gt;
&lt;td&gt;The filter&amp;rsquo;s own &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filtered outputs&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Computed and not published.&lt;/strong&gt; The filtered measurement and its velocity go to the parent in software only&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter lag&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The block advances the filtered signal by one sample using the filter&amp;rsquo;s own derivative, recovering most of the low-pass&amp;rsquo;s delay&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referenceLoadSide&lt;/code&gt; units&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Actuator units&lt;/strong&gt;, unlike the transducer&amp;rsquo;s own reference parameters&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-modules present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Depends on which directions the instance converts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;There is no enable, disable or isEnabled, despite what older documentation said&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One axis per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This block &lt;strong&gt;logs nothing itself&lt;/strong&gt;. The transducer below it logs during
referencing. Every other condition above shows as a value on a trace, or not at
all.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Transducer</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer-3.32/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version c3-version--archived&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer-3.32/&#34; selected&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;
    Superseded. The current release is
    &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Transducer&lt;/code&gt; converts between a sensor&amp;rsquo;s raw units — encoder ticks, resolver
counts, an analogue reading — and engineering units, in both directions. It
also handles &lt;strong&gt;referencing&lt;/strong&gt;: telling the machine that a particular raw reading
corresponds to a particular real position.&lt;/p&gt;
&lt;p&gt;For an incremental sensor it can additionally store that position to disk, so
the machine knows where it is after a power cycle.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputSoftwareSide&amp;quot;]) --&amp;gt; B[&amp;quot;Transducer&amp;quot;]
    i2([&amp;quot;inputHardwareSide&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;persistenceEvent&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;disablePersistence&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;referencing/softwareReferenceExternal&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;gainNum, gainDen, ticksPerRevolution, offset&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;referencing/softwareReferenceInternal, useSoftwareReferenceExternal&amp;quot;]) --&amp;gt; B
    p3([&amp;quot;enablePersistence, deltaTicksMax, numberOfPolePairs, enableSingleTurnCounter&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputSoftwareSide&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputHardwareSide&amp;quot;])
    B --&amp;gt; o3([&amp;quot;gain&amp;quot;])
    B --&amp;gt; o4([&amp;quot;referencing/hardwareSnapshot&amp;quot;])
    B --&amp;gt; o5([&amp;quot;atStoredTicks, deltaTicks, singleTurnCounter&amp;quot;])
    B --&amp;gt; o6([&amp;quot;isPersistenceEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;After referencing, the output to the drive is deliberately frozen&lt;/strong&gt; until
the commanded and measured positions have agreed closely for a moment. This
stops the drive being stepped by the whole size of the correction. If the two
never agree, &lt;strong&gt;the output stays frozen indefinitely&lt;/strong&gt; with nothing reported —
so if a drive stops responding right after referencing, this is where to
look.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; of 0 breaks the conversion.&lt;/strong&gt; The other two gain
factors refuse a zero and keep their previous value; this one does not. Never
write 0 to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Persistence is switched off at every startup&lt;/strong&gt;, whatever &lt;code&gt;enablePersistence&lt;/code&gt;
says. Your application must re-enable it after each start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Some of the parameters below only exist on an encoder-type instance.&lt;/strong&gt; The
type is fixed when the controller is built.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The setpoint to convert to raw units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The sensor reading to convert to engineering units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;persistenceEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;enumeration&lt;/td&gt;
&lt;td&gt;Commands a store or load of the position file. Encoder instances only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disablePersistence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Suspends the file handling. &lt;strong&gt;Forced true at every startup.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/softwareReferenceExternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value the captured raw reading should correspond to, when &lt;code&gt;useSoftwareReferenceExternal&lt;/code&gt; is true.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;The converted sensor reading — where the machine thinks it is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The converted setpoint, for the drive. &lt;strong&gt;Held after referencing&lt;/strong&gt; until the interlock clears.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per engineering unit&lt;/td&gt;
&lt;td&gt;The combined conversion factor. &lt;strong&gt;Read this back to check your three factors multiply to what you expect.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/hardwareSnapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The raw reading captured for referencing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The position from the file matches the sensor. &lt;strong&gt;Reads true whenever persistence is off&lt;/strong&gt;, so read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; alongside it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deltaTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;How far the sensor is from the stored position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;singleTurnCounter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;revolutions&lt;/td&gt;
&lt;td&gt;Whole turns counted, for a sensor that only reports position within one turn.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isPersistenceEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The file handling is active.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Numerator of the conversion. &lt;strong&gt;A 0 is refused and the previous value kept.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Denominator. &lt;strong&gt;A value of 0 or below is refused and the previous value kept.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;The sensor&amp;rsquo;s resolution. Multiplied into the gain. &lt;strong&gt;A negative value is used as its magnitude, but 0 is not refused and will break the conversion.&lt;/strong&gt; Encoder instances only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The raw reading that corresponds to zero engineering units. &lt;strong&gt;Referencing overwrites this&lt;/strong&gt; — save your configuration afterwards, or a reload will undo it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/softwareReferenceInternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value referencing aims at, when the external one is not selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/useSoftwareReferenceExternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Whether to use the external reference input instead of the internal parameter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enablePersistence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Whether to store the position to a file. &lt;strong&gt;Only for a sensor that does not remember its own absolute position.&lt;/strong&gt; Turn it off for an absolute encoder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-1, or 0 upward&lt;/td&gt;
&lt;td&gt;How far the sensor may differ from the stored position and still be trusted. &lt;strong&gt;The default of -1 means no check.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numberOfPolePairs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;For resolver-type sensors, how many electrical revolutions make one mechanical one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enableSingleTurnCounter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Counts whole turns, for a sensor that reports only within one turn.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart. &lt;strong&gt;No parameters exist
below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Establish your sensor&amp;rsquo;s resolution and set &lt;code&gt;ticksPerRevolution&lt;/code&gt;. &lt;strong&gt;Never
write 0.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt; to express the rest of the conversion — a lead
screw pitch, a pulley ratio, a unit change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read &lt;code&gt;gain&lt;/code&gt; back. It should be &lt;code&gt;gainNum ÷ gainDen × ticksPerRevolution&lt;/code&gt;, and
it is the number of ticks per engineering unit. &lt;strong&gt;Check it against a hand
calculation before going further.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move the machine a known distance by hand and confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt;
changes by that amount.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decide whether you need persistence. &lt;strong&gt;An absolute encoder does not&lt;/strong&gt; —
leave &lt;code&gt;enablePersistence&lt;/code&gt; false. A resolver or incremental encoder does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reference the machine: bring it to a known position, set the reference
value, and trigger referencing from your application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt; now reads the true position.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;After referencing, the drive output is frozen&lt;/strong&gt; until the commanded and
measured positions agree. Expect a short pause before the axis responds
again, and investigate if it never does.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Save your configuration&lt;/strong&gt;, or the referenced offset is lost on the next
reload.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There is nothing dynamic to tune. Get the conversion right and the rest
follows.&lt;/li&gt;
&lt;li&gt;Derive the three gain factors from the drivetrain rather than measuring
them — exact integers avoid rounding in a long-running position count.&lt;/li&gt;
&lt;li&gt;Verify over a long move, not a short one. A 1% gain error is invisible over
a millimetre and obvious over a metre.&lt;/li&gt;
&lt;li&gt;Check the direction. A sensor wired backwards shows as &lt;code&gt;outputSoftwareSide&lt;/code&gt;
moving the wrong way, and is fixed with a negative &lt;code&gt;gainNum&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reference at a repeatable physical feature — a hard stop, a switch — not at
an arbitrary position.&lt;/li&gt;
&lt;li&gt;If you use persistence, set &lt;code&gt;deltaTicksMax&lt;/code&gt; to how far the machine could
plausibly be moved while powered down. Leave it at -1 only if you accept the
stored position unconditionally.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;atStoredTicks&lt;/code&gt; after each start, together with
&lt;code&gt;isPersistenceEnabled&lt;/code&gt; — the first reads true whenever the second is false.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/transducer-conversion-08d6d3d6.svg&#34; alt=&#34;Hardware ticks against engineering units for a 4096-tick sensor. The offsetshifts the line without changing its slope; the gain is theslope.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Referencing moves the line up or down. It never changes the slope.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The conversion is wildly wrong or invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; is 0, which is not refused&lt;/td&gt;
&lt;td&gt;Set it to the sensor&amp;rsquo;s real resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A gain factor I wrote did not take&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; was 0, or &lt;code&gt;gainDen&lt;/code&gt; was 0 or negative — both are refused&lt;/td&gt;
&lt;td&gt;Read them back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine moves the wrong way&lt;/td&gt;
&lt;td&gt;The gain&amp;rsquo;s sign is wrong&lt;/td&gt;
&lt;td&gt;Use a negative &lt;code&gt;gainNum&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The distance is proportionally wrong&lt;/td&gt;
&lt;td&gt;The three factors do not multiply to the right ratio&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;gain&lt;/code&gt; against a hand calculation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stopped responding after referencing&lt;/td&gt;
&lt;td&gt;Expected briefly: the output is held until the positions agree&lt;/td&gt;
&lt;td&gt;If it never resumes, the positions are not converging — check the drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The referenced position was lost after a restart&lt;/td&gt;
&lt;td&gt;Referencing overwrites &lt;code&gt;offset&lt;/code&gt;, and a configuration reload restores the saved value&lt;/td&gt;
&lt;td&gt;Save the configuration after referencing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt; reads true and I do not trust it&lt;/td&gt;
&lt;td&gt;It reads true whenever persistence is off&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence is off even though I enabled it&lt;/td&gt;
&lt;td&gt;It is forced off at every startup&lt;/td&gt;
&lt;td&gt;Re-enable it from your application after each start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stored position is accepted when it should not be&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt; is -1, which means no check&lt;/td&gt;
&lt;td&gt;Set a real window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position jumps by a whole revolution&lt;/td&gt;
&lt;td&gt;The single-turn counter is needed, or miscounted&lt;/td&gt;
&lt;td&gt;Enable it and set &lt;code&gt;numberOfPolePairs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything became invalid&lt;/td&gt;
&lt;td&gt;A gain factor that is not a valid number passes the zero checks&lt;/td&gt;
&lt;td&gt;Rewrite all three&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some parameters are missing from the tree&lt;/td&gt;
&lt;td&gt;Your instance is not an encoder type&lt;/td&gt;
&lt;td&gt;The type is fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need a non-linear conversion&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Put a &lt;code&gt;Lookup&lt;/code&gt; after it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a 4096-tick encoder on a direct-driven axis:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ticksPerRevolution = 4096
gainNum            = 1
gainDen            = 1
offset             = 0
enablePersistence  = false
deltaTicksMax      = -1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then read &lt;code&gt;gain&lt;/code&gt; back and confirm it is 4096.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;, &lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Their product is the ticks per engineering unit, both directions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused&lt;/strong&gt; — the previous value is kept&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainDen&lt;/code&gt; of 0 or below&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused&lt;/strong&gt; — the previous value is kept&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The gain becomes 0 and &lt;strong&gt;the conversion divides by zero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. They pass the zero tests and poison both directions&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing&lt;/td&gt;
&lt;td&gt;The reference value and a captured raw snapshot&lt;/td&gt;
&lt;td&gt;Solves for the &lt;code&gt;offset&lt;/code&gt; that makes them correspond, and &lt;strong&gt;overwrites &lt;code&gt;offset&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referencing/hardwareSnapshot&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output after referencing&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Held&lt;/strong&gt; until the commanded and measured positions agree closely for a short, fixed period. &lt;strong&gt;No timeout, nothing reported if they never do&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence at startup&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Always disabled&lt;/strong&gt;, whatever the parameter says&lt;/td&gt;
&lt;td&gt;&lt;code&gt;isPersistenceEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stored position check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compares the sensor against the stored position. &lt;strong&gt;A default of -1 means no check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;, &lt;code&gt;deltaTicks&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Reads true when persistence is off&lt;/strong&gt;, so it means &amp;ldquo;nothing to distrust&amp;rdquo;, not &amp;ldquo;a check passed&amp;rdquo;&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence file errors&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;A zero or negative &lt;code&gt;ticksPerRevolution&lt;/code&gt; skips the stored-position check, &lt;strong&gt;and is logged&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controller log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instance type&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;A non-encoder instance has no persistence, no turn counter and no &lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One sensor per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block logs when a stored-position check is skipped for a bad
&lt;code&gt;ticksPerRevolution&lt;/code&gt;, and logs the loaded position at startup. Every other
condition above shows as a value on a trace, or not at all.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: Transducer</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer-3.32/&#34;&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;Current release&lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;Transducer&lt;/code&gt; converts between a sensor&amp;rsquo;s raw units — encoder ticks, resolver
counts, an analogue reading — and engineering units, in both directions. It
also handles &lt;strong&gt;referencing&lt;/strong&gt;: telling the machine that a particular raw reading
corresponds to a particular real position.&lt;/p&gt;
&lt;p&gt;For an incremental sensor it can additionally store that position to disk, so
the machine knows where it is after a power cycle.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;inputSoftwareSide&amp;quot;]) --&amp;gt; B[&amp;quot;Transducer&amp;quot;]
    i2([&amp;quot;inputHardwareSide&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;persistenceEvent&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;disablePersistence&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;referencing/softwareReferenceExternal&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;gainNum, gainDen, ticksPerRevolution, offset&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;referencing/softwareReferenceInternal, useSoftwareReferenceExternal&amp;quot;]) --&amp;gt; B
    p4([&amp;quot;referencing/reconnectToleranceHardwareSide&amp;quot;]) --&amp;gt; B
    p3([&amp;quot;enablePersistence, deltaTicksMax, numberOfPolePairs, enableSingleTurnCounter&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputSoftwareSide&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputHardwareSide&amp;quot;])
    B --&amp;gt; o3([&amp;quot;gain&amp;quot;])
    B --&amp;gt; o4([&amp;quot;referencing/hardwareSnapshot&amp;quot;])
    B --&amp;gt; o5([&amp;quot;atStoredTicks, deltaTicks, singleTurnCounter&amp;quot;])
    B --&amp;gt; o7([&amp;quot;referencing/isOpenLoop&amp;quot;])
    B --&amp;gt; o6([&amp;quot;isPersistenceEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;After referencing, the output to the drive is deliberately frozen&lt;/strong&gt; until
the software side has been re-based onto the new offset. Precisely: until
recomputing the hardware output would land within
&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; of the &lt;strong&gt;frozen&lt;/strong&gt; value, for
more than 100 consecutive cycles. Because the test is against the frozen
output rather than the measurement, &lt;strong&gt;reconnecting cannot step the drive by
more than that tolerance&lt;/strong&gt;, whatever the measurement did meanwhile.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It is a re-basing wait, not a timer, so it does not time out.&lt;/strong&gt; If nothing
re-bases the software side the output stays frozen indefinitely, with nothing
reported — the safe outcome, but the axis stays held, and a further reference
request is ignored while it is. If a drive stops responding right after
referencing, this is where to look. &lt;code&gt;referencing/:fromState.reset&lt;/code&gt; clears it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Set &lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; for your hardware side.&lt;/strong&gt;
The default of 2 is sized for a hardware side carrying encoder ticks. On an
instance whose hardware side carries metres or radians, 2 is enormous and
would let a whole offset step through.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; of 0 breaks the conversion.&lt;/strong&gt; The other two gain
factors refuse a zero and keep their previous value; this one does not. Never
write 0 to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Persistence is switched off at every startup&lt;/strong&gt;, whatever &lt;code&gt;enablePersistence&lt;/code&gt;
says. Your application must re-enable it after each start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Some of the parameters below only exist on an encoder-type instance.&lt;/strong&gt; The
type is fixed when the controller is built.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The setpoint to convert to raw units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The sensor reading to convert to engineering units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;persistenceEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;enumeration&lt;/td&gt;
&lt;td&gt;Commands a store or load of the position file. Encoder instances only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disablePersistence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Suspends the file handling. &lt;strong&gt;Forced true at every startup.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/softwareReferenceExternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value the captured raw reading should correspond to, when &lt;code&gt;useSoftwareReferenceExternal&lt;/code&gt; is true.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputSoftwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;The converted sensor reading — where the machine thinks it is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The converted setpoint, for the drive. &lt;strong&gt;Held after referencing&lt;/strong&gt; until the interlock clears.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per engineering unit&lt;/td&gt;
&lt;td&gt;The combined conversion factor. &lt;strong&gt;Read this back to check your three factors multiply to what you expect.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/hardwareSnapshot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;The raw reading captured for referencing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The position from the file matches the sensor. &lt;strong&gt;Reads true whenever persistence is off&lt;/strong&gt;, so read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; alongside it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deltaTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;How far the sensor is from the stored position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;singleTurnCounter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;revolutions&lt;/td&gt;
&lt;td&gt;Whole turns counted, for a sensor that only reports position within one turn.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isPersistenceEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The file handling is active.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/isOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The open-loop flag the parent block feeds in, published so the value stays visible. &lt;strong&gt;It is no longer part of the reconnect test&lt;/strong&gt; — an open loop does not release the post-referencing freeze.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Numerator of the conversion. &lt;strong&gt;A 0 is refused and the previous value kept.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Denominator. &lt;strong&gt;A value of 0 or below is refused and the previous value kept.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;The sensor&amp;rsquo;s resolution. Multiplied into the gain. &lt;strong&gt;A negative value is used as its magnitude, but 0 is not refused and will break the conversion.&lt;/strong&gt; Encoder instances only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The raw reading that corresponds to zero engineering units. &lt;strong&gt;Referencing overwrites this&lt;/strong&gt; — save your configuration afterwards, or a reload will undo it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/softwareReferenceInternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;engineering unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value referencing aims at, when the external one is not selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/useSoftwareReferenceExternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Whether to use the external reference input instead of the internal parameter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hardware-side unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;How close the recomputed hardware output must come to the frozen one before the output reconnects, for more than 100 consecutive cycles. A write is taken as its absolute value. &lt;strong&gt;The default suits encoder ticks only&lt;/strong&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enablePersistence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Whether to store the position to a file. &lt;strong&gt;Only for a sensor that does not remember its own absolute position.&lt;/strong&gt; Turn it off for an absolute encoder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-1, or 0 upward&lt;/td&gt;
&lt;td&gt;How far the sensor may differ from the stored position and still be trusted. &lt;strong&gt;The default of -1 means no check.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numberOfPolePairs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;For resolver-type sensors, how many electrical revolutions make one mechanical one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enableSingleTurnCounter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Counts whole turns, for a sensor that reports only within one turn.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart. &lt;strong&gt;No parameters exist
below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Establish your sensor&amp;rsquo;s resolution and set &lt;code&gt;ticksPerRevolution&lt;/code&gt;. &lt;strong&gt;Never
write 0.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt; to express the rest of the conversion — a lead
screw pitch, a pulley ratio, a unit change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read &lt;code&gt;gain&lt;/code&gt; back. It should be &lt;code&gt;gainNum ÷ gainDen × ticksPerRevolution&lt;/code&gt;, and
it is the number of ticks per engineering unit. &lt;strong&gt;Check it against a hand
calculation before going further.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move the machine a known distance by hand and confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt;
changes by that amount.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decide whether you need persistence. &lt;strong&gt;An absolute encoder does not&lt;/strong&gt; —
leave &lt;code&gt;enablePersistence&lt;/code&gt; false. A resolver or incremental encoder does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reference the machine: bring it to a known position, set the reference
value, and trigger referencing from your application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;outputSoftwareSide&lt;/code&gt; now reads the true position.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;After referencing, the drive output is frozen&lt;/strong&gt; until the commanded and
measured positions agree. Expect a short pause before the axis responds
again, and investigate if it never does.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Save your configuration&lt;/strong&gt;, or the referenced offset is lost on the next
reload.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There is nothing dynamic to tune. Get the conversion right and the rest
follows.&lt;/li&gt;
&lt;li&gt;Derive the three gain factors from the drivetrain rather than measuring
them — exact integers avoid rounding in a long-running position count.&lt;/li&gt;
&lt;li&gt;Verify over a long move, not a short one. A 1% gain error is invisible over
a millimetre and obvious over a metre.&lt;/li&gt;
&lt;li&gt;Check the direction. A sensor wired backwards shows as &lt;code&gt;outputSoftwareSide&lt;/code&gt;
moving the wrong way, and is fixed with a negative &lt;code&gt;gainNum&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reference at a repeatable physical feature — a hard stop, a switch — not at
an arbitrary position.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; for the unit your hardware
side carries. Leave it at 2 for encoder ticks; set a few thousandths for
metres or radians. Do this &lt;strong&gt;before&lt;/strong&gt; referencing.&lt;/li&gt;
&lt;li&gt;If you use persistence, set &lt;code&gt;deltaTicksMax&lt;/code&gt; to how far the machine could
plausibly be moved while powered down. Leave it at -1 only if you accept the
stored position unconditionally.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;atStoredTicks&lt;/code&gt; after each start, together with
&lt;code&gt;isPersistenceEnabled&lt;/code&gt; — the first reads true whenever the second is false.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/transducer-conversion-08d6d3d6.svg&#34; alt=&#34;Hardware ticks against engineering units for a 4096-tick sensor. The offsetshifts the line without changing its slope; the gain is theslope.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Referencing moves the line up or down. It never changes the slope.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The conversion is wildly wrong or invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; is 0, which is not refused&lt;/td&gt;
&lt;td&gt;Set it to the sensor&amp;rsquo;s real resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A gain factor I wrote did not take&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; was 0, or &lt;code&gt;gainDen&lt;/code&gt; was 0 or negative — both are refused&lt;/td&gt;
&lt;td&gt;Read them back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine moves the wrong way&lt;/td&gt;
&lt;td&gt;The gain&amp;rsquo;s sign is wrong&lt;/td&gt;
&lt;td&gt;Use a negative &lt;code&gt;gainNum&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The distance is proportionally wrong&lt;/td&gt;
&lt;td&gt;The three factors do not multiply to the right ratio&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;gain&lt;/code&gt; against a hand calculation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stopped responding after referencing&lt;/td&gt;
&lt;td&gt;Expected briefly: the output is held until the positions agree&lt;/td&gt;
&lt;td&gt;If it never resumes, the positions are not converging — check the drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The referenced position was lost after a restart&lt;/td&gt;
&lt;td&gt;Referencing overwrites &lt;code&gt;offset&lt;/code&gt;, and a configuration reload restores the saved value&lt;/td&gt;
&lt;td&gt;Save the configuration after referencing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt; reads true and I do not trust it&lt;/td&gt;
&lt;td&gt;It reads true whenever persistence is off&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence is off even though I enabled it&lt;/td&gt;
&lt;td&gt;It is forced off at every startup&lt;/td&gt;
&lt;td&gt;Re-enable it from your application after each start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stored position is accepted when it should not be&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt; is -1, which means no check&lt;/td&gt;
&lt;td&gt;Set a real window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stopped responding right after referencing, with nothing reported&lt;/td&gt;
&lt;td&gt;Expected: the output is frozen until the software side is re-based. It is a wait, not a timer&lt;/td&gt;
&lt;td&gt;Have the application follow the disconnect flag and re-base its setpoint onto the measured actual; or assert &lt;code&gt;referencing/:fromState.reset&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The drive stepped when the output reconnected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; is too large for the unit the hardware side carries — the default of 2 on a metres-or-radians instance&lt;/td&gt;
&lt;td&gt;Set it to a few thousandths and re-reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnecting takes far longer than expected&lt;/td&gt;
&lt;td&gt;The tolerance is tight enough that the re-based value rarely holds it for 100 consecutive cycles&lt;/td&gt;
&lt;td&gt;Loosen it, or reduce the noise on the software side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A reference request was ignored&lt;/td&gt;
&lt;td&gt;Expected: requests are ignored while the hardware side is still disconnected from the previous one&lt;/td&gt;
&lt;td&gt;Wait for the freeze to clear, or reset it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The freeze did not clear on an open-loop axis&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;referencing/isOpenLoop&lt;/code&gt; does not release the freeze&lt;/td&gt;
&lt;td&gt;Re-base the software side; the flag is published for visibility only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position jumps by a whole revolution&lt;/td&gt;
&lt;td&gt;The single-turn counter is needed, or miscounted&lt;/td&gt;
&lt;td&gt;Enable it and set &lt;code&gt;numberOfPolePairs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything became invalid&lt;/td&gt;
&lt;td&gt;A gain factor that is not a valid number passes the zero checks&lt;/td&gt;
&lt;td&gt;Rewrite all three&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some parameters are missing from the tree&lt;/td&gt;
&lt;td&gt;Your instance is not an encoder type&lt;/td&gt;
&lt;td&gt;The type is fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need a non-linear conversion&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Put a &lt;code&gt;Lookup&lt;/code&gt; after it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a 4096-tick encoder on a direct-driven axis:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ticksPerRevolution = 4096
gainNum            = 1
gainDen            = 1
offset             = 0
enablePersistence  = false
deltaTicksMax      = -1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then read &lt;code&gt;gain&lt;/code&gt; back and confirm it is 4096.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;, &lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Their product is the ticks per engineering unit, both directions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused&lt;/strong&gt; — the previous value is kept&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainDen&lt;/code&gt; of 0 or below&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused&lt;/strong&gt; — the previous value is kept&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ticksPerRevolution&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The gain becomes 0 and &lt;strong&gt;the conversion divides by zero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. They pass the zero tests and poison both directions&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing&lt;/td&gt;
&lt;td&gt;The reference value and a captured raw snapshot&lt;/td&gt;
&lt;td&gt;Solves for the &lt;code&gt;offset&lt;/code&gt; that makes them correspond, and &lt;strong&gt;overwrites &lt;code&gt;offset&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referencing/hardwareSnapshot&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output after referencing&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Held&lt;/strong&gt; until the commanded and measured positions agree closely for a short, fixed period. &lt;strong&gt;No timeout, nothing reported if they never do&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence at startup&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Always disabled&lt;/strong&gt;, whatever the parameter says&lt;/td&gt;
&lt;td&gt;&lt;code&gt;isPersistenceEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stored position check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deltaTicksMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compares the sensor against the stored position. &lt;strong&gt;A default of -1 means no check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;, &lt;code&gt;deltaTicks&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-referencing freeze&lt;/td&gt;
&lt;td&gt;The reconnect condition&lt;/td&gt;
&lt;td&gt;The hardware output is held until recomputing it would land within &lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; of the frozen value, for more than 100 consecutive cycles. &lt;strong&gt;A re-basing wait, not a timer — it does not time out&lt;/strong&gt;, and a further reference request is ignored while it holds&lt;/td&gt;
&lt;td&gt;Not reported directly; read the referencing state group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Default 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sized for a hardware side carrying encoder ticks. On one carrying metres or radians, 2 would let a whole offset step through on reconnect. A negative write is taken as its absolute value&lt;/td&gt;
&lt;td&gt;Read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnect step&lt;/td&gt;
&lt;td&gt;The tolerance&lt;/td&gt;
&lt;td&gt;Bounded &lt;strong&gt;by construction&lt;/strong&gt;: the test is against the frozen output, not the measurement, so reconnecting cannot move the hardware output by more than the tolerance&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atStoredTicks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Reads true when persistence is off&lt;/strong&gt;, so it means &amp;ldquo;nothing to distrust&amp;rdquo;, not &amp;ldquo;a check passed&amp;rdquo;&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isPersistenceEnabled&lt;/code&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence file errors&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;A zero or negative &lt;code&gt;ticksPerRevolution&lt;/code&gt; skips the stored-position check, &lt;strong&gt;and is logged&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Controller log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instance type&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;A non-encoder instance has no persistence, no turn counter and no &lt;code&gt;ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One sensor per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block logs when a stored-position check is skipped for a bad
&lt;code&gt;ticksPerRevolution&lt;/code&gt;, and logs the loaded position at startup. Every other
condition above shows as a value on a trace, or not at all.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.33.0 (69625c1).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: TorqueSensorModule</title>
      <link>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/torque-sensor-module/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/physical-models-and-compensation/torque-sensor-module/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control c3-version__control--static&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;span class=&#34;c3-version__value&#34;&gt;3.30–3.34&lt;/span&gt;
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;TorqueSensorModule&lt;/code&gt; is the signal-conditioning chain for a joint torque
sensor. It converts raw sensor counts to engineering units, corrects the
sensor&amp;rsquo;s non-linearity, removes its dominant noise frequency, and then &lt;strong&gt;splits
the result into two signals&lt;/strong&gt;: a slow one that is the load the joint is
carrying, and a fast one that is the structure vibrating around it.&lt;/p&gt;
&lt;p&gt;That split is the point. The two need different treatment — the load goes to a
force controller, the vibration drives an active-damping correction that this
block also produces.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;sensorTorqueActual — raw sensor counts&amp;quot;]) --&amp;gt; B[&amp;quot;TorqueSensorModule&amp;quot;]
    i2([&amp;quot;sensorTorqueReference — commanded torque&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;staticTorqueActual — the load&amp;quot;])
    B --&amp;gt; o2([&amp;quot;dynamicTorqueActual — the vibration&amp;quot;])
    B --&amp;gt; o3([&amp;quot;disturbanceCorrection — damping output&amp;quot;])
    B --&amp;gt; o4([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The chain is: counts → engineering units → non-linearity correction → notch
filter → &lt;strong&gt;split&lt;/strong&gt;. The static branch low-passes the result. The dynamic
branch subtracts &lt;code&gt;sensorTorqueReference&lt;/code&gt; first, then high-passes it, so it
measures deviation from what was commanded. That deviation drives a
controller whose smoothed output is &lt;code&gt;disturbanceCorrection&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost everything is configured below this block&lt;/strong&gt;, in seven sub-trees. This
page&amp;rsquo;s own eight paths are the wiring; the tuning is in the sub-trees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;disturbanceCorrection&lt;/code&gt; reads zero until you configure the &lt;code&gt;controller&lt;/code&gt;
sub-tree.&lt;/strong&gt; Its gains all default to zero. The two torque outputs work
regardless.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;counts&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The raw torque sensor reading, before any conversion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The torque being commanded. &lt;strong&gt;It is subtracted in the dynamic branch only&lt;/strong&gt; — the static output is the absolute load and does not use it. Leave it at zero if you only want the load.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces &lt;code&gt;disturbanceCorrection&lt;/code&gt; to zero. The two torque outputs keep working. Use it for a runtime override; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;The load the joint is carrying — the slow content of the conditioned signal. &lt;strong&gt;Published whether or not the block is enabled&lt;/strong&gt;, so you can commission the whole chain with the correction disconnected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamicTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;The vibration: the fast content of the conditioned signal, measured &lt;strong&gt;relative to &lt;code&gt;sensorTorqueReference&lt;/code&gt;&lt;/strong&gt;. Also published while disabled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceCorrection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;The damping correction derived from the vibration. &lt;strong&gt;The only output gated by &lt;code&gt;enable&lt;/code&gt;.&lt;/strong&gt; Zero until the &lt;code&gt;controller&lt;/code&gt; sub-tree is configured.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. It does not mean a correction is being produced.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces &lt;code&gt;disturbanceCorrection&lt;/code&gt; to zero and leaves both torque outputs reporting.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One parameter at this level. &lt;strong&gt;The configuration lives in seven sub-trees:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sub-tree&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Where to read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transducer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Counts to newton-metres, and the sensor tare&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/transducer/&#34;&gt;&lt;code&gt;transducer.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;polynomialCorrection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Corrects the sensor&amp;rsquo;s non-linearity, up to 5th order&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/polynomial/&#34;&gt;&lt;code&gt;polynomial.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IIRFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Notch filter for the sensor&amp;rsquo;s dominant noise frequency. &lt;strong&gt;Ships as a pass-through&lt;/strong&gt; — it does nothing until you supply coefficients. Note the capitalised path name&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter/&#34;&gt;&lt;code&gt;iir-filter.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low-pass that extracts the load. Its cut-off sets the split frequency&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/filters/low-pass-1/&#34;&gt;&lt;code&gt;low-pass-1.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamicTorqueFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-pass that extracts the vibration. Its cut-off is the other half of the split&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/filters/high-pass-2/&#34;&gt;&lt;code&gt;high-pass-2.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The damping controller. &lt;strong&gt;Its gains default to zero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;pid.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationCorrectionFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Smooths the correction. &lt;strong&gt;Its cut-off is forced to 1 Hz at every start and cannot be configured&lt;/strong&gt; — see Limits and errors&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;/docs/developing-control-applications/control-blocks/filters/low-pass-1/&#34;&gt;&lt;code&gt;low-pass-1.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;enable&lt;/code&gt; is persistent and survives a restart, as is everything in the
sub-trees.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. Both torque outputs still report, so the whole
conditioning chain can be commissioned with nothing reaching the machine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure &lt;code&gt;transducer&lt;/code&gt; with your sensor&amp;rsquo;s counts-per-newton-metre. Apply a
known load and confirm &lt;code&gt;staticTorqueActual&lt;/code&gt; reads the right value in N·m.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tare the sensor with no load applied. &lt;strong&gt;This needs an application call&lt;/strong&gt; —
there is no parameter for it. If your application does not offer one, work
around it with an offset in the &lt;code&gt;transducer&lt;/code&gt; sub-tree.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the sensor is non-linear, configure &lt;code&gt;polynomialCorrection&lt;/code&gt;. Check it
against known loads across the range, not at one point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace &lt;code&gt;staticTorqueActual&lt;/code&gt; with the joint held still and look for a dominant
noise frequency. If there is one, design a notch for it and write the
coefficients into the &lt;code&gt;IIRFilter&lt;/code&gt; sub-tree. &lt;strong&gt;Design them for your task
rate&lt;/strong&gt; — those coefficients are in samples, not seconds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the split: &lt;code&gt;staticTorqueFilter&lt;/code&gt;&amp;rsquo;s cut-off decides what counts as load,
and &lt;code&gt;dynamicTorqueFilter&lt;/code&gt;&amp;rsquo;s decides what counts as vibration. Start with
both around the frequency where your structure rings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;sensorTorqueReference&lt;/code&gt; from your torque command and confirm
&lt;code&gt;dynamicTorqueActual&lt;/code&gt; sits near zero while the joint tracks that command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure the &lt;code&gt;controller&lt;/code&gt; sub-tree. Set its output limits and integrator
limits &lt;strong&gt;before&lt;/strong&gt; any gain — its integrator bounds default to ±0.1, which
binds almost at once.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 is what makes the block act on the machine.&lt;/strong&gt; Until now every
output has been observation only. Set &lt;code&gt;enable&lt;/code&gt; true last, and raise the
controller&amp;rsquo;s gains from zero with the joint clear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Get the units right before anything else. If &lt;code&gt;staticTorqueActual&lt;/code&gt; is not in
real newton-metres, every downstream number is wrong.&lt;/li&gt;
&lt;li&gt;Tune the notch against a measurement, not a guess. Trace the raw signal at
rest, find the peak, and design for that frequency.&lt;/li&gt;
&lt;li&gt;Set the split frequency from the structure, not from feel. Below your
structure&amp;rsquo;s ringing frequency is load; above it is vibration. If the two
overlap, this block cannot separate them.&lt;/li&gt;
&lt;li&gt;Check the split works: apply a steady load and confirm it appears on
&lt;code&gt;staticTorqueActual&lt;/code&gt; and &lt;strong&gt;not&lt;/strong&gt; on &lt;code&gt;dynamicTorqueActual&lt;/code&gt;. Then excite the
structure and confirm the reverse.&lt;/li&gt;
&lt;li&gt;Tune the &lt;code&gt;controller&lt;/code&gt; last, and as a controller — set its output limits and
integrator limits first, then raise the proportional gain until the ringing
is damped, then back off.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;disturbanceCorrection&lt;/code&gt; on a trace while you do it. If it saturates or
sits pinned, the controller&amp;rsquo;s integrator limits are too tight.&lt;/li&gt;
&lt;li&gt;Re-check the notch coefficients after any task-rate change. They are defined
in samples, so the same coefficients are a different filter at a different
rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/torque-sensor-module-split-ef2b1e91.svg&#34; alt=&#34;A noisy torque with a step and 25 Hz vibration, split into a static outputthat follows the 8 Nm load and a dynamic output that carries the vibration andsits near zero otherwise.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the split off the two curves: the load appears on one and the ringing on
the other.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceCorrection&lt;/code&gt; reads zero with &lt;code&gt;isEnabled&lt;/code&gt; true&lt;/td&gt;
&lt;td&gt;Expected: the &lt;code&gt;controller&lt;/code&gt; sub-tree&amp;rsquo;s gains all default to zero&lt;/td&gt;
&lt;td&gt;Configure the controller, per Setup step 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceCorrection&lt;/code&gt; saturates or sits pinned&lt;/td&gt;
&lt;td&gt;The controller&amp;rsquo;s integrator limits default to ±0.1&lt;/td&gt;
&lt;td&gt;Raise them in the &lt;code&gt;controller&lt;/code&gt; sub-tree before raising &lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A correction appears at full value the moment the block is enabled&lt;/td&gt;
&lt;td&gt;The controller keeps integrating while disabled, so it re-enables already wound&lt;/td&gt;
&lt;td&gt;Enable with the joint at rest, or reset the controller first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Both torque outputs read zero&lt;/td&gt;
&lt;td&gt;The notch filter was disabled in its own sub-tree, which outputs zero rather than passing through&lt;/td&gt;
&lt;td&gt;Re-enable it, or leave it as the pass-through it ships as&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The notch does nothing&lt;/td&gt;
&lt;td&gt;Expected: it ships as a pass-through with no coefficients&lt;/td&gt;
&lt;td&gt;Design a notch and write &lt;code&gt;num&lt;/code&gt; and &lt;code&gt;den&lt;/code&gt; into the &lt;code&gt;IIRFilter&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The notch behaves nothing like the design&lt;/td&gt;
&lt;td&gt;Its coefficients were designed for a different sample rate&lt;/td&gt;
&lt;td&gt;Redesign at the controller&amp;rsquo;s actual task period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueActual&lt;/code&gt; is in the wrong units&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;transducer&lt;/code&gt; sub-tree is not configured for your sensor&lt;/td&gt;
&lt;td&gt;Work Setup step 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueActual&lt;/code&gt; has an offset with no load&lt;/td&gt;
&lt;td&gt;The sensor is not tared&lt;/td&gt;
&lt;td&gt;Tare it — this needs an application call, not a parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueActual&lt;/code&gt; is right at one load and wrong at another&lt;/td&gt;
&lt;td&gt;The sensor is non-linear and &lt;code&gt;polynomialCorrection&lt;/code&gt; is not configured&lt;/td&gt;
&lt;td&gt;Work Setup step 4 with several known loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The load appears on &lt;code&gt;dynamicTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The split frequency is too low&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;dynamicTorqueFilter&lt;/code&gt;&amp;rsquo;s cut-off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vibration appears on &lt;code&gt;staticTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The split frequency is too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;staticTorqueFilter&lt;/code&gt;&amp;rsquo;s cut-off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamicTorqueActual&lt;/code&gt; is large even when the joint is tracking well&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueReference&lt;/code&gt; is not linked, so the whole load reads as deviation&lt;/td&gt;
&lt;td&gt;Link the commanded torque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing &lt;code&gt;vibrationCorrectionFilter&lt;/code&gt;&amp;rsquo;s cut-off had no effect&lt;/td&gt;
&lt;td&gt;Expected: it is forced to 1 Hz at every start and your value is overwritten&lt;/td&gt;
&lt;td&gt;It cannot be changed from configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A non-numeric value appeared and will not clear&lt;/td&gt;
&lt;td&gt;There is no reset in this block, and the filters hold it&lt;/td&gt;
&lt;td&gt;Restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need this on several joints&lt;/td&gt;
&lt;td&gt;Not possible — one sensor per instance&lt;/td&gt;
&lt;td&gt;Use one instance per joint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a joint whose structure rings near 25 Hz on a 1 ms task,
with the correction path not yet active:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable = false
transducer:          configure counts per Nm for your sensor
polynomialCorrection: leave at its pass-through default at first
IIRFilter:           leave as pass-through until a noise peak is measured
staticTorqueFilter:  omega = 63.0     (10 Hz)
dynamicTorqueFilter: omega = 157.0    (25 Hz), beta = 1.0
controller:          kp = 0, ki = 0, kd = 0, output limits set first
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then work Setup step 8. This is a starting point, not a final tuning.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationCorrectionFilter&lt;/code&gt; cut-off&lt;/td&gt;
&lt;td&gt;Fixed in code&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forced to 1 Hz every time the controller initialises, overriding whatever your configuration sets.&lt;/strong&gt; It is the one setting in the sub-trees you cannot change&lt;/td&gt;
&lt;td&gt;Not reported; read the sub-tree value back after a restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller&lt;/code&gt; gains&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;All default to zero, so &lt;code&gt;disturbanceCorrection&lt;/code&gt; is zero until configured. Its integrator bounds default to ±0.1&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notch coefficients&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not checked for stability. A badly designed set can make the whole chain grow without bound — see the filter&amp;rsquo;s own page&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensor tare&lt;/td&gt;
&lt;td&gt;Not exposed&lt;/td&gt;
&lt;td&gt;Taring is available only through an application call, not from the parameter tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;staticTorqueActual&lt;/code&gt;, &lt;code&gt;dynamicTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded, and published whether or not the block is enabled&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceCorrection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;controller&lt;/code&gt; sub-tree&amp;rsquo;s own output limits&lt;/td&gt;
&lt;td&gt;Bounded only by what you set there. Zero while disabled&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chain state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;There is &lt;strong&gt;no reset input.&lt;/strong&gt; The filters and the controller hold their state across a stop, and a non-numeric value can only be cleared by a restart&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;One sensor per instance, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
  </channel>
</rss>
