<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title> – Control loops</title>
    <link>/docs/developing-control-applications/control-blocks/control-loops/</link>
    <description>Recent content in Control loops on </description>
    <generator>Hugo -- gohugo.io</generator>
    
	  <atom:link href="/docs/developing-control-applications/control-blocks/control-loops/index.xml" rel="self" type="application/rss+xml" />
    
    
      
        
      
    
    
    <item>
      <title>Docs: ActuatorControlLoop</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.30/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-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/control-loops/actuator-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-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/control-loops/actuator-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;ActuatorControlLoop&lt;/code&gt; is the control loop for a single axis. It converts sensor
readings into engineering units, decides what the axis should be doing,
converts that back into drive units, and watches everything for faults.&lt;/p&gt;
&lt;p&gt;Everything else in this library exists to feed or be fed by this block.&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;inputPosition, inputVelocity, inputAcceleration, inputTorque&amp;quot;]) --&amp;gt; B[&amp;quot;ActuatorControlLoop&amp;quot;]
    i2([&amp;quot;motorPositionActual, motorVelocityActual, motorTorqueActual, sensorTorqueActual&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;gotoEngaged, gotoJogging, inputJogVelocity&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;driveMode, doInstantSwitchToggle, disable&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;impedance/inputExternalTorque, impedance/usePositionTarget, impedance/disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;motorPositionTarget, motorVelocityTarget, motorTorqueTarget, motorTorqueOffsetTarget&amp;quot;])
    B --&amp;gt; o2([&amp;quot;actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget, actuatorTorqueTarget&amp;quot;])
    B --&amp;gt; o3([&amp;quot;actuatorPositionActual, actuatorVelocityActual, actuatorTorqueActual, actuatorPowerActual&amp;quot;])
    B --&amp;gt; o4([&amp;quot;actuatorPositionError, actuatorVelocityError&amp;quot;])
    B --&amp;gt; o5([&amp;quot;…ActualFiltered outputs, gearboxGain, isEnabled&amp;quot;])
    B --&amp;gt; s1([&amp;quot;positionTransformation, velocityTransformation, torqueTransformation&amp;quot;])
    B --&amp;gt; s2([&amp;quot;positionController, velocityController, vibrationController&amp;quot;])
    B --&amp;gt; s3([&amp;quot;six window detectors, engagedSwitch, limiters, feedforwardControl&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Four things determine how this block behaves. Get these right first.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gotoEngaged&lt;/code&gt; decides whether the axis follows you or follows itself.&lt;/strong&gt;
While it is false the target &lt;em&gt;tracks the measured position&lt;/em&gt;, so nothing jumps
when the drive powers on. While it is true the target follows
&lt;code&gt;inputPosition&lt;/code&gt;. The transition between the two is faded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;driveMode&lt;/code&gt; decides where the torque goes.&lt;/strong&gt; In a position or velocity mode
the additive torque leaves through &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;; in a torque
mode it leaves through &lt;code&gt;motorTorqueTarget&lt;/code&gt;. Exactly one channel is live at a
time, deliberately, so that a drive whose reported mode lags your commanded
mode cannot receive the same torque twice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt; scale everything.&lt;/strong&gt; They are the
single most consequential pair of numbers in this block. &lt;strong&gt;Neither is checked
for zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block does not ship configured.&lt;/strong&gt; The limiters, the window detectors,
the transformations and the controllers all live in sub-trees and all need
setting. An unconfigured loop will not move an axis usefully and may not
protect it.&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;inputPosition&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 commanded position. Followed only while engaged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity, used as feedforward and as the target in a velocity mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputAcceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration, used by the feedforward path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;An additive torque. Filtered, then routed by &lt;code&gt;driveMode&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From a separate force or torque sensor, if fitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoEngaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Follow the input, or track the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Make that transition a &lt;strong&gt;step&lt;/strong&gt; instead of a fade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;drive enumeration&lt;/td&gt;
&lt;td&gt;Which mode the drive is in. Decides the torque routing and gates impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoJogging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;inputJogVelocity&lt;/code&gt; to the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputJogVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The jog speed. &lt;strong&gt;Watchdogged: if it stops being written it falls to 0&lt;/strong&gt; after a timeout.&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;true or false&lt;/td&gt;
&lt;td&gt;Disables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/inputExternalTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The external torque for impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/usePositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Whether impedance limiting works from the target or the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Disables impedance mode.&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;motorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;To the drive.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;To the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a torque mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a position or velocity mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;What the loop decided the axis should do, before conversion. &lt;strong&gt;Trace this first when commissioning.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;Zeroed when the position limiter is limiting in the direction of travel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;Zeroed when either limiter is limiting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The torque path&amp;rsquo;s output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the axis is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPowerActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;power unit&lt;/td&gt;
&lt;td&gt;Torque times velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Target minus actual. &lt;strong&gt;The main health signal for the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;The filtered measurements. These are what the loop uses internally while idle, and what the window detectors watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The separate sensor, converted to actuator units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide ÷ gearboxMotorSide&lt;/code&gt;. &lt;strong&gt;Read it back to check the ratio.&lt;/strong&gt;&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;The loop is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Impedance mode is active. &lt;strong&gt;It requires a torque drive mode&lt;/strong&gt; — enabling it in a position mode does nothing.&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;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Load-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Motor-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Turns all six fault detectors on. &lt;strong&gt;Leave this on in service.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Derive velocity from the filtered position instead of the drive&amp;rsquo;s own velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Feed the feedforward path the measured motion instead of the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetDeadband&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Suppresses small torque targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables impedance mode, &lt;strong&gt;subject to the drive being in a torque mode&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The position PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/usePositionControlForTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Route the position PID&amp;rsquo;s output to torque instead of adding it to the velocity target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Delays the target before the error is formed, to match the fieldbus round trip. &lt;strong&gt;Typically 4 for EtherCAT.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;The same for the measurement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The velocity PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The vibration damper&amp;rsquo;s gains. &lt;strong&gt;Its derivative gain is forced to 0&lt;/strong&gt; whatever you write.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else is in the sub-trees — the transformations, the limiters, the
six window detectors, the feedforward controller and the backlash
compensation. All parameters are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which sub-trees exist depends on how the loop was built.&lt;/strong&gt; A loop built
without torque support has no &lt;code&gt;torqueTransformation&lt;/code&gt;, no &lt;code&gt;torqueWindowDetector&lt;/code&gt;
and no feedforward controller.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the transformations first&lt;/strong&gt;, under &lt;code&gt;positionTransformation&lt;/code&gt; and
&lt;code&gt;velocityTransformation&lt;/code&gt;. Nothing else can be checked until the axis reports
its position correctly in engineering units.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt;, and read &lt;code&gt;gearboxGain&lt;/code&gt; back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the drive disabled, move the axis by hand and confirm
&lt;code&gt;actuatorPositionActual&lt;/code&gt; reads correctly over a long move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the limiters under &lt;code&gt;positionLimiter&lt;/code&gt; and &lt;code&gt;velocityLimiter&lt;/code&gt; to values the
machine cannot be hurt by.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the six window detectors&#39; levels from a trace of normal operation, and
set &lt;code&gt;windowDetectorsEnable&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;gotoEngaged&lt;/code&gt; false and enable the drive. Confirm
&lt;code&gt;actuatorPositionTarget&lt;/code&gt; tracks &lt;code&gt;actuatorPositionActual&lt;/code&gt; and the axis does
not move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt; and
&lt;code&gt;…Actual&lt;/code&gt; to match your fieldbus — 4 samples is typical for EtherCAT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;inputPosition&lt;/code&gt; the axis&amp;rsquo;s current measured position, then set
&lt;code&gt;gotoEngaged&lt;/code&gt; true.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 hands the axis to your setpoint source.&lt;/strong&gt; If &lt;code&gt;inputPosition&lt;/code&gt; does
not match where the axis is, the loop will move it there over the fade.
Match them first, and keep the limiters tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tune the controllers — go to Tuning.&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 measurement chain right before touching a gain. Trace
&lt;code&gt;actuatorPositionActual&lt;/code&gt; and &lt;code&gt;actuatorVelocityActual&lt;/code&gt; at rest and in motion;
noise here becomes torque later.&lt;/li&gt;
&lt;li&gt;Set the two control-error delays to your fieldbus round trip. Getting these
wrong makes the position error look like a lag, and tempts you into gains
that oscillate.&lt;/li&gt;
&lt;li&gt;Tune the velocity controller first, then the position controller around it.
The usual order: raise proportional gain until the axis is stiff, add
integral until steady-state error clears, add derivative only if you must.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;actuatorPositionError&lt;/code&gt; throughout. It is the single best indicator of
whether the loop is healthy.&lt;/li&gt;
&lt;li&gt;Add feedforward once the feedback loop is stable. It reduces the error the
controllers have to work on rather than replacing them.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;usePvaActualForCompensation&lt;/code&gt; only if the feedforward is fighting the
commanded motion — normally the commanded motion is the right input.&lt;/li&gt;
&lt;li&gt;Set the window detector levels from the error you actually see, with margin.
They are your protection, not a diagnostic.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the control-error delays are
in &lt;strong&gt;cycles&lt;/strong&gt;, so their meaning in seconds changes with the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/actuator-control-engage-443f3226.svg&#34; alt=&#34;Engaging the loop. While idle the target follows the measured position; whengotoEngaged goes true it fades onto the commandedposition.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The fade is what keeps the axis from stepping at the moment you take control.&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 jumped when engaging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt; did not match the measured position&lt;/td&gt;
&lt;td&gt;Match them before engaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis stepped rather than faded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis moves while idle&lt;/td&gt;
&lt;td&gt;Something downstream is not respecting &lt;code&gt;motorPositionTarget&lt;/code&gt;, or the drive is in the wrong mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing moves when engaged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false, &lt;code&gt;disable&lt;/code&gt; is true, or the limiters are clamping&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;isEnabled&lt;/code&gt; and the limiters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position error grows with speed&lt;/td&gt;
&lt;td&gt;Feedforward is missing or under-tuned, or the control-error delays are wrong&lt;/td&gt;
&lt;td&gt;Set the delays first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates&lt;/td&gt;
&lt;td&gt;Controller gains too high, or the measurement is noisy&lt;/td&gt;
&lt;td&gt;Lower the gains; check the measurement filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates only at one frequency&lt;/td&gt;
&lt;td&gt;A structural mode&lt;/td&gt;
&lt;td&gt;Use the IIR filter in the transformation, or the vibration controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The vibration controller&amp;rsquo;s derivative gain does nothing&lt;/td&gt;
&lt;td&gt;It is forced to 0 in this version&lt;/td&gt;
&lt;td&gt;Use proportional and integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque appears on the wrong output&lt;/td&gt;
&lt;td&gt;Expected: the routing follows &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check which mode the drive reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis received torque twice during a mode change&lt;/td&gt;
&lt;td&gt;Should not happen — the two channels are gated so only one is live&lt;/td&gt;
&lt;td&gt;Report it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode does nothing&lt;/td&gt;
&lt;td&gt;It requires a &lt;strong&gt;torque&lt;/strong&gt; drive mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt; and &lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis keeps jogging after the operator lets go&lt;/td&gt;
&lt;td&gt;Should not happen — the jog velocity is watchdogged to 0&lt;/td&gt;
&lt;td&gt;Check the writer is really stopping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The window detectors latch. The reset is &lt;strong&gt;not on this block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on the parent&amp;rsquo;s state input — it clears every axis at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything is scaled wrongly&lt;/td&gt;
&lt;td&gt;The gearbox ratio, or the transformations&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gearboxGain&lt;/code&gt; back and check the transformations first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop misbehaves after writing a gearbox value&lt;/td&gt;
&lt;td&gt;A zero tooth count is not refused here&lt;/td&gt;
&lt;td&gt;Read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A sub-tree I expected is missing&lt;/td&gt;
&lt;td&gt;The loop was built without that feature&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque detector&amp;rsquo;s thresholds seem to be in the wrong units&lt;/td&gt;
&lt;td&gt;It watches the &lt;strong&gt;raw sensor&lt;/strong&gt; value, unlike the other five&lt;/td&gt;
&lt;td&gt;Set its levels from a trace of that signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point once the transformations are correct:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearboxLoadSide       = &amp;lt;from the drivetrain&amp;gt;
gearboxMotorSide      = &amp;lt;from the drivetrain&amp;gt;
windowDetectorsEnable = true
positionController/delayForControlError/numberOfSamplesTarget = 4
positionController/delayForControlError/numberOfSamplesActual = 4
usePvaActualForCompensation = false
&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;Position and velocity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;positionLimiter&lt;/code&gt;, &lt;code&gt;velocityLimiter&lt;/code&gt; sub-trees&lt;/td&gt;
&lt;td&gt;Targets are clamped. &lt;strong&gt;The velocity target is zeroed&lt;/strong&gt; when the position limiter is limiting in the direction of travel, and the acceleration target when either is limiting&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault detection&lt;/td&gt;
&lt;td&gt;The six window detector sub-trees&lt;/td&gt;
&lt;td&gt;Latching warning and error flags per quantity&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s &lt;code&gt;noError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;parent&amp;rsquo;s&lt;/strong&gt; state input&lt;/td&gt;
&lt;td&gt;This block has no reset path of its own. Inside &lt;code&gt;AxesControl&lt;/code&gt; the reset arrives through its state channel and clears &lt;strong&gt;every&lt;/strong&gt; actuator at once. A standalone loop can be reset only by an application&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog velocity loss&lt;/td&gt;
&lt;td&gt;Watchdog&lt;/td&gt;
&lt;td&gt;Falls to &lt;strong&gt;0&lt;/strong&gt; if the writer stops&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engage transition&lt;/td&gt;
&lt;td&gt;The engaged switch sub-tree&lt;/td&gt;
&lt;td&gt;Faded, unless &lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque routing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one of the two torque outputs is live; the other is 0&lt;/td&gt;
&lt;td&gt;Both outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Silently inactive&lt;/strong&gt; unless the drive is in a torque mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gearbox ratio&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A zero tooth count is not refused&lt;/strong&gt; and makes the conversion invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&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 anywhere in this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control error delays&lt;/td&gt;
&lt;td&gt;Fixed in &lt;strong&gt;cycles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Their meaning in seconds changes with the task rate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vibration controller derivative&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forced to 0&lt;/strong&gt; every cycle, whatever you write&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Torque, feedforward, sensor torque and the two controllers are each compiled in or out&lt;/td&gt;
&lt;td&gt;Missing sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis 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;. Sub-modules below it may log — the
transformation&amp;rsquo;s transducer 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: ActuatorControlLoop</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-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/control-loops/actuator-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.32/&#34; selected&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.30/&#34;&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/control-loops/actuator-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;ActuatorControlLoop&lt;/code&gt; is the control loop for a single axis. It converts sensor
readings into engineering units, decides what the axis should be doing,
converts that back into drive units, and watches everything for faults.&lt;/p&gt;
&lt;p&gt;Everything else in this library exists to feed or be fed by this block.&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;inputPosition, inputVelocity, inputAcceleration, inputTorque&amp;quot;]) --&amp;gt; B[&amp;quot;ActuatorControlLoop&amp;quot;]
    i2([&amp;quot;motorPositionActual, motorVelocityActual, motorTorqueActual, sensorTorqueActual&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;gotoEngaged, gotoJogging, inputJogVelocity&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;driveMode, doInstantSwitchToggle, disable&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;impedance/inputExternalTorque, impedance/usePositionTarget, impedance/disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;motorPositionTarget, motorVelocityTarget, motorTorqueTarget, motorTorqueOffsetTarget&amp;quot;])
    B --&amp;gt; o2([&amp;quot;actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget, actuatorTorqueTarget&amp;quot;])
    B --&amp;gt; o3([&amp;quot;actuatorPositionActual, actuatorVelocityActual, actuatorTorqueActual, actuatorPowerActual&amp;quot;])
    B --&amp;gt; o4([&amp;quot;actuatorPositionError, actuatorVelocityError&amp;quot;])
    B --&amp;gt; o5([&amp;quot;…ActualFiltered outputs, gearboxGain, isEnabled&amp;quot;])
    B --&amp;gt; s1([&amp;quot;positionTransformation, velocityTransformation, torqueTransformation&amp;quot;])
    B --&amp;gt; s2([&amp;quot;positionController, velocityController, vibrationController&amp;quot;])
    B --&amp;gt; s3([&amp;quot;six window detectors, engagedSwitch, limiters, feedforwardControl&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Four things determine how this block behaves. Get these right first.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gotoEngaged&lt;/code&gt; decides whether the axis follows you or follows itself.&lt;/strong&gt;
While it is false the target &lt;em&gt;tracks the measured position&lt;/em&gt;, so nothing jumps
when the drive powers on. While it is true the target follows
&lt;code&gt;inputPosition&lt;/code&gt;. The transition between the two is faded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;driveMode&lt;/code&gt; decides where the additive torque goes.&lt;/strong&gt; In a position or
velocity mode it leaves through &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;; in a torque mode
it leaves through &lt;code&gt;motorTorqueTarget&lt;/code&gt;. Exactly one of the two carries it at a
time, deliberately, so that a drive whose reported mode lags your commanded
mode cannot receive the same torque twice. &lt;strong&gt;This is about the additive
torque only&lt;/strong&gt; — the controller torques stay on &lt;code&gt;motorTorqueTarget&lt;/code&gt; in every
mode, so that output is generally non-zero in position and velocity mode too.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt; scale everything.&lt;/strong&gt; They are the
single most consequential pair of numbers in this block. &lt;strong&gt;Neither is checked
for zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block does not ship configured.&lt;/strong&gt; The limiters, the window detectors,
the transformations and the controllers all live in sub-trees and all need
setting. An unconfigured loop will not move an axis usefully and may not
protect it.&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;inputPosition&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 commanded position. Followed only while engaged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity, used as feedforward and as the target in a velocity mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputAcceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration, used by the feedforward path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;An additive torque. Filtered, then routed by &lt;code&gt;driveMode&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From a separate force or torque sensor, if fitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoEngaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Follow the input, or track the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Make that transition a &lt;strong&gt;step&lt;/strong&gt; instead of a fade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;drive enumeration&lt;/td&gt;
&lt;td&gt;Which mode the drive is in. Decides the torque routing and gates impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoJogging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;inputJogVelocity&lt;/code&gt; to the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputJogVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The jog speed. &lt;strong&gt;Watchdogged: if it stops being written it falls to 0&lt;/strong&gt; after a timeout.&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;true or false&lt;/td&gt;
&lt;td&gt;Disables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/inputExternalTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The external torque for impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/usePositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Whether impedance limiting works from the target or the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Disables impedance mode.&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;motorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;To the drive.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;To the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a torque mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a position or velocity mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;What the loop decided the axis should do, before conversion. &lt;strong&gt;Trace this first when commissioning.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;Zeroed when the position limiter is limiting in the direction of travel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;Zeroed when either limiter is limiting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The torque path&amp;rsquo;s output: the position, velocity and impedance controller torques in every mode, plus the additive torque in a torque mode only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the axis is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPowerActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;power unit&lt;/td&gt;
&lt;td&gt;Torque times velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Target minus actual. &lt;strong&gt;The main health signal for the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;The filtered measurements. These are what the loop uses internally while idle, and what the window detectors watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The separate sensor, converted to actuator units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide ÷ gearboxMotorSide&lt;/code&gt;. &lt;strong&gt;Read it back to check the ratio.&lt;/strong&gt;&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;The loop is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Impedance mode is active. &lt;strong&gt;It requires a torque drive mode&lt;/strong&gt; — enabling it in a position mode does nothing.&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;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Load-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Motor-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Turns all six fault detectors on. &lt;strong&gt;Leave this on in service.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Derive velocity from the filtered position instead of the drive&amp;rsquo;s own velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Feed the feedforward path the measured motion instead of the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetDeadband&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Suppresses small torque targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables impedance mode, &lt;strong&gt;subject to the drive being in a torque mode&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The position PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/usePositionControlForTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Route the position PID&amp;rsquo;s output to torque instead of adding it to the velocity target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Delays the target before the error is formed, to match the fieldbus round trip. &lt;strong&gt;Typically 4 for EtherCAT.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;The same for the measurement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The velocity PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The vibration damper&amp;rsquo;s gains. &lt;strong&gt;Its derivative gain is forced to 0&lt;/strong&gt; whatever you write.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else is in the sub-trees — the transformations, the limiters, the
six window detectors, the feedforward controller and the backlash
compensation. All parameters are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which sub-trees exist depends on how the loop was built.&lt;/strong&gt; A loop built
without torque support has no &lt;code&gt;torqueTransformation&lt;/code&gt;, no &lt;code&gt;torqueWindowDetector&lt;/code&gt;
and no feedforward controller.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the transformations first&lt;/strong&gt;, under &lt;code&gt;positionTransformation&lt;/code&gt; and
&lt;code&gt;velocityTransformation&lt;/code&gt;. Nothing else can be checked until the axis reports
its position correctly in engineering units.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt;, and read &lt;code&gt;gearboxGain&lt;/code&gt; back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the drive disabled, move the axis by hand and confirm
&lt;code&gt;actuatorPositionActual&lt;/code&gt; reads correctly over a long move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the limiters under &lt;code&gt;positionLimiter&lt;/code&gt; and &lt;code&gt;velocityLimiter&lt;/code&gt; to values the
machine cannot be hurt by.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the six window detectors&#39; levels from a trace of normal operation, and
set &lt;code&gt;windowDetectorsEnable&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;gotoEngaged&lt;/code&gt; false and enable the drive. Confirm
&lt;code&gt;actuatorPositionTarget&lt;/code&gt; tracks &lt;code&gt;actuatorPositionActual&lt;/code&gt; and the axis does
not move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt; and
&lt;code&gt;…Actual&lt;/code&gt; to match your fieldbus — 4 samples is typical for EtherCAT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;inputPosition&lt;/code&gt; the axis&amp;rsquo;s current measured position, then set
&lt;code&gt;gotoEngaged&lt;/code&gt; true.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 hands the axis to your setpoint source.&lt;/strong&gt; If &lt;code&gt;inputPosition&lt;/code&gt; does
not match where the axis is, the loop will move it there over the fade.
Match them first, and keep the limiters tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tune the controllers — go to Tuning.&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 measurement chain right before touching a gain. Trace
&lt;code&gt;actuatorPositionActual&lt;/code&gt; and &lt;code&gt;actuatorVelocityActual&lt;/code&gt; at rest and in motion;
noise here becomes torque later.&lt;/li&gt;
&lt;li&gt;Set the two control-error delays to your fieldbus round trip. Getting these
wrong makes the position error look like a lag, and tempts you into gains
that oscillate.&lt;/li&gt;
&lt;li&gt;Tune the velocity controller first, then the position controller around it.
The usual order: raise proportional gain until the axis is stiff, add
integral until steady-state error clears, add derivative only if you must.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;actuatorPositionError&lt;/code&gt; throughout. It is the single best indicator of
whether the loop is healthy.&lt;/li&gt;
&lt;li&gt;Add feedforward once the feedback loop is stable. It reduces the error the
controllers have to work on rather than replacing them.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;usePvaActualForCompensation&lt;/code&gt; only if the feedforward is fighting the
commanded motion — normally the commanded motion is the right input.&lt;/li&gt;
&lt;li&gt;Set the window detector levels from the error you actually see, with margin.
They are your protection, not a diagnostic.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the control-error delays are
in &lt;strong&gt;cycles&lt;/strong&gt;, so their meaning in seconds changes with the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/actuator-control-engage-443f3226.svg&#34; alt=&#34;Engaging the loop. While idle the target follows the measured position; whengotoEngaged goes true it fades onto the commandedposition.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The fade is what keeps the axis from stepping at the moment you take control.&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 jumped when engaging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt; did not match the measured position&lt;/td&gt;
&lt;td&gt;Match them before engaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis stepped rather than faded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis moves while idle&lt;/td&gt;
&lt;td&gt;Something downstream is not respecting &lt;code&gt;motorPositionTarget&lt;/code&gt;, or the drive is in the wrong mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing moves when engaged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false, &lt;code&gt;disable&lt;/code&gt; is true, or the limiters are clamping&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;isEnabled&lt;/code&gt; and the limiters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position error grows with speed&lt;/td&gt;
&lt;td&gt;Feedforward is missing or under-tuned, or the control-error delays are wrong&lt;/td&gt;
&lt;td&gt;Set the delays first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates&lt;/td&gt;
&lt;td&gt;Controller gains too high, or the measurement is noisy&lt;/td&gt;
&lt;td&gt;Lower the gains; check the measurement filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates only at one frequency&lt;/td&gt;
&lt;td&gt;A structural mode&lt;/td&gt;
&lt;td&gt;Use the IIR filter in the transformation, or the vibration controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The vibration controller&amp;rsquo;s derivative gain does nothing&lt;/td&gt;
&lt;td&gt;It is forced to 0 in this version&lt;/td&gt;
&lt;td&gt;Use proportional and integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque appears on the wrong output&lt;/td&gt;
&lt;td&gt;Expected: the routing follows &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check which mode the drive reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The feedforward torque blips to zero for a moment when the drive changes mode&lt;/td&gt;
&lt;td&gt;Expected: the routing switches on the commanded mode, a few cycles before the drive reports the new one&lt;/td&gt;
&lt;td&gt;Nothing to fix; sequence mode changes at rest if the blip matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis received torque twice during a mode change&lt;/td&gt;
&lt;td&gt;Should not happen — the two channels are gated so only one is live&lt;/td&gt;
&lt;td&gt;Report it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode does nothing&lt;/td&gt;
&lt;td&gt;It requires a &lt;strong&gt;torque&lt;/strong&gt; drive mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt; and &lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis keeps jogging after the operator lets go&lt;/td&gt;
&lt;td&gt;Should not happen — the jog velocity is watchdogged to 0&lt;/td&gt;
&lt;td&gt;Check the writer is really stopping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The window detectors latch. The reset is &lt;strong&gt;not on this block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on the parent&amp;rsquo;s state input — it clears every axis at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything is scaled wrongly&lt;/td&gt;
&lt;td&gt;The gearbox ratio, or the transformations&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gearboxGain&lt;/code&gt; back and check the transformations first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop misbehaves after writing a gearbox value&lt;/td&gt;
&lt;td&gt;A zero tooth count is not refused here&lt;/td&gt;
&lt;td&gt;Read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A sub-tree I expected is missing&lt;/td&gt;
&lt;td&gt;The loop was built without that feature&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque detector&amp;rsquo;s thresholds seem to be in the wrong units&lt;/td&gt;
&lt;td&gt;It watches the &lt;strong&gt;raw sensor&lt;/strong&gt; value, unlike the other five&lt;/td&gt;
&lt;td&gt;Set its levels from a trace of that signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point once the transformations are correct:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearboxLoadSide       = &amp;lt;from the drivetrain&amp;gt;
gearboxMotorSide      = &amp;lt;from the drivetrain&amp;gt;
windowDetectorsEnable = true
positionController/delayForControlError/numberOfSamplesTarget = 4
positionController/delayForControlError/numberOfSamplesActual = 4
usePvaActualForCompensation = false
&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;Position and velocity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;positionLimiter&lt;/code&gt;, &lt;code&gt;velocityLimiter&lt;/code&gt; sub-trees&lt;/td&gt;
&lt;td&gt;Targets are clamped. &lt;strong&gt;The velocity target is zeroed&lt;/strong&gt; when the position limiter is limiting in the direction of travel, and the acceleration target when either is limiting&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault detection&lt;/td&gt;
&lt;td&gt;The six window detector sub-trees&lt;/td&gt;
&lt;td&gt;Latching warning and error flags per quantity&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s &lt;code&gt;noError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;parent&amp;rsquo;s&lt;/strong&gt; state input&lt;/td&gt;
&lt;td&gt;This block has no reset path of its own. Inside &lt;code&gt;AxesControl&lt;/code&gt; the reset arrives through its state channel and clears &lt;strong&gt;every&lt;/strong&gt; actuator at once. A standalone loop can be reset only by an application&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog velocity loss&lt;/td&gt;
&lt;td&gt;Watchdog&lt;/td&gt;
&lt;td&gt;Falls to &lt;strong&gt;0&lt;/strong&gt; if the writer stops&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engage transition&lt;/td&gt;
&lt;td&gt;The engaged switch sub-tree&lt;/td&gt;
&lt;td&gt;Faded, unless &lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additive torque routing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one of the two torque outputs carries it; the other contribution is 0. The gate follows the &lt;strong&gt;commanded&lt;/strong&gt; mode, so on a change &lt;strong&gt;into&lt;/strong&gt; a torque mode the additive torque drops out for the few cycles the drive takes to report the new mode&lt;/td&gt;
&lt;td&gt;Both outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Silently inactive&lt;/strong&gt; unless the drive is in a torque mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gearbox ratio&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A zero tooth count is not refused&lt;/strong&gt; and makes the conversion invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&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 anywhere in this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control error delays&lt;/td&gt;
&lt;td&gt;Fixed in &lt;strong&gt;cycles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Their meaning in seconds changes with the task rate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vibration controller derivative&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forced to 0&lt;/strong&gt; every cycle, whatever you write&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Torque, feedforward, sensor torque and the two controllers are each compiled in or out&lt;/td&gt;
&lt;td&gt;Missing sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis 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;. Sub-modules below it may log — the
transformation&amp;rsquo;s transducer 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.32.1 (340db23).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: ActuatorControlLoop</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.34/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.34/</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/control-loops/actuator-control-3.34/&#34; selected&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.30/&#34;&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/control-loops/actuator-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;ActuatorControlLoop&lt;/code&gt; is the control loop for a single axis. It converts sensor
readings into engineering units, decides what the axis should be doing,
converts that back into drive units, and watches everything for faults.&lt;/p&gt;
&lt;p&gt;Everything else in this library exists to feed or be fed by this block.&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;inputPosition, inputVelocity, inputAcceleration, inputTorque&amp;quot;]) --&amp;gt; B[&amp;quot;ActuatorControlLoop&amp;quot;]
    i2([&amp;quot;motorPositionActual, motorVelocityActual, motorTorqueActual, sensorTorqueActual&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;gotoEngaged, gotoJogging, inputJogVelocity&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;driveMode, doInstantSwitchToggle, disable&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;impedance/inputExternalTorque, impedance/usePositionTarget, impedance/disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;motorPositionTarget, motorVelocityTarget, motorTorqueTarget, motorTorqueOffsetTarget&amp;quot;])
    B --&amp;gt; o2([&amp;quot;actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget, actuatorTorqueTarget&amp;quot;])
    B --&amp;gt; o3([&amp;quot;actuatorPositionActual, actuatorVelocityActual, actuatorTorqueActual, actuatorPowerActual&amp;quot;])
    B --&amp;gt; o4([&amp;quot;actuatorPositionError, actuatorVelocityError&amp;quot;])
    B --&amp;gt; o5([&amp;quot;…ActualFiltered outputs, gearboxGain, isEnabled&amp;quot;])
    B --&amp;gt; s1([&amp;quot;positionTransformation, velocityTransformation, torqueTransformation&amp;quot;])
    B --&amp;gt; s2([&amp;quot;positionController, velocityController, vibrationController&amp;quot;])
    B --&amp;gt; s3([&amp;quot;six window detectors, engagedSwitch, limiters, feedforwardControl&amp;quot;])
    B --&amp;gt; s4([&amp;quot;positionTargetFilter, velocityTargetFilter, torqueTargetFilter, torqueOffsetTargetFilter, vibrationFilter&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Four things determine how this block behaves. Get these right first.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gotoEngaged&lt;/code&gt; decides whether the axis follows you or follows itself.&lt;/strong&gt;
While it is false the target &lt;em&gt;tracks the measured position&lt;/em&gt;, so nothing jumps
when the drive powers on. While it is true the target follows
&lt;code&gt;inputPosition&lt;/code&gt;. The transition between the two is faded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;driveMode&lt;/code&gt; decides where the additive torque goes.&lt;/strong&gt; In a position or
velocity mode it leaves through &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;; in a torque mode
it leaves through &lt;code&gt;motorTorqueTarget&lt;/code&gt;. Exactly one of the two carries it at a
time, deliberately, so that a drive whose reported mode lags your commanded
mode cannot receive the same torque twice. &lt;strong&gt;This is about the additive
torque only&lt;/strong&gt; — the controller torques stay on &lt;code&gt;motorTorqueTarget&lt;/code&gt; in every
mode, so that output is generally non-zero in position and velocity mode too.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt; scale everything.&lt;/strong&gt; They are the
single most consequential pair of numbers in this block. &lt;strong&gt;Neither is checked
for zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block does not ship configured.&lt;/strong&gt; The limiters, the window detectors,
the transformations and the controllers all live in sub-trees and all need
setting. An unconfigured loop will not move an axis usefully and may not
protect it.&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;inputPosition&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 commanded position. Followed only while engaged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity, used as feedforward and as the target in a velocity mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputAcceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration, used by the feedforward path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;An additive torque. Filtered, then routed by &lt;code&gt;driveMode&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From a separate force or torque sensor, if fitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoEngaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Follow the input, or track the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Make that transition a &lt;strong&gt;step&lt;/strong&gt; instead of a fade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;drive enumeration&lt;/td&gt;
&lt;td&gt;Which mode the drive is in. Decides the torque routing and gates impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoJogging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;inputJogVelocity&lt;/code&gt; to the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputJogVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The jog speed. &lt;strong&gt;Watchdogged: if it stops being written it falls to 0&lt;/strong&gt; after a timeout.&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;true or false&lt;/td&gt;
&lt;td&gt;Disables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/inputExternalTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The external torque for impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/usePositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Whether impedance limiting works from the target or the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Disables impedance mode.&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;motorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;To the drive.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;To the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a torque mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a position or velocity mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;What the loop decided the axis should do, before conversion. &lt;strong&gt;Trace this first when commissioning.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;Zeroed when the position limiter is limiting in the direction of travel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;Zeroed when either limiter is limiting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The torque path&amp;rsquo;s output: the position, velocity and impedance controller torques in every mode, plus the additive torque in a torque mode only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the axis is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPowerActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;power unit&lt;/td&gt;
&lt;td&gt;Torque times velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Target minus actual. &lt;strong&gt;The main health signal for the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;The filtered measurements. These are what the loop uses internally while idle, and what the window detectors watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The separate sensor, converted to actuator units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide ÷ gearboxMotorSide&lt;/code&gt;. &lt;strong&gt;Read it back to check the ratio.&lt;/strong&gt;&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;The loop is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Impedance mode is active. &lt;strong&gt;It requires a torque drive mode&lt;/strong&gt; — enabling it in a position mode does nothing.&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;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Load-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Motor-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Turns all six fault detectors on. &lt;strong&gt;Leave this on in service.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Derive velocity from the filtered position instead of the drive&amp;rsquo;s own velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Feed the feedforward path the measured motion instead of the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetDeadband&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Suppresses small torque targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables impedance mode, &lt;strong&gt;subject to the drive being in a torque mode&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The position PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/usePositionControlForTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Route the position PID&amp;rsquo;s output to torque instead of adding it to the velocity target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Delays the target before the error is formed, to match the fieldbus round trip. &lt;strong&gt;Typically 4 for EtherCAT.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;The same for the measurement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The velocity PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The vibration damper&amp;rsquo;s gains. &lt;strong&gt;Its derivative gain is forced to 0&lt;/strong&gt; whatever you write.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else is in the sub-trees — the transformations, the limiters, the
six window detectors, the feedforward controller and the backlash
compensation. All parameters are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which sub-trees exist depends on how the loop was built.&lt;/strong&gt; A loop built
without torque support has no &lt;code&gt;torqueTransformation&lt;/code&gt;, no &lt;code&gt;torqueWindowDetector&lt;/code&gt;
and no feedforward controller.&lt;/p&gt;
&lt;h2 id=&#34;target-shaping-filters&#34;&gt;Target shaping filters&lt;/h2&gt;
&lt;p&gt;Four &lt;code&gt;IIRFilter&lt;/code&gt; sub-trees shape the outgoing targets. Each is a full IIR of up
to order 6 — enough for two notches plus a lowpass — and each sits &lt;strong&gt;in front
of its limiter&lt;/strong&gt;, which in turn sits in front of the transformation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;Shapes&lt;/th&gt;
&lt;th&gt;Order in the chain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the position target&lt;/td&gt;
&lt;td&gt;engaged switch → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;positionLimiter&lt;/code&gt; → &lt;code&gt;positionTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the velocity target&lt;/td&gt;
&lt;td&gt;engaged switch → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;velocityLimiter&lt;/code&gt; → &lt;code&gt;velocityTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the torque target&lt;/td&gt;
&lt;td&gt;torque sum → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;torqueLimiter&lt;/code&gt; → &lt;code&gt;torqueTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueOffsetTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the additive torque&lt;/td&gt;
&lt;td&gt;offset sum → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;torqueOffsetLimiter&lt;/code&gt; → &lt;code&gt;torqueOffsetTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;All four default to pass-through&lt;/strong&gt; (order 1, &lt;code&gt;num[0]&lt;/code&gt; = &lt;code&gt;den[0]&lt;/code&gt; = 1), so a
loop you have not tuned behaves exactly as it did before these filters existed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A filter that is switched off is a bypass, not a mute.&lt;/strong&gt; &lt;code&gt;IIRFilter&lt;/code&gt; on its
own 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 — which in a setpoint path would be a command to zero. This block
therefore guards every one of the four: whenever a filter is not actually
filtering, the &lt;strong&gt;unfiltered&lt;/strong&gt; signal is passed to the limiter instead. You can
disable any of these four safely. That guard is specific to this block; an
&lt;code&gt;IIRFilter&lt;/code&gt; you place in a setpoint path yourself has no such protection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The two torque filters are inside the feedback loops.&lt;/strong&gt; Their input is the
sum of the feedforward and the position and velocity controller outputs, so
tuning them &lt;strong&gt;changes loop stability&lt;/strong&gt;. That is exactly what you want from a
notch on a mechanical resonance, but treat these as stability parameters, not
as cosmetic smoothing. The position and velocity filters shape the reference
only and are outside every loop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Filtering happens before the limiter, and that is deliberate.&lt;/strong&gt; An IIR
response is not amplitude-bounded: a filter placed after a limiter can ring
past the limit and hand the drive a target outside the allowed envelope, and
it would also decouple the limiters&#39; own limiting flags — which drive the
velocity and acceleration zeroing — from the signal actually commanded. The
price is that clipping is not smoothed, which only matters in saturation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Feedforward does not see these filters.&lt;/strong&gt; The feedforward path taps the
setpoints &lt;em&gt;before&lt;/em&gt; the filters but &lt;em&gt;after&lt;/em&gt; the same limiting, so retuning a
filter never changes the feedforward torque. One second-order effect remains:
&lt;code&gt;positionLimiter&lt;/code&gt; is adaptive, and its active window is computed from the
filtered signal, so a tuned position filter shifts that window slightly and
the feedforward tap inherits the shift.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The acceleration target is &lt;strong&gt;not&lt;/strong&gt; filtered.&lt;/p&gt;
&lt;h3 id=&#34;the-vibration-dampers-own-filter&#34;&gt;The vibration damper&amp;rsquo;s own filter&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;vibrationFilter&lt;/code&gt; is a fifth &lt;code&gt;IIRFilter&lt;/code&gt;, in the vibration controller&amp;rsquo;s
&lt;em&gt;feedback&lt;/em&gt; path rather than a target path:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sensorTorqueActual → sensorTorqueHighPass → vibrationFilter → vibrationController
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;sensorTorqueHighPass&lt;/code&gt; is unchanged and still does the DC rejection that decides
what counts as vibration. &lt;code&gt;vibrationFilter&lt;/code&gt; sits behind it, pass-through by
default, and is where you put notches to keep higher-frequency content out of
the damper. It is guarded the same way as the four target filters, so switching
it off bypasses it instead of zeroing the feedback.&lt;/p&gt;
&lt;p&gt;Remember where the damper&amp;rsquo;s output goes: it is added to the &lt;strong&gt;additive torque
channel only&lt;/strong&gt;, so it is inactive in a torque mode whatever this filter is set
to.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the transformations first&lt;/strong&gt;, under &lt;code&gt;positionTransformation&lt;/code&gt; and
&lt;code&gt;velocityTransformation&lt;/code&gt;. Nothing else can be checked until the axis reports
its position correctly in engineering units.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt;, and read &lt;code&gt;gearboxGain&lt;/code&gt; back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the drive disabled, move the axis by hand and confirm
&lt;code&gt;actuatorPositionActual&lt;/code&gt; reads correctly over a long move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the limiters under &lt;code&gt;positionLimiter&lt;/code&gt; and &lt;code&gt;velocityLimiter&lt;/code&gt; to values the
machine cannot be hurt by.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the six window detectors&#39; levels from a trace of normal operation, and
set &lt;code&gt;windowDetectorsEnable&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;gotoEngaged&lt;/code&gt; false and enable the drive. Confirm
&lt;code&gt;actuatorPositionTarget&lt;/code&gt; tracks &lt;code&gt;actuatorPositionActual&lt;/code&gt; and the axis does
not move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt; and
&lt;code&gt;…Actual&lt;/code&gt; to match your fieldbus — 4 samples is typical for EtherCAT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;inputPosition&lt;/code&gt; the axis&amp;rsquo;s current measured position, then set
&lt;code&gt;gotoEngaged&lt;/code&gt; true.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 hands the axis to your setpoint source.&lt;/strong&gt; If &lt;code&gt;inputPosition&lt;/code&gt; does
not match where the axis is, the loop will move it there over the fade.
Match them first, and keep the limiters tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave the four target filters at their pass-through default for now. Tune a
notch only once you have measured the resonance you are aiming at.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tune the controllers — go to Tuning.&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 measurement chain right before touching a gain. Trace
&lt;code&gt;actuatorPositionActual&lt;/code&gt; and &lt;code&gt;actuatorVelocityActual&lt;/code&gt; at rest and in motion;
noise here becomes torque later.&lt;/li&gt;
&lt;li&gt;Set the two control-error delays to your fieldbus round trip. Getting these
wrong makes the position error look like a lag, and tempts you into gains
that oscillate.&lt;/li&gt;
&lt;li&gt;Tune the velocity controller first, then the position controller around it.
The usual order: raise proportional gain until the axis is stiff, add
integral until steady-state error clears, add derivative only if you must.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;actuatorPositionError&lt;/code&gt; throughout. It is the single best indicator of
whether the loop is healthy.&lt;/li&gt;
&lt;li&gt;Add feedforward once the feedback loop is stable. It reduces the error the
controllers have to work on rather than replacing them.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;usePvaActualForCompensation&lt;/code&gt; only if the feedforward is fighting the
commanded motion — normally the commanded motion is the right input.&lt;/li&gt;
&lt;li&gt;Set the window detector levels from the error you actually see, with margin.
They are your protection, not a diagnostic.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the control-error delays are
in &lt;strong&gt;cycles&lt;/strong&gt;, so their meaning in seconds changes with the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/actuator-control-engage-443f3226.svg&#34; alt=&#34;Engaging the loop. While idle the target follows the measured position; whengotoEngaged goes true it fades onto the commandedposition.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The fade is what keeps the axis from stepping at the moment you take control.&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 jumped when engaging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt; did not match the measured position&lt;/td&gt;
&lt;td&gt;Match them before engaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis stepped rather than faded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis moves while idle&lt;/td&gt;
&lt;td&gt;Something downstream is not respecting &lt;code&gt;motorPositionTarget&lt;/code&gt;, or the drive is in the wrong mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing moves when engaged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false, &lt;code&gt;disable&lt;/code&gt; is true, or the limiters are clamping&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;isEnabled&lt;/code&gt; and the limiters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position error grows with speed&lt;/td&gt;
&lt;td&gt;Feedforward is missing or under-tuned, or the control-error delays are wrong&lt;/td&gt;
&lt;td&gt;Set the delays first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates&lt;/td&gt;
&lt;td&gt;Controller gains too high, or the measurement is noisy&lt;/td&gt;
&lt;td&gt;Lower the gains; check the measurement filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates only at one frequency&lt;/td&gt;
&lt;td&gt;A structural mode&lt;/td&gt;
&lt;td&gt;Use the IIR filter in the transformation, or the vibration controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The vibration controller&amp;rsquo;s derivative gain does nothing&lt;/td&gt;
&lt;td&gt;It is forced to 0 in this version&lt;/td&gt;
&lt;td&gt;Use proportional and integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque appears on the wrong output&lt;/td&gt;
&lt;td&gt;Expected: the routing follows &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check which mode the drive reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The feedforward torque blips to zero for a moment when the drive changes mode&lt;/td&gt;
&lt;td&gt;Expected: the routing switches on the commanded mode, a few cycles before the drive reports the new one&lt;/td&gt;
&lt;td&gt;Nothing to fix; sequence mode changes at rest if the blip matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis received torque twice during a mode change&lt;/td&gt;
&lt;td&gt;Should not happen — the two channels are gated so only one is live&lt;/td&gt;
&lt;td&gt;Report it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode does nothing&lt;/td&gt;
&lt;td&gt;It requires a &lt;strong&gt;torque&lt;/strong&gt; drive mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt; and &lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis keeps jogging after the operator lets go&lt;/td&gt;
&lt;td&gt;Should not happen — the jog velocity is watchdogged to 0&lt;/td&gt;
&lt;td&gt;Check the writer is really stopping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The window detectors latch. The reset is &lt;strong&gt;not on this block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on the parent&amp;rsquo;s state input — it clears every axis at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything is scaled wrongly&lt;/td&gt;
&lt;td&gt;The gearbox ratio, or the transformations&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gearboxGain&lt;/code&gt; back and check the transformations first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop misbehaves after writing a gearbox value&lt;/td&gt;
&lt;td&gt;A zero tooth count is not refused here&lt;/td&gt;
&lt;td&gt;Read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A sub-tree I expected is missing&lt;/td&gt;
&lt;td&gt;The loop was built without that feature&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque detector&amp;rsquo;s thresholds seem to be in the wrong units&lt;/td&gt;
&lt;td&gt;It watches the &lt;strong&gt;raw sensor&lt;/strong&gt; value, unlike the other five&lt;/td&gt;
&lt;td&gt;Set its levels from a trace of that signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point once the transformations are correct:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearboxLoadSide       = &amp;lt;from the drivetrain&amp;gt;
gearboxMotorSide      = &amp;lt;from the drivetrain&amp;gt;
windowDetectorsEnable = true
positionController/delayForControlError/numberOfSamplesTarget = 4
positionController/delayForControlError/numberOfSamplesActual = 4
usePvaActualForCompensation = false
&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;Position and velocity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;positionLimiter&lt;/code&gt;, &lt;code&gt;velocityLimiter&lt;/code&gt; sub-trees&lt;/td&gt;
&lt;td&gt;Targets are clamped. &lt;strong&gt;The velocity target is zeroed&lt;/strong&gt; when the position limiter is limiting in the direction of travel, and the acceleration target when either is limiting&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target filter switched off&lt;/td&gt;
&lt;td&gt;Guarded in this block&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;unfiltered&lt;/strong&gt; signal is passed on. A bare &lt;code&gt;IIRFilter&lt;/code&gt; would output 0 here&lt;/td&gt;
&lt;td&gt;The filter&amp;rsquo;s &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target filter overshoot&lt;/td&gt;
&lt;td&gt;The limiter downstream of it&lt;/td&gt;
&lt;td&gt;Clamped, because every target filter runs &lt;strong&gt;before&lt;/strong&gt; its limiter&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault detection&lt;/td&gt;
&lt;td&gt;The six window detector sub-trees&lt;/td&gt;
&lt;td&gt;Latching warning and error flags per quantity&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s &lt;code&gt;noError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;parent&amp;rsquo;s&lt;/strong&gt; state input&lt;/td&gt;
&lt;td&gt;This block has no reset path of its own. Inside &lt;code&gt;AxesControl&lt;/code&gt; the reset arrives through its state channel and clears &lt;strong&gt;every&lt;/strong&gt; actuator at once. A standalone loop can be reset only by an application&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog velocity loss&lt;/td&gt;
&lt;td&gt;Watchdog&lt;/td&gt;
&lt;td&gt;Falls to &lt;strong&gt;0&lt;/strong&gt; if the writer stops&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engage transition&lt;/td&gt;
&lt;td&gt;The engaged switch sub-tree&lt;/td&gt;
&lt;td&gt;Faded, unless &lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additive torque routing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one of the two torque outputs carries it; the other contribution is 0. The gate follows the &lt;strong&gt;commanded&lt;/strong&gt; mode, so on a change &lt;strong&gt;into&lt;/strong&gt; a torque mode the additive torque drops out for the few cycles the drive takes to report the new mode&lt;/td&gt;
&lt;td&gt;Both outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Silently inactive&lt;/strong&gt; unless the drive is in a torque mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gearbox ratio&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A zero tooth count is not refused&lt;/strong&gt; and makes the conversion invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&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 anywhere in this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control error delays&lt;/td&gt;
&lt;td&gt;Fixed in &lt;strong&gt;cycles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Their meaning in seconds changes with the task rate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vibration controller derivative&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forced to 0&lt;/strong&gt; every cycle, whatever you write&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Torque, feedforward, sensor torque and the two controllers are each compiled in or out&lt;/td&gt;
&lt;td&gt;Missing sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis 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;. Sub-modules below it may log — the
transformation&amp;rsquo;s transducer 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.32.1 (340db23).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: ActuatorControlLoop</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/actuator-control/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/actuator-control/</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/control-loops/actuator-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/actuator-control-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;ActuatorControlLoop&lt;/code&gt; is the control loop for a single axis. It converts sensor
readings into engineering units, decides what the axis should be doing,
converts that back into drive units, and watches everything for faults.&lt;/p&gt;
&lt;p&gt;Everything else in this library exists to feed or be fed by this block.&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;inputPosition, inputVelocity, inputAcceleration, inputTorque&amp;quot;]) --&amp;gt; B[&amp;quot;ActuatorControlLoop&amp;quot;]
    i2([&amp;quot;motorPositionActual, motorVelocityActual, motorTorqueActual, sensorTorqueActual&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;gotoEngaged, gotoJogging, inputJogVelocity&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;driveMode, doInstantSwitchToggle, disable&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;impedance/inputExternalTorque, impedance/usePositionTarget, impedance/disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;motorPositionTarget, motorVelocityTarget, motorTorqueTarget, motorTorqueOffsetTarget&amp;quot;])
    B --&amp;gt; o2([&amp;quot;actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget, actuatorTorqueTarget&amp;quot;])
    B --&amp;gt; o3([&amp;quot;actuatorPositionActual, actuatorVelocityActual, actuatorTorqueActual, actuatorPowerActual&amp;quot;])
    B --&amp;gt; o4([&amp;quot;actuatorPositionError, actuatorVelocityError&amp;quot;])
    B --&amp;gt; o5([&amp;quot;…ActualFiltered outputs, gearboxGain, isEnabled&amp;quot;])
    B --&amp;gt; s1([&amp;quot;positionTransformation, velocityTransformation, torqueTransformation&amp;quot;])
    B --&amp;gt; s2([&amp;quot;positionController, velocityController, vibrationController&amp;quot;])
    B --&amp;gt; s3([&amp;quot;six window detectors, engagedSwitch, limiters, feedforwardControl&amp;quot;])
    B --&amp;gt; s4([&amp;quot;positionTargetFilter, velocityTargetFilter, torqueTargetFilter, torqueOffsetTargetFilter, vibrationFilter&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Four things determine how this block behaves. Get these right first.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gotoEngaged&lt;/code&gt; decides whether the axis follows you or follows itself.&lt;/strong&gt;
While it is false the target &lt;em&gt;tracks the measured position&lt;/em&gt;, so nothing jumps
when the drive powers on. While it is true the target follows
&lt;code&gt;inputPosition&lt;/code&gt;. The transition between the two is faded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;driveMode&lt;/code&gt; decides where the additive torque goes.&lt;/strong&gt; In a position or
velocity mode it leaves through &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;; in a torque mode
it leaves through &lt;code&gt;motorTorqueTarget&lt;/code&gt;. Exactly one of the two carries it at a
time, deliberately, so that a drive whose reported mode lags your commanded
mode cannot receive the same torque twice. &lt;strong&gt;This is about the additive
torque only&lt;/strong&gt; — the controller torques stay on &lt;code&gt;motorTorqueTarget&lt;/code&gt; in every
mode, so that output is generally non-zero in position and velocity mode too.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt; scale everything.&lt;/strong&gt; They are the
single most consequential pair of numbers in this block. &lt;strong&gt;Neither is checked
for zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block does not ship configured.&lt;/strong&gt; The limiters, the window detectors,
the transformations and the controllers all live in sub-trees and all need
setting. An unconfigured loop will not move an axis usefully and may not
protect it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;inside-the-loop&#34;&gt;Inside the loop&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/actuator-control-blocks-c8a616e3.svg&#34; alt=&#34;ActuatorControlLoop block diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Every sub-module on one sheet. Square blocks are sub-modules — the type inside,
the parameter-tree name below — and grey rounded boxes are logic in the loop&amp;rsquo;s
own code, quoting the leaves it reads. Fills mark the family: blue
controllers (the &lt;code&gt;Impedance&lt;/code&gt; block among them), light orange limiters, darker orange filters, green
transformations, light red window detectors, yellow switches. The
&lt;code&gt;FeedforwardController&lt;/code&gt; and &lt;code&gt;Impedance&lt;/code&gt; blocks hide several sub-modules each;
only their external ports are named — the feedforward&amp;rsquo;s inside is drawn in
&lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/&#34;&gt;feedforward-controller.md&lt;/a&gt;. The left half is the control system, top to bottom in the
order the code runs; the right half is the motor side, where each &lt;code&gt;DUAL&lt;/code&gt;
transformation is one tall block shared by the target lane flowing right to the
drive and the actual lane flowing left from it. A pointed tag is a signal that
would otherwise cross the sheet: it leaves at one tag and resumes at every tag
of the same name.&lt;/em&gt;&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;inputPosition&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 commanded position. Followed only while engaged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity, used as feedforward and as the target in a velocity mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputAcceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration, used by the feedforward path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;An additive torque. Filtered, then routed by &lt;code&gt;driveMode&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From a separate force or torque sensor, if fitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoEngaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Follow the input, or track the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Make that transition a &lt;strong&gt;step&lt;/strong&gt; instead of a fade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;drive enumeration&lt;/td&gt;
&lt;td&gt;Which mode the drive is in. Decides the torque routing and gates impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoJogging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;inputJogVelocity&lt;/code&gt; to the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputJogVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The jog speed. &lt;strong&gt;Watchdogged: if it stops being written it falls to 0&lt;/strong&gt; after a timeout.&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;true or false&lt;/td&gt;
&lt;td&gt;Disables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/inputExternalTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The external torque for impedance mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/usePositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Whether impedance limiting works from the target or the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Disables impedance mode.&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;motorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;To the drive.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;To the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a torque mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive unit&lt;/td&gt;
&lt;td&gt;To the drive, &lt;strong&gt;in a position or velocity mode only&lt;/strong&gt; — otherwise 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;What the loop decided the axis should do, before conversion. &lt;strong&gt;Trace this first when commissioning.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;Zeroed when the position limiter is limiting in the direction of travel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;Zeroed when either limiter is limiting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The torque path&amp;rsquo;s output: the position, velocity and impedance controller torques in every mode, plus the additive torque in a torque mode only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the axis is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPowerActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;power unit&lt;/td&gt;
&lt;td&gt;Torque times velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Target minus actual. &lt;strong&gt;The main health signal for the axis.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;The filtered measurements. These are what the loop uses internally while idle, and what the window detectors watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;The separate sensor, converted to actuator units.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorSensorTorqueActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide ÷ gearboxMotorSide&lt;/code&gt;. &lt;strong&gt;Read it back to check the ratio.&lt;/strong&gt;&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;The loop is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Impedance mode is active. &lt;strong&gt;It requires a torque drive mode&lt;/strong&gt; — enabling it in a position mode does nothing.&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;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Load-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Motor-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Turns all six fault detectors on. &lt;strong&gt;Leave this on in service.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Derive velocity from the filtered position instead of the drive&amp;rsquo;s own velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Feed the feedforward path the measured motion instead of the commanded motion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetDeadband&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Suppresses small torque targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables impedance mode, &lt;strong&gt;subject to the drive being in a torque mode&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The position PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/usePositionControlForTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Route the position PID&amp;rsquo;s output to torque instead of adding it to the velocity target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Delays the target before the error is formed, to match the fieldbus round trip. &lt;strong&gt;Typically 4 for EtherCAT.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/delayForControlError/numberOfSamplesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;The same for the measurement.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The velocity PID&amp;rsquo;s gains and limits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vibrationController/parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The vibration damper&amp;rsquo;s gains. &lt;strong&gt;Its derivative gain is forced to 0&lt;/strong&gt; whatever you write.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else is in the sub-trees — the transformations, the limiters, the
six window detectors, the feedforward controller and the backlash
compensation. All parameters are persistent and survive a controller restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which sub-trees exist depends on how the loop was built.&lt;/strong&gt; A loop built
without torque support has no &lt;code&gt;torqueTransformation&lt;/code&gt;, no &lt;code&gt;torqueWindowDetector&lt;/code&gt;
and no feedforward controller.&lt;/p&gt;
&lt;h3 id=&#34;the-drive-modes&#34;&gt;The drive modes&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;driveMode&lt;/code&gt; follows &lt;code&gt;mcx::drive::DriveMode&lt;/code&gt;, plus a few values specific to this block:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What the drive follows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cyclic synchronous position&lt;/td&gt;
&lt;td&gt;&lt;code&gt;motorPositionTarget&lt;/code&gt;, with &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt; as additive torque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;9&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cyclic synchronous velocity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;, with &lt;code&gt;motorTorqueOffsetTarget&lt;/code&gt; as additive torque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cyclic synchronous torque&lt;/td&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cyclic synchronous current&lt;/td&gt;
&lt;td&gt;&lt;code&gt;motorTorqueTarget&lt;/code&gt;, motor constant in the torque transformation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-110&lt;/code&gt; / &lt;code&gt;-109&lt;/code&gt; / &lt;code&gt;-108&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensodrive torque / velocity / position&lt;/td&gt;
&lt;td&gt;as &lt;code&gt;10&lt;/code&gt; / &lt;code&gt;9&lt;/code&gt; / &lt;code&gt;8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;..&lt;code&gt;7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;other &lt;code&gt;DriveMode&lt;/code&gt; values&lt;/td&gt;
&lt;td&gt;treated like a position or velocity mode: no torque command&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;the-three-controllers-leaf-by-leaf&#34;&gt;The three controllers, leaf by leaf&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;positionController/&lt;/code&gt;, &lt;code&gt;velocityController/&lt;/code&gt; and &lt;code&gt;vibrationController/&lt;/code&gt; have the same shape. Each has a &lt;code&gt;parameter/&lt;/code&gt;
folder of gains, an &lt;code&gt;input/&lt;/code&gt; folder of runtime inputs and an &lt;code&gt;output/&lt;/code&gt; folder:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;input/&lt;/code&gt; node&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;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The loop writes this every cycle from the engage state and the impedance mode; write it yourself only through the block&amp;rsquo;s own API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integratorFreeze&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hold the integrator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integratorReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero the integrator; clears itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controllerReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero every state; clears itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The error the module computed this cycle. Published for reading.&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;output/&lt;/code&gt; node&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;controllerOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The saturated controller output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integratorOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The integrator state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;derivativeOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The filtered derivative term.&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;code&gt;parameter/enable &amp;amp;&amp;amp; !input/disable&lt;/code&gt;. All outputs are zero while this is false.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;controllerparameter&#34;&gt;&lt;code&gt;&amp;lt;controller&amp;gt;/parameter/&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;All three controllers share these; the vibration controller ignores &lt;code&gt;derivativeGain&lt;/code&gt; (it is forced to zero).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;Default&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;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The controller&amp;rsquo;s own enable. &lt;strong&gt;Off by default: nothing is closed until you set it.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proportionalGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;P gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integratorGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;I gain, on the error outside &lt;code&gt;controlErrorDeadBand&lt;/code&gt;. Integration stops in the direction that would push further into a saturated output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backCalculationGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anti-windup: drives the integrator so the unsaturated output returns to the saturated one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;derivativeGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;D gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;derivativeFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bandwidth of the derivative filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlErrorDeadBand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Error below which the integrator does not integrate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlIntegrationMin&lt;/code&gt; / &lt;code&gt;controlIntegrationMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt; / &lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hard clamp on the integrator state. &lt;strong&gt;Both zero means the integrator is clamped to zero&lt;/strong&gt; — set them before using &lt;code&gt;integratorGain&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMin&lt;/code&gt; / &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-100.0&lt;/code&gt; / &lt;code&gt;100.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Saturation of &lt;code&gt;controllerOutput&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;target-shaping-filters&#34;&gt;Target shaping filters&lt;/h2&gt;
&lt;p&gt;Four &lt;code&gt;IIRFilter&lt;/code&gt; sub-trees shape the outgoing targets. Each is a full IIR of up
to order 6 — enough for two notches plus a lowpass — and each sits &lt;strong&gt;in front
of its limiter&lt;/strong&gt;, which in turn sits in front of the transformation:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;Shapes&lt;/th&gt;
&lt;th&gt;Order in the chain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the position target&lt;/td&gt;
&lt;td&gt;engaged switch → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;positionLimiter&lt;/code&gt; → &lt;code&gt;positionTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the velocity target&lt;/td&gt;
&lt;td&gt;engaged switch → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;velocityLimiter&lt;/code&gt; → &lt;code&gt;velocityTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the torque target&lt;/td&gt;
&lt;td&gt;torque sum → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;torqueLimiter&lt;/code&gt; → &lt;code&gt;torqueTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueOffsetTargetFilter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the additive torque&lt;/td&gt;
&lt;td&gt;offset sum → &lt;strong&gt;filter&lt;/strong&gt; → &lt;code&gt;torqueOffsetLimiter&lt;/code&gt; → &lt;code&gt;torqueOffsetTransformation&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;All four default to pass-through&lt;/strong&gt; (order 1, &lt;code&gt;num[0]&lt;/code&gt; = &lt;code&gt;den[0]&lt;/code&gt; = 1), so a
loop you have not tuned behaves exactly as it did before these filters existed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A filter that is switched off is a bypass, not a mute.&lt;/strong&gt; &lt;code&gt;IIRFilter&lt;/code&gt; on its
own 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 — which in a setpoint path would be a command to zero. This block
therefore guards every one of the four: whenever a filter is not actually
filtering, the &lt;strong&gt;unfiltered&lt;/strong&gt; signal is passed to the limiter instead. You can
disable any of these four safely. That guard is specific to this block; an
&lt;code&gt;IIRFilter&lt;/code&gt; you place in a setpoint path yourself has no such protection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The two torque filters are inside the feedback loops.&lt;/strong&gt; Their input is the
sum of the feedforward and the position and velocity controller outputs, so
tuning them &lt;strong&gt;changes loop stability&lt;/strong&gt;. That is exactly what you want from a
notch on a mechanical resonance, but treat these as stability parameters, not
as cosmetic smoothing. The position and velocity filters shape the reference
only and are outside every loop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Filtering happens before the limiter, and that is deliberate.&lt;/strong&gt; An IIR
response is not amplitude-bounded: a filter placed after a limiter can ring
past the limit and hand the drive a target outside the allowed envelope, and
it would also decouple the limiters&#39; own limiting flags — which drive the
velocity and acceleration zeroing — from the signal actually commanded. The
price is that clipping is not smoothed, which only matters in saturation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Feedforward does not see these filters.&lt;/strong&gt; The feedforward path taps the
setpoints &lt;em&gt;before&lt;/em&gt; the filters but &lt;em&gt;after&lt;/em&gt; the same limiting, so retuning a
filter never changes the feedforward torque. One second-order effect remains:
&lt;code&gt;positionLimiter&lt;/code&gt; is adaptive, and its active window is computed from the
filtered signal, so a tuned position filter shifts that window slightly and
the feedforward tap inherits the shift.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The acceleration target is &lt;strong&gt;not&lt;/strong&gt; filtered.&lt;/p&gt;
&lt;h3 id=&#34;the-vibration-dampers-own-filter&#34;&gt;The vibration damper&amp;rsquo;s own filter&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;vibrationFilter&lt;/code&gt; is a fifth &lt;code&gt;IIRFilter&lt;/code&gt;, in the vibration controller&amp;rsquo;s
&lt;em&gt;feedback&lt;/em&gt; path rather than a target path:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sensorTorqueActual → sensorTorqueHighPass → vibrationFilter → vibrationController
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;sensorTorqueHighPass&lt;/code&gt; is unchanged and still does the DC rejection that decides
what counts as vibration. &lt;code&gt;vibrationFilter&lt;/code&gt; sits behind it, pass-through by
default, and is where you put notches to keep higher-frequency content out of
the damper. It is guarded the same way as the four target filters, so switching
it off bypasses it instead of zeroing the feedback.&lt;/p&gt;
&lt;p&gt;Remember where the damper&amp;rsquo;s output goes: it is added to the &lt;strong&gt;additive torque
channel only&lt;/strong&gt;, so it is inactive in a torque mode whatever this filter is set
to.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the transformations first&lt;/strong&gt;, under &lt;code&gt;positionTransformation&lt;/code&gt; and
&lt;code&gt;velocityTransformation&lt;/code&gt;. Nothing else can be checked until the axis reports
its position correctly in engineering units.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt;, and read &lt;code&gt;gearboxGain&lt;/code&gt; back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the drive disabled, move the axis by hand and confirm
&lt;code&gt;actuatorPositionActual&lt;/code&gt; reads correctly over a long move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the limiters under &lt;code&gt;positionLimiter&lt;/code&gt; and &lt;code&gt;velocityLimiter&lt;/code&gt; to values the
machine cannot be hurt by.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the six window detectors&#39; levels from a trace of normal operation, and
set &lt;code&gt;windowDetectorsEnable&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;gotoEngaged&lt;/code&gt; false and enable the drive. Confirm
&lt;code&gt;actuatorPositionTarget&lt;/code&gt; tracks &lt;code&gt;actuatorPositionActual&lt;/code&gt; and the axis does
not move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;positionController/delayForControlError/numberOfSamplesTarget&lt;/code&gt; and
&lt;code&gt;…Actual&lt;/code&gt; to match your fieldbus — 4 samples is typical for EtherCAT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;inputPosition&lt;/code&gt; the axis&amp;rsquo;s current measured position, then set
&lt;code&gt;gotoEngaged&lt;/code&gt; true.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 hands the axis to your setpoint source.&lt;/strong&gt; If &lt;code&gt;inputPosition&lt;/code&gt; does
not match where the axis is, the loop will move it there over the fade.
Match them first, and keep the limiters tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave the four target filters at their pass-through default for now. Tune a
notch only once you have measured the resonance you are aiming at.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tune the controllers — go to Tuning.&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 measurement chain right before touching a gain. Trace
&lt;code&gt;actuatorPositionActual&lt;/code&gt; and &lt;code&gt;actuatorVelocityActual&lt;/code&gt; at rest and in motion;
noise here becomes torque later.&lt;/li&gt;
&lt;li&gt;Set the two control-error delays to your fieldbus round trip. Getting these
wrong makes the position error look like a lag, and tempts you into gains
that oscillate.&lt;/li&gt;
&lt;li&gt;Tune the velocity controller first, then the position controller around it.
The usual order: raise proportional gain until the axis is stiff, add
integral until steady-state error clears, add derivative only if you must.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;actuatorPositionError&lt;/code&gt; throughout. It is the single best indicator of
whether the loop is healthy.&lt;/li&gt;
&lt;li&gt;Add feedforward once the feedback loop is stable. It reduces the error the
controllers have to work on rather than replacing them.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;usePvaActualForCompensation&lt;/code&gt; only if the feedforward is fighting the
commanded motion — normally the commanded motion is the right input.&lt;/li&gt;
&lt;li&gt;Set the window detector levels from the error you actually see, with margin.
They are your protection, not a diagnostic.&lt;/li&gt;
&lt;li&gt;Re-check everything after a task-rate change: the control-error delays are
in &lt;strong&gt;cycles&lt;/strong&gt;, so their meaning in seconds changes with the rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/actuator-control-engage-443f3226.svg&#34; alt=&#34;Engaging the loop. While idle the target follows the measured position; whengotoEngaged goes true it fades onto the commandedposition.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The fade is what keeps the axis from stepping at the moment you take control.&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 jumped when engaging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt; did not match the measured position&lt;/td&gt;
&lt;td&gt;Match them before engaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis stepped rather than faded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis moves while idle&lt;/td&gt;
&lt;td&gt;Something downstream is not respecting &lt;code&gt;motorPositionTarget&lt;/code&gt;, or the drive is in the wrong mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing moves when engaged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false, &lt;code&gt;disable&lt;/code&gt; is true, or the limiters are clamping&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;isEnabled&lt;/code&gt; and the limiters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position error grows with speed&lt;/td&gt;
&lt;td&gt;Feedforward is missing or under-tuned, or the control-error delays are wrong&lt;/td&gt;
&lt;td&gt;Set the delays first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates&lt;/td&gt;
&lt;td&gt;Controller gains too high, or the measurement is noisy&lt;/td&gt;
&lt;td&gt;Lower the gains; check the measurement filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis oscillates only at one frequency&lt;/td&gt;
&lt;td&gt;A structural mode&lt;/td&gt;
&lt;td&gt;Use the IIR filter in the transformation, or the vibration controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The vibration controller&amp;rsquo;s derivative gain does nothing&lt;/td&gt;
&lt;td&gt;It is forced to 0 in this version&lt;/td&gt;
&lt;td&gt;Use proportional and integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque appears on the wrong output&lt;/td&gt;
&lt;td&gt;Expected: the routing follows &lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check which mode the drive reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The feedforward torque blips to zero for a moment when the drive changes mode&lt;/td&gt;
&lt;td&gt;Expected: the routing switches on the commanded mode, a few cycles before the drive reports the new one&lt;/td&gt;
&lt;td&gt;Nothing to fix; sequence mode changes at rest if the blip matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis received torque twice during a mode change&lt;/td&gt;
&lt;td&gt;Should not happen — the two channels are gated so only one is live&lt;/td&gt;
&lt;td&gt;Report it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode does nothing&lt;/td&gt;
&lt;td&gt;It requires a &lt;strong&gt;torque&lt;/strong&gt; drive mode&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;driveMode&lt;/code&gt; and &lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis keeps jogging after the operator lets go&lt;/td&gt;
&lt;td&gt;Should not happen — the jog velocity is watchdogged to 0&lt;/td&gt;
&lt;td&gt;Check the writer is really stopping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The window detectors latch. The reset is &lt;strong&gt;not on this block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on the parent&amp;rsquo;s state input — it clears every axis at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything is scaled wrongly&lt;/td&gt;
&lt;td&gt;The gearbox ratio, or the transformations&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gearboxGain&lt;/code&gt; back and check the transformations first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop misbehaves after writing a gearbox value&lt;/td&gt;
&lt;td&gt;A zero tooth count is not refused here&lt;/td&gt;
&lt;td&gt;Read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A sub-tree I expected is missing&lt;/td&gt;
&lt;td&gt;The loop was built without that feature&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque detector&amp;rsquo;s thresholds seem to be in the wrong units&lt;/td&gt;
&lt;td&gt;It watches the &lt;strong&gt;raw sensor&lt;/strong&gt; value, unlike the other five&lt;/td&gt;
&lt;td&gt;Set its levels from a trace of that signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point once the transformations are correct:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearboxLoadSide       = &amp;lt;from the drivetrain&amp;gt;
gearboxMotorSide      = &amp;lt;from the drivetrain&amp;gt;
windowDetectorsEnable = true
positionController/delayForControlError/numberOfSamplesTarget = 4
positionController/delayForControlError/numberOfSamplesActual = 4
usePvaActualForCompensation = false
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;worked-configuration--cyclic-synchronous-position&#34;&gt;Worked configuration — cyclic synchronous position&lt;/h2&gt;
&lt;p&gt;A drive in mode &lt;code&gt;8&lt;/code&gt;, with a 2000-count encoder behind a 50:1 gearbox, the position loop closed in the drive and the
module supplying the position target and a feedforward torque on the offset channel. Values not listed stay at
their defaults.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Set to&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;50:1 reduction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionTransformation/ticksPerRevolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;encoder counts per motor revolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionTransformation/gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;, &lt;code&gt;6.283185307179586&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain = gainNum / gainDen * ticksPerRevolution&lt;/code&gt; ticks per unit of position: 2000 ticks per 2π of motor rotation; the gearbox ratio is applied on top automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityTransformation/ticksPerRevolution&lt;/code&gt;, &lt;code&gt;gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;as above&lt;/td&gt;
&lt;td&gt;same scaling for the drive&amp;rsquo;s velocity unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueOffsetTransformation/gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive-specific&lt;/td&gt;
&lt;td&gt;actuator torque → the drive&amp;rsquo;s torque unit; the gearbox ratio is applied automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;travel limits on the setpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the travel&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-2.0&lt;/code&gt;, &lt;code&gt;2.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;what the mechanism may do&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueOffsetLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;never ask the drive for more than the motor delivers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueOffsetLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor rating&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;engagedSwitch/fadeInTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one second from the actual position onto the setpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feedforwardControl/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model-based additive torque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feedforwardControl/externalMass&lt;/code&gt;, &lt;code&gt;gravity&lt;/code&gt;, …&lt;/td&gt;
&lt;td&gt;the model&lt;/td&gt;
&lt;td&gt;see &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/&#34;&gt;&lt;code&gt;feedforward-controller.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; &lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;the drive closes the position loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; &lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionErrorWindowDetector/low&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-0.01&lt;/code&gt;, &lt;code&gt;0.01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;following-error warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionErrorWindowDetector/tooLow&lt;/code&gt;, &lt;code&gt;tooHigh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-0.05&lt;/code&gt;, &lt;code&gt;0.05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;following-error error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Operate it with &lt;code&gt;driveMode = 8&lt;/code&gt;, then &lt;code&gt;gotoEngaged = 1&lt;/code&gt; once the drive is operational and the transformation is
referenced.&lt;/p&gt;
&lt;h2 id=&#34;worked-configuration--cyclic-synchronous-torque-loops-closed-here&#34;&gt;Worked configuration — cyclic synchronous torque, loops closed here&lt;/h2&gt;
&lt;p&gt;The same actuator in mode &lt;code&gt;10&lt;/code&gt;: the drive follows &lt;code&gt;motorTorqueTarget&lt;/code&gt;, and the module closes both loops as a
position-over-velocity cascade.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Set to&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;transformations, gearbox, limiters, &lt;code&gt;engagedSwitch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;as the position example&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueTransformation/gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;drive-specific&lt;/td&gt;
&lt;td&gt;actuator torque → the drive&amp;rsquo;s torque unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the torque channel is now the command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torqueLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor rating&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inner loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/proportionalGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuned&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/integratorGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuned&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/controlIntegrationMin&lt;/code&gt;, &lt;code&gt;controlIntegrationMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-10&lt;/code&gt;, &lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;must be set&lt;/strong&gt; — both at zero clamps the integrator to zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityController/parameter/controlOutputMin&lt;/code&gt;, &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor rating&lt;/td&gt;
&lt;td&gt;saturation of the controller torque&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;outer loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter/proportionalGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuned&lt;/td&gt;
&lt;td&gt;velocity per unit of position error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter/controlOutputMin&lt;/code&gt;, &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-2.0&lt;/code&gt;, &lt;code&gt;2.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the outer loop may not ask for more than &lt;code&gt;velocityLimiter&lt;/code&gt; allows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/usePositionControlForTorque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; &lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;cascade, not a direct position-to-torque loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feedforwardControl/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;in a torque mode the feedforward goes out on &lt;code&gt;motorTorqueTarget&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The feedforward moves from the offset channel to the torque channel by itself when &lt;code&gt;driveMode&lt;/code&gt; changes to &lt;code&gt;10&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;worked-configuration--impedance&#34;&gt;Worked configuration — impedance&lt;/h2&gt;
&lt;p&gt;Torque mode as the torque example, and the actuator made compliant to an external controller writing
&lt;code&gt;impedance/inputExternalTorque&lt;/code&gt;, with a soft fence of &lt;code&gt;±0.1&lt;/code&gt; around wherever the actuator is:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Set to&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;as the torque example&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/externalTorqueLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bound what the outside may ask&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/externalTorqueLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor rating&lt;/td&gt;
&lt;td&gt;&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/positionLimiter/enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the fence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;impedance/positionLimiter/lowerLimit&lt;/code&gt;, &lt;code&gt;upperLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-0.1&lt;/code&gt;, &lt;code&gt;0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the fence, around the actual position (&lt;code&gt;impedance/usePositionTarget = 0&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positionController/parameter/proportionalGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuned&lt;/td&gt;
&lt;td&gt;this is the stiffness of the fence: only the position controller pushes back, on the undelayed error and without integrator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt; confirms the mode is active; it needs &lt;code&gt;driveMode = 10&lt;/code&gt;, &lt;code&gt;impedance/enable&lt;/code&gt; and
&lt;code&gt;impedance/disable = 0&lt;/code&gt;. While it is active &lt;code&gt;getPositionClosedLoop()&lt;/code&gt; reports the loop as open.&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;Position and velocity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;positionLimiter&lt;/code&gt;, &lt;code&gt;velocityLimiter&lt;/code&gt; sub-trees&lt;/td&gt;
&lt;td&gt;Targets are clamped. &lt;strong&gt;The velocity target is zeroed&lt;/strong&gt; when the position limiter is limiting in the direction of travel, and the acceleration target when either is limiting&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target filter switched off&lt;/td&gt;
&lt;td&gt;Guarded in this block&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;unfiltered&lt;/strong&gt; signal is passed on. A bare &lt;code&gt;IIRFilter&lt;/code&gt; would output 0 here&lt;/td&gt;
&lt;td&gt;The filter&amp;rsquo;s &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target filter overshoot&lt;/td&gt;
&lt;td&gt;The limiter downstream of it&lt;/td&gt;
&lt;td&gt;Clamped, because every target filter runs &lt;strong&gt;before&lt;/strong&gt; its limiter&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault detection&lt;/td&gt;
&lt;td&gt;The six window detector sub-trees&lt;/td&gt;
&lt;td&gt;Latching warning and error flags per quantity&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s &lt;code&gt;noError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;parent&amp;rsquo;s&lt;/strong&gt; state input&lt;/td&gt;
&lt;td&gt;This block has no reset path of its own. Inside &lt;code&gt;AxesControl&lt;/code&gt; the reset arrives through its state channel and clears &lt;strong&gt;every&lt;/strong&gt; actuator at once. A standalone loop can be reset only by an application&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog velocity loss&lt;/td&gt;
&lt;td&gt;Watchdog&lt;/td&gt;
&lt;td&gt;Falls to &lt;strong&gt;0&lt;/strong&gt; if the writer stops&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engage transition&lt;/td&gt;
&lt;td&gt;The engaged switch sub-tree&lt;/td&gt;
&lt;td&gt;Faded, unless &lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additive torque routing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one of the two torque outputs carries it; the other contribution is 0. The gate follows the &lt;strong&gt;commanded&lt;/strong&gt; mode, so on a change &lt;strong&gt;into&lt;/strong&gt; a torque mode the additive torque drops out for the few cycles the drive takes to report the new mode&lt;/td&gt;
&lt;td&gt;Both outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impedance mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Silently inactive&lt;/strong&gt; unless the drive is in a torque mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;impedance/isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gearbox ratio&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A zero tooth count is not refused&lt;/strong&gt; and makes the conversion invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&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 anywhere in this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control error delays&lt;/td&gt;
&lt;td&gt;Fixed in &lt;strong&gt;cycles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Their meaning in seconds changes with the task rate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vibration controller derivative&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forced to 0&lt;/strong&gt; every cycle, whatever you write&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features present&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Torque, feedforward, sensor torque and the two controllers are each compiled in or out&lt;/td&gt;
&lt;td&gt;Missing sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis 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;. Sub-modules below it may log — the
transformation&amp;rsquo;s transducer 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.32.1 (340db23).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: SimpleActuatorControlLoop</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/simple-actuator-control/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/simple-actuator-control/</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;SimpleActuatorControlLoop&lt;/code&gt; is the reduced control loop for a single axis. It
converts sensor readings into engineering units, switches between tracking the
measurement and following a command, converts the result back into drive units,
and watches four quantities for faults.&lt;/p&gt;
&lt;p&gt;Use it for an axis whose &lt;strong&gt;drive closes its own position loop&lt;/strong&gt; and which needs
no torque handling.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This loop does not limit anything.&lt;/strong&gt; There is no position limiter and no
velocity limiter — whatever you write to &lt;code&gt;inputPosition&lt;/code&gt; goes to the drive,
converted. Whatever feeds this block is entirely responsible for keeping the
commanded motion within what the machine can take. The full
&lt;code&gt;ActuatorControlLoop&lt;/code&gt; does clamp its setpoints.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Compared with the full loop it also has &lt;strong&gt;no torque path, no feedback
controllers, no feedforward, no impedance mode and no jogging&lt;/strong&gt;. If you need
any of those, use &lt;code&gt;ActuatorControlLoop&lt;/code&gt;.&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;inputPosition, inputVelocity, inputAcceleration&amp;quot;]) --&amp;gt; B[&amp;quot;SimpleActuatorControlLoop&amp;quot;]
    i2([&amp;quot;motorPositionActual, motorVelocityActual&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;gotoEngaged, doInstantSwitchToggle&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;driveMode, disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;motorPositionTarget, motorVelocityTarget&amp;quot;])
    B --&amp;gt; o2([&amp;quot;actuatorPositionTarget, actuatorVelocityTarget, actuatorAccelerationTarget&amp;quot;])
    B --&amp;gt; o3([&amp;quot;actuatorPositionActual, actuatorVelocityActual&amp;quot;])
    B --&amp;gt; o4([&amp;quot;actuatorPositionError, actuatorVelocityError&amp;quot;])
    B --&amp;gt; o5([&amp;quot;…ActualFiltered outputs, gearboxGain, isEnabled&amp;quot;])
    B --&amp;gt; s1([&amp;quot;positionTransformation, velocityTransformation&amp;quot;])
    B --&amp;gt; s2([&amp;quot;engagedSwitch, actualVelocityFilter, motorBacklashCompensation&amp;quot;])
    B --&amp;gt; s3([&amp;quot;four window detectors&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gotoEngaged&lt;/code&gt; decides whether the axis follows you or follows itself.&lt;/strong&gt;
While false, the target tracks the measured position so nothing jumps when
the drive powers on. While true, it follows &lt;code&gt;inputPosition&lt;/code&gt;. The transition
is faded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;During that fade, &lt;code&gt;actuatorVelocityTarget&lt;/code&gt; and &lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;
do not match &lt;code&gt;actuatorPositionTarget&lt;/code&gt;.&lt;/strong&gt; They are correct before and after,
and wrong in between, by an amount that grows with the gap between where the
axis is and where you are commanding it. Match them before engaging.&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;inputPosition&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 commanded position. Followed only while engaged. &lt;strong&gt;Not limited by this block.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputAcceleration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;From the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gotoEngaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Follow the input, or track the measured position.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Make that transition a &lt;strong&gt;step&lt;/strong&gt; instead of a fade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;drive enumeration&lt;/td&gt;
&lt;td&gt;Which mode the drive is in. Recorded, but this loop has no torque routing to gate on it.&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;true or false&lt;/td&gt;
&lt;td&gt;Disables the loop.&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;motorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;To the drive.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;motorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ticks per second&lt;/td&gt;
&lt;td&gt;To the drive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;What the loop decided, before conversion. &lt;strong&gt;Trace this first.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;Wrong during an engage fade — see the callout above.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationTarget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;Same.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Where the axis is.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;From the drive, or derived from the position — see &lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;Target minus actual. &lt;strong&gt;The main health signal.&lt;/strong&gt; Its exact definition depends on &lt;code&gt;useAngleDiffForPositionError&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorPositionActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit&lt;/td&gt;
&lt;td&gt;The filtered measurements. These are what the loop tracks while idle, and what three of the four detectors watch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorVelocityActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorAccelerationActualFiltered&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide ÷ gearboxMotorSide&lt;/code&gt;. &lt;strong&gt;Read it back to check the ratio.&lt;/strong&gt;&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;The loop is running.&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;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Enables the loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxLoadSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Load-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearboxMotorSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;teeth&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Motor-side tooth count. &lt;strong&gt;Not checked for zero.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;windowDetectorsEnable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Turns all four fault detectors on. &lt;strong&gt;Leave this on in service.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Derive the velocity from the position instead of taking the drive&amp;rsquo;s own velocity. Useful when the drive&amp;rsquo;s velocity is noisy or absent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useAngleDiffForPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;For a &lt;strong&gt;rotary axis&lt;/strong&gt;: compute the position error by the shortest angular path, so an axis near the wrap-around point does not report a full-turn error. &lt;strong&gt;It also switches the error to use the filtered position&lt;/strong&gt;, which changes its noise and lag.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Has no effect in this loop.&lt;/strong&gt; It selects a feedforward input, and this loop has no feedforward.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Everything else is in the sub-trees — the two transformations, the engage
switch, the velocity filter, the backlash compensation, and the four window
detectors. All parameters are persistent and survive a controller restart.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the transformations first&lt;/strong&gt;, under &lt;code&gt;positionTransformation&lt;/code&gt; and
&lt;code&gt;velocityTransformation&lt;/code&gt;. Nothing else can be checked until the axis reports
its position correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;gearboxLoadSide&lt;/code&gt; and &lt;code&gt;gearboxMotorSide&lt;/code&gt;, and read &lt;code&gt;gearboxGain&lt;/code&gt; back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the drive disabled, move the axis by hand and confirm
&lt;code&gt;actuatorPositionActual&lt;/code&gt; reads correctly over a long move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm whatever feeds &lt;code&gt;inputPosition&lt;/code&gt; has its own limits.&lt;/strong&gt; This block
has none.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the four window detectors&#39; levels from a trace of normal operation, and
set &lt;code&gt;windowDetectorsEnable&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For a rotary axis that wraps, set &lt;code&gt;useAngleDiffForPositionError&lt;/code&gt; true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;gotoEngaged&lt;/code&gt; false and enable the drive. Confirm
&lt;code&gt;actuatorPositionTarget&lt;/code&gt; tracks &lt;code&gt;actuatorPositionActual&lt;/code&gt; and the axis does
not move.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed &lt;code&gt;inputPosition&lt;/code&gt; the axis&amp;rsquo;s current measured position, then set
&lt;code&gt;gotoEngaged&lt;/code&gt; true.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 hands the axis to your setpoint source, with nothing between them
but a unit conversion.&lt;/strong&gt; If &lt;code&gt;inputPosition&lt;/code&gt; does not match where the axis
is, the loop will move it there over the fade, at whatever rate the fade
implies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move slowly and watch &lt;code&gt;actuatorPositionError&lt;/code&gt;.&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 are no gains here — the drive closes the loop. The tuning is in the
measurement chain and the fault levels.&lt;/li&gt;
&lt;li&gt;Set the measurement filtering under &lt;code&gt;positionTransformation/actualLP1Filter&lt;/code&gt;
from the noise you see. Lower cut-off is smoother and laggier.&lt;/li&gt;
&lt;li&gt;Choose the velocity source. If the drive reports velocity cleanly, use it;
if not, set &lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt; and take the derivative
of the filtered position instead.&lt;/li&gt;
&lt;li&gt;Set the engage fade time under &lt;code&gt;engagedSwitch/fadeInTime&lt;/code&gt; from how abruptly
you are willing to take control. Longer is gentler, and longer also means
the velocity and acceleration targets are wrong for longer.&lt;/li&gt;
&lt;li&gt;Set the backlash compensation only if the drivetrain has measurable
backlash, and only after the position reads correctly.&lt;/li&gt;
&lt;li&gt;Set the four detector levels from the error you actually see, with margin.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;actuatorPositionError&lt;/code&gt; through a full working cycle before declaring
the axis commissioned.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/simple-actuator-control-toggle-497dee55.svg&#34; alt=&#34;Engaging with and without the instant toggle. The faded transition takes thetarget smoothly from the measured position to the commanded one; the instanttoggle steps it.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The step is what you get with &lt;code&gt;doInstantSwitchToggle&lt;/code&gt; true. Use it only when
the two values already match.&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 jumped when engaging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt; did not match the measured position, and nothing limits the difference&lt;/td&gt;
&lt;td&gt;Match them before engaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis stepped rather than faded&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doInstantSwitchToggle&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Set it false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis moved further or faster than intended&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;This block has no limiters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limit the source that feeds &lt;code&gt;inputPosition&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing moves when engaged&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;Check &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The velocity target looks wrong during engaging&lt;/td&gt;
&lt;td&gt;Expected in this version: it does not match the position target during the fade&lt;/td&gt;
&lt;td&gt;Match the values before engaging, or shorten the fade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A rotary axis reports a full-turn error&lt;/td&gt;
&lt;td&gt;The position error is not using the shortest angular path&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;useAngleDiffForPositionError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position error got smoother when I set that flag&lt;/td&gt;
&lt;td&gt;Expected: it also switches to the filtered position&lt;/td&gt;
&lt;td&gt;Nothing, but be aware the signal changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The velocity is noisy&lt;/td&gt;
&lt;td&gt;The drive&amp;rsquo;s velocity is noisy&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;usePositionActualFilteredForVelocity&lt;/code&gt; and tune the filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The window detectors latch. The reset is &lt;strong&gt;not on this block&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on the parent&amp;rsquo;s state input — it clears every axis at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt; does nothing&lt;/td&gt;
&lt;td&gt;Expected: there is no feedforward in this loop&lt;/td&gt;
&lt;td&gt;Use the full loop if you need feedforward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything is scaled wrongly&lt;/td&gt;
&lt;td&gt;The gearbox ratio, or the transformations&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;gearboxGain&lt;/code&gt; back; check the transformations first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop misbehaves after writing a gearbox value&lt;/td&gt;
&lt;td&gt;A zero tooth count is not refused&lt;/td&gt;
&lt;td&gt;Read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need torque control, feedforward, or jogging&lt;/td&gt;
&lt;td&gt;Not in this loop&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;ActuatorControlLoop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need the setpoints limited here&lt;/td&gt;
&lt;td&gt;Not in this loop&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;ActuatorControlLoop&lt;/code&gt;, or limit upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point once the transformations are correct:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gearboxLoadSide                      = &amp;lt;from the drivetrain&amp;gt;
gearboxMotorSide                     = &amp;lt;from the drivetrain&amp;gt;
windowDetectorsEnable                = true
usePositionActualFilteredForVelocity = false
useAngleDiffForPositionError         = false   (true for a rotary axis)
&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;Setpoint limiting&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing in this block clamps position, velocity or acceleration.&lt;/strong&gt; The commanded value reaches the drive converted&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault detection&lt;/td&gt;
&lt;td&gt;The four window detector sub-trees&lt;/td&gt;
&lt;td&gt;Latching warning and error flags on the position error, position, velocity and acceleration&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s &lt;code&gt;noError&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;parent&amp;rsquo;s&lt;/strong&gt; state input&lt;/td&gt;
&lt;td&gt;This block has no reset path of its own. Inside &lt;code&gt;AxesControl&lt;/code&gt; the reset arrives through its state channel and clears &lt;strong&gt;every&lt;/strong&gt; actuator at once&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engage transition&lt;/td&gt;
&lt;td&gt;The engage switch sub-tree&lt;/td&gt;
&lt;td&gt;Faded, unless &lt;code&gt;doInstantSwitchToggle&lt;/code&gt;. &lt;strong&gt;The velocity and acceleration targets do not match the position target during the fade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position error definition&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useAngleDiffForPositionError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plain subtraction, or the shortest angular path — &lt;strong&gt;and the second also switches to the filtered measurement&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gearbox ratio&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A zero tooth count is not refused&lt;/strong&gt; and makes the conversion invalid&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gearboxGain&lt;/code&gt;&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 anywhere in this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usePvaActualForCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Inert.&lt;/strong&gt; There is no feedforward here for it to select&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driveMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Recorded but not acted on — there is no torque path to route&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature set&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;No torque, no controllers, no feedforward, no impedance, no jogging, no limiters&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis 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 transducers below it log 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: AxesControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.30/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/axes-control-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/control-loops/axes-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-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/control-loops/axes-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;AxesControl&lt;/code&gt; is the multi-axis orchestrator. It takes the motion you command
in &lt;strong&gt;axis space&lt;/strong&gt;, conditions and limits it, converts it into &lt;strong&gt;actuator
space&lt;/strong&gt;, runs one control loop per actuator, and converts the measurements
back.&lt;/p&gt;
&lt;p&gt;It is where the distinction between an axis and an actuator lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;axis&lt;/strong&gt; is what you command — a joint of the kinematic model, a Cartesian
direction, a virtual degree of freedom.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;actuator&lt;/strong&gt; is a physical drive with its own encoder, drive mode and
control loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two matrices relate them, so one axis may drive several actuators and one
actuator may be shared by several axes — differential drives, tandem axes, belt
couplings.&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;axesPositionsInput, axesVelocitiesInput, axesAccelerationsInput&amp;quot;]) --&amp;gt; B[&amp;quot;AxesControl&amp;quot;]
    i2([&amp;quot;axesTorquesInput&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;axesHomingJogVelocitiesInput, axesHomingGotoJog&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual&amp;quot;])
    B --&amp;gt; o2([&amp;quot;sensorTorquesActual&amp;quot;])
    B --&amp;gt; o3([&amp;quot;axesIsOpenLoop&amp;quot;])
    B --&amp;gt; s1([&amp;quot;actuatorControlLoops — one per actuator&amp;quot;])
    B --&amp;gt; s2([&amp;quot;axesLimiters, smoothStop&amp;quot;])
    B --&amp;gt; s3([&amp;quot;interpolators, axesSetpointJumpDetectors, axesTorqueDetectors&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The number of axes and the number of actuators are fixed when the
controller is built&lt;/strong&gt;, and they need not be equal. So are the two matrices&#39;
dimensions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost all the configuration is in the sub-trees.&lt;/strong&gt; The per-axis limits are
under &lt;code&gt;axesLimiters&lt;/code&gt;, and every actuator has a complete control loop of its
own under &lt;code&gt;actuatorControlLoops&lt;/code&gt;. Budget commissioning time per actuator, not
per machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; is the signal to watch.&lt;/strong&gt; It tells you, per axis, whether
the loop is genuinely closed. An axis driven by two actuators whose
contributions cancel is &lt;strong&gt;not&lt;/strong&gt; closed, and this output is what says so.&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;axesPositionsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded position per axis. Interpolated, limited, then converted to actuator space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorquesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded torque per axis. &lt;strong&gt;Only present when the actuator loops were built with torque support.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;A jog speed per axis, for homing. &lt;strong&gt;Maps onto every actuator that axis drives.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingGotoJog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Enables that jog, per axis.&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;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;Where each axis is, transformed back from the actuator measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorquesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;Per axis, when the actuator loops have torque sensors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the position loop is not closed.&lt;/strong&gt; True when no actuator driving that axis is closing its loop, &lt;strong&gt;and also when their contributions cancel&lt;/strong&gt; — check it before trusting an axis&amp;rsquo;s position.&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;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;How many control cycles pass between updates from your motion source. 1 disables interpolation. Interpolating adds &lt;strong&gt;one source period of delay&lt;/strong&gt; — acceptable when this block runs faster than the planner feeding it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That is the only parameter at this level. The rest is 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;axesLimiters/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The per-axis position, velocity, acceleration and jerk limits.&lt;/strong&gt; The main safety configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smoothStop/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How the machine is brought to rest on a stop or a detected setpoint jump.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis: how large a setpoint jump must be to count as a fault.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorqueDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis torque supervision, on torque-capable builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorControlLoops/actuatorControlLoop&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A complete actuator control loop per actuator.&lt;/strong&gt; See the &lt;code&gt;ActuatorControlLoop&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commission each actuator loop individually first&lt;/strong&gt;, under
&lt;code&gt;actuatorControlLoops&lt;/code&gt;. Get every transformation, gearbox ratio and window
detector right one actuator at a time. Nothing at this level can be checked
until they are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the two axis-to-actuator matrices from the machine&amp;rsquo;s mechanics. &lt;strong&gt;They
are checked for dimension&lt;/strong&gt; — a mismatched matrix is rejected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With everything disengaged, move each axis by hand and confirm
&lt;code&gt;axesPositionsActual&lt;/code&gt; reads correctly. This is the check that the matrices
are the right way round.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; reads true on every axis while the drives are off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the per-axis limits under &lt;code&gt;axesLimiters&lt;/code&gt; to values the machine cannot be
hurt by. &lt;strong&gt;Start conservative.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; from your motion source&amp;rsquo;s real update rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the setpoint-jump detectors so a plausible planner glitch is caught but
normal motion is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Engage one axis at a time, at low speed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 moves the machine under your motion source&amp;rsquo;s control.&lt;/strong&gt; With
several actuators per axis, a matrix error moves more than one drive.
Have the stop within reach and the axis limits tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; goes false on the axes you engaged.&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;All the loop tuning is per actuator, one level down. This level is limits,
interpolation and stopping behaviour.&lt;/li&gt;
&lt;li&gt;Set the axis limits from what the mechanism can take, expressed in axis
units. They apply before the conversion to actuator space, so one axis limit
protects every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Raise the limits in steps, re-testing each time, rather than setting them
from a datasheet and hoping.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; to the true ratio between this block&amp;rsquo;s rate
and your motion source&amp;rsquo;s. Too low shows as a step at each source update; too
high shows as staleness.&lt;/li&gt;
&lt;li&gt;If the interpolation delay matters — a machine where this block is inside a
feedback path — run the source at this block&amp;rsquo;s rate and set the factor to 1.&lt;/li&gt;
&lt;li&gt;Tune the smooth stop so an emergency stop is firm but does not itself become
a fault. Test it deliberately.&lt;/li&gt;
&lt;li&gt;Set the setpoint-jump detectors last, once you know what a normal setpoint
stream looks like.&lt;/li&gt;
&lt;li&gt;Re-check the axis limits after any change to the matrices — the same axis
limit means a different actuator motion once the coupling changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/axes-control-limiting-22d534c7.svg&#34; alt=&#34;An axis commanded faster than it can move, and the limited target. Thelimiter holds the axis to its configured velocity and acceleration rather thanfollowing the command.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The gap between the two traces is motion your source asked for and did not get.
If that gap is routine, your source and your limits disagree.&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;An axis moves the wrong actuators&lt;/td&gt;
&lt;td&gt;The axis-to-actuator matrix is wrong or transposed&lt;/td&gt;
&lt;td&gt;Move each axis by hand and check &lt;code&gt;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A matrix was rejected&lt;/td&gt;
&lt;td&gt;Its dimensions do not match the axis and actuator counts&lt;/td&gt;
&lt;td&gt;Check both counts; they need not be equal to each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; stays true on an engaged axis&lt;/td&gt;
&lt;td&gt;No actuator driving that axis is closing its loop, &lt;strong&gt;or their contributions cancel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check each actuator loop&amp;rsquo;s own engage state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis reads a position but is not controlled&lt;/td&gt;
&lt;td&gt;Same cause — this is what &lt;code&gt;axesIsOpenLoop&lt;/code&gt; exists to tell you&lt;/td&gt;
&lt;td&gt;Do not trust the axis until it reads false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine will not follow the commanded speed&lt;/td&gt;
&lt;td&gt;The axis limiters are clamping&lt;/td&gt;
&lt;td&gt;Compare the input and actual traces; raise the limits if the machine can take it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion is stepped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt; is lower than the real ratio&lt;/td&gt;
&lt;td&gt;Set it to the true ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion lags the command&lt;/td&gt;
&lt;td&gt;The interpolator adds one source period&lt;/td&gt;
&lt;td&gt;Run the source at this block&amp;rsquo;s rate and set the factor to 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine stopped by itself&lt;/td&gt;
&lt;td&gt;A setpoint-jump detector fired and the smooth stop ran&lt;/td&gt;
&lt;td&gt;Check the detectors and your setpoint stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stop itself caused a fault&lt;/td&gt;
&lt;td&gt;The smooth stop is too aggressive for the axis limits&lt;/td&gt;
&lt;td&gt;Soften the stop, or widen the limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved more actuators than expected&lt;/td&gt;
&lt;td&gt;Expected: a jog maps onto &lt;strong&gt;every&lt;/strong&gt; actuator that axis drives&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The detectors latch until reset&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on this block&amp;rsquo;s state input. &lt;strong&gt;It clears every actuator and every axis at once&lt;/strong&gt; — there is no per-axis reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals are missing from the tree&lt;/td&gt;
&lt;td&gt;The actuator loops were built without torque support&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more axes or actuators&lt;/td&gt;
&lt;td&gt;Both counts are fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One actuator behaves differently from the rest&lt;/td&gt;
&lt;td&gt;Its own loop is configured differently&lt;/td&gt;
&lt;td&gt;Compare the two loops&#39; sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point, after every actuator loop is commissioned:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interpolators/sampleFactor = &amp;lt;control rate ÷ motion source rate&amp;gt;
axesLimiters/…             = &amp;lt;conservative per-axis limits&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;Axis motion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesLimiters&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Position, velocity, acceleration and jerk are limited &lt;strong&gt;per axis, in axis units&lt;/strong&gt;, before conversion&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix dimensions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A matrix whose dimensions do not match the axis and actuator counts is &lt;strong&gt;rejected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop closure&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; accounts for &lt;strong&gt;signed cancellation&lt;/strong&gt; — two actuators whose contributions cancel do not count as closing the axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint jumps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A jump beyond the configured window triggers the smooth stop&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stopping&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoothStop&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Brings the machine to rest on a stop request or a detected jump&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpolation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upsamples the axis inputs and adds &lt;strong&gt;one source period of delay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-actuator limits and faults&lt;/td&gt;
&lt;td&gt;Each actuator loop&amp;rsquo;s own sub-tree&lt;/td&gt;
&lt;td&gt;Applied independently, after the axis-space limits&lt;/td&gt;
&lt;td&gt;Each loop&amp;rsquo;s outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;This block&amp;rsquo;s state input&lt;/td&gt;
&lt;td&gt;Asserting &lt;code&gt;resetErrors&lt;/code&gt; there clears &lt;strong&gt;every&lt;/strong&gt; actuator loop&amp;rsquo;s detectors and every axis torque detector at once. &lt;strong&gt;There is no per-axis or per-actuator reset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Present only when the actuator loops were built with torque support&lt;/td&gt;
&lt;td&gt;Missing paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis and actuator counts&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Independent of each other, and not changeable 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 itself&lt;/strong&gt;. The transducers inside each actuator loop
log 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: AxesControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/axes-control-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/control-loops/axes-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.32/&#34; selected&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.30/&#34;&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/control-loops/axes-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;AxesControl&lt;/code&gt; is the multi-axis orchestrator. It takes the motion you command
in &lt;strong&gt;axis space&lt;/strong&gt;, conditions and limits it, converts it into &lt;strong&gt;actuator
space&lt;/strong&gt;, runs one control loop per actuator, and converts the measurements
back.&lt;/p&gt;
&lt;p&gt;It is where the distinction between an axis and an actuator lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;axis&lt;/strong&gt; is what you command — a joint of the kinematic model, a Cartesian
direction, a virtual degree of freedom.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;actuator&lt;/strong&gt; is a physical drive with its own encoder, drive mode and
control loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two matrices relate them, so one axis may drive several actuators and one
actuator may be shared by several axes — differential drives, tandem axes, belt
couplings.&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;axesPositionsInput, axesVelocitiesInput, axesAccelerationsInput&amp;quot;]) --&amp;gt; B[&amp;quot;AxesControl&amp;quot;]
    i2([&amp;quot;axesTorquesInput&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;axesHomingJogVelocitiesInput, axesHomingGotoJog&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual&amp;quot;])
    B --&amp;gt; o2([&amp;quot;sensorTorquesActual&amp;quot;])
    B --&amp;gt; o3([&amp;quot;axesIsOpenLoop&amp;quot;])
    B --&amp;gt; s1([&amp;quot;actuatorControlLoops — one per actuator&amp;quot;])
    B --&amp;gt; s2([&amp;quot;axesLimiters, smoothStop&amp;quot;])
    B --&amp;gt; s3([&amp;quot;interpolators, axesSetpointJumpDetectors, axesTorqueDetectors&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The number of axes and the number of actuators are fixed when the
controller is built&lt;/strong&gt;, and they need not be equal. So are the two matrices&#39;
dimensions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost all the configuration is in the sub-trees.&lt;/strong&gt; The per-axis limits are
under &lt;code&gt;axesLimiters&lt;/code&gt;, and every actuator has a complete control loop of its
own under &lt;code&gt;actuatorControlLoops&lt;/code&gt;. Budget commissioning time per actuator, not
per machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; is the signal to watch.&lt;/strong&gt; It tells you, per axis, whether
the loop is genuinely closed. An axis driven by two actuators whose
contributions cancel is &lt;strong&gt;not&lt;/strong&gt; closed, and this output is what says so.&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;axesPositionsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded position per axis. Interpolated, limited, then converted to actuator space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorquesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded torque per axis. &lt;strong&gt;Only present when the actuator loops were built with torque support.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;A jog speed per axis, for homing. &lt;strong&gt;Maps onto every actuator that axis drives.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingGotoJog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Enables that jog, per axis.&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;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;Where each axis is, transformed back from the actuator measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorquesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;Per axis, when the actuator loops have torque sensors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the position loop is not closed.&lt;/strong&gt; True when no actuator driving that axis is closing its loop, &lt;strong&gt;and also when their contributions cancel&lt;/strong&gt; — check it before trusting an axis&amp;rsquo;s position.&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;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;How many control cycles pass between updates from your motion source. 1 disables interpolation. Interpolating adds &lt;strong&gt;one source period of delay&lt;/strong&gt; — acceptable when this block runs faster than the planner feeding it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That is the only parameter at this level. The rest is 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;axesLimiters/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The per-axis position, velocity, acceleration and jerk limits.&lt;/strong&gt; The main safety configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smoothStop/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How the machine is brought to rest on a stop or a detected setpoint jump.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis: how large a setpoint jump must be to count as a fault.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorqueDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis torque supervision, on torque-capable builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorControlLoops/actuatorControlLoop&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A complete actuator control loop per actuator.&lt;/strong&gt; See the &lt;code&gt;ActuatorControlLoop&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commission each actuator loop individually first&lt;/strong&gt;, under
&lt;code&gt;actuatorControlLoops&lt;/code&gt;. Get every transformation, gearbox ratio and window
detector right one actuator at a time. Nothing at this level can be checked
until they are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the two axis-to-actuator matrices from the machine&amp;rsquo;s mechanics. &lt;strong&gt;They
are checked for dimension&lt;/strong&gt; — a mismatched matrix is rejected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With everything disengaged, move each axis by hand and confirm
&lt;code&gt;axesPositionsActual&lt;/code&gt; reads correctly. This is the check that the matrices
are the right way round.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; reads true on every axis while the drives are off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the per-axis limits under &lt;code&gt;axesLimiters&lt;/code&gt; to values the machine cannot be
hurt by. &lt;strong&gt;Start conservative.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; from your motion source&amp;rsquo;s real update rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the setpoint-jump detectors so a plausible planner glitch is caught but
normal motion is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Engage one axis at a time, at low speed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 moves the machine under your motion source&amp;rsquo;s control.&lt;/strong&gt; With
several actuators per axis, a matrix error moves more than one drive.
Have the stop within reach and the axis limits tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; goes false on the axes you engaged.&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;All the loop tuning is per actuator, one level down. This level is limits,
interpolation and stopping behaviour.&lt;/li&gt;
&lt;li&gt;Set the axis limits from what the mechanism can take, expressed in axis
units. They apply before the conversion to actuator space, so one axis limit
protects every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Raise the limits in steps, re-testing each time, rather than setting them
from a datasheet and hoping.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; to the true ratio between this block&amp;rsquo;s rate
and your motion source&amp;rsquo;s. Too low shows as a step at each source update; too
high shows as staleness.&lt;/li&gt;
&lt;li&gt;If the interpolation delay matters — a machine where this block is inside a
feedback path — run the source at this block&amp;rsquo;s rate and set the factor to 1.&lt;/li&gt;
&lt;li&gt;Tune the smooth stop so an emergency stop is firm but does not itself become
a fault. Test it deliberately.&lt;/li&gt;
&lt;li&gt;Set the setpoint-jump detectors last, once you know what a normal setpoint
stream looks like.&lt;/li&gt;
&lt;li&gt;Re-check the axis limits after any change to the matrices — the same axis
limit means a different actuator motion once the coupling changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/axes-control-limiting-22d534c7.svg&#34; alt=&#34;An axis commanded faster than it can move, and the limited target. Thelimiter holds the axis to its configured velocity and acceleration rather thanfollowing the command.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The gap between the two traces is motion your source asked for and did not get.
If that gap is routine, your source and your limits disagree.&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;An axis moves the wrong actuators&lt;/td&gt;
&lt;td&gt;The axis-to-actuator matrix is wrong or transposed&lt;/td&gt;
&lt;td&gt;Move each axis by hand and check &lt;code&gt;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A matrix was rejected&lt;/td&gt;
&lt;td&gt;Its dimensions do not match the axis and actuator counts&lt;/td&gt;
&lt;td&gt;Check both counts; they need not be equal to each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; stays true on an engaged axis&lt;/td&gt;
&lt;td&gt;No actuator driving that axis is closing its loop, &lt;strong&gt;or their contributions cancel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check each actuator loop&amp;rsquo;s own engage state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis reads a position but is not controlled&lt;/td&gt;
&lt;td&gt;Same cause — this is what &lt;code&gt;axesIsOpenLoop&lt;/code&gt; exists to tell you&lt;/td&gt;
&lt;td&gt;Do not trust the axis until it reads false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine will not follow the commanded speed&lt;/td&gt;
&lt;td&gt;The axis limiters are clamping&lt;/td&gt;
&lt;td&gt;Compare the input and actual traces; raise the limits if the machine can take it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion is stepped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt; is lower than the real ratio&lt;/td&gt;
&lt;td&gt;Set it to the true ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion lags the command&lt;/td&gt;
&lt;td&gt;The interpolator adds one source period&lt;/td&gt;
&lt;td&gt;Run the source at this block&amp;rsquo;s rate and set the factor to 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine stopped by itself&lt;/td&gt;
&lt;td&gt;A setpoint-jump detector fired and the smooth stop ran&lt;/td&gt;
&lt;td&gt;Check the detectors and your setpoint stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stop itself caused a fault&lt;/td&gt;
&lt;td&gt;The smooth stop is too aggressive for the axis limits&lt;/td&gt;
&lt;td&gt;Soften the stop, or widen the limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved more actuators than expected&lt;/td&gt;
&lt;td&gt;Expected: a jog maps onto &lt;strong&gt;every&lt;/strong&gt; actuator that axis drives&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The detectors latch until reset&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on this block&amp;rsquo;s state input. &lt;strong&gt;It clears every actuator and every axis at once&lt;/strong&gt; — there is no per-axis reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals are missing from the tree&lt;/td&gt;
&lt;td&gt;The actuator loops were built without torque support&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more axes or actuators&lt;/td&gt;
&lt;td&gt;Both counts are fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One actuator behaves differently from the rest&lt;/td&gt;
&lt;td&gt;Its own loop is configured differently&lt;/td&gt;
&lt;td&gt;Compare the two loops&#39; sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point, after every actuator loop is commissioned:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interpolators/sampleFactor = &amp;lt;control rate ÷ motion source rate&amp;gt;
axesLimiters/…             = &amp;lt;conservative per-axis limits&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;Axis motion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesLimiters&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Position, velocity, acceleration and jerk are limited &lt;strong&gt;per axis, in axis units&lt;/strong&gt;, before conversion&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix dimensions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A matrix whose dimensions do not match the axis and actuator counts is &lt;strong&gt;rejected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop closure&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; accounts for &lt;strong&gt;signed cancellation&lt;/strong&gt; — two actuators whose contributions cancel do not count as closing the axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint jumps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A jump beyond the configured window triggers the smooth stop&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stopping&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoothStop&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Brings the machine to rest on a stop request or a detected jump&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpolation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upsamples the axis inputs and adds &lt;strong&gt;one source period of delay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-actuator limits and faults&lt;/td&gt;
&lt;td&gt;Each actuator loop&amp;rsquo;s own sub-tree&lt;/td&gt;
&lt;td&gt;Applied independently, after the axis-space limits&lt;/td&gt;
&lt;td&gt;Each loop&amp;rsquo;s outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;This block&amp;rsquo;s state input&lt;/td&gt;
&lt;td&gt;Asserting &lt;code&gt;resetErrors&lt;/code&gt; there clears &lt;strong&gt;every&lt;/strong&gt; actuator loop&amp;rsquo;s detectors and every axis torque detector at once. &lt;strong&gt;There is no per-axis or per-actuator reset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Present only when the actuator loops were built with torque support&lt;/td&gt;
&lt;td&gt;Missing paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis and actuator counts&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Independent of each other, and not changeable 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 itself&lt;/strong&gt;. The transducers inside each actuator loop
log 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.32.1 (340db23).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: AxesControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.34/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.34/</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/control-loops/axes-control-3.34/&#34; selected&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.30/&#34;&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/control-loops/axes-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;AxesControl&lt;/code&gt; is the multi-axis orchestrator. It takes the motion you command
in &lt;strong&gt;axis space&lt;/strong&gt;, conditions and limits it, converts it into &lt;strong&gt;actuator
space&lt;/strong&gt;, runs one control loop per actuator, and converts the measurements
back.&lt;/p&gt;
&lt;p&gt;It is where the distinction between an axis and an actuator lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;axis&lt;/strong&gt; is what you command — a joint of the kinematic model, a Cartesian
direction, a virtual degree of freedom.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;actuator&lt;/strong&gt; is a physical drive with its own encoder, drive mode and
control loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two matrices relate them, so one axis may drive several actuators and one
actuator may be shared by several axes — differential drives, tandem axes, belt
couplings.&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;axesPositionsInput, axesVelocitiesInput, axesAccelerationsInput&amp;quot;]) --&amp;gt; B[&amp;quot;AxesControl&amp;quot;]
    i2([&amp;quot;axesTorquesInput&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;axesHomingJogVelocitiesInput, axesHomingGotoJog&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual&amp;quot;])
    B --&amp;gt; o2([&amp;quot;sensorTorquesActual&amp;quot;])
    B --&amp;gt; o3([&amp;quot;axesIsOpenLoop, axesIsReferencing&amp;quot;])
    B --&amp;gt; o4([&amp;quot;axesPositionsTargetLimited, axesPositionsReference, axesVelocitiesReference, axesAccelerationsReference&amp;quot;])
    B --&amp;gt; o5([&amp;quot;axesHomingJogDelta&amp;quot;])
    B --&amp;gt; o6([&amp;quot;axesPositionsTargetHardware, axesPositionsActualHardware&amp;quot;])
    B --&amp;gt; s1([&amp;quot;actuatorControlLoops — one per actuator&amp;quot;])
    B --&amp;gt; s2([&amp;quot;axesLimiters, smoothStop&amp;quot;])
    B --&amp;gt; s3([&amp;quot;axesTransducers — one per axis&amp;quot;])
    B --&amp;gt; s4([&amp;quot;axesJogRateLimiter, axesJogIntegrator&amp;quot;])
    B --&amp;gt; s5([&amp;quot;interpolators, axesSetpointJumpDetectors, axesTorqueDetectors&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The number of axes and the number of actuators are fixed when the
controller is built&lt;/strong&gt;, and they need not be equal. So are the two matrices&#39;
dimensions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost all the configuration is in the sub-trees.&lt;/strong&gt; The per-axis limits are
under &lt;code&gt;axesLimiters&lt;/code&gt;, and every actuator has a complete control loop of its
own under &lt;code&gt;actuatorControlLoops&lt;/code&gt;. Budget commissioning time per actuator, not
per machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; is the signal to watch.&lt;/strong&gt; It tells you, per axis, whether
the loop is genuinely closed. An axis driven by two actuators whose
contributions cancel is &lt;strong&gt;not&lt;/strong&gt; closed, and this output is what says so.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Each axis has its own transducer&lt;/strong&gt;, under
&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt;. It gives the axis a gain and a zero
point of its own, so a coupled or differential axis can be referenced as an
axis rather than one actuator at a time. With the defaults it is an exact
identity, so a plain one-actuator-per-axis machine is unaffected and can skip
it entirely.&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;axesPositionsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded position per axis. Interpolated, limited, then converted to actuator space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorquesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded torque per axis. &lt;strong&gt;Only present when the actuator loops were built with torque support.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;A jog speed per axis, for homing. &lt;strong&gt;Maps onto every actuator that axis drives.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingGotoJog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Enables that jog, per axis.&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;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;Where each axis is, transformed back from the actuator measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorquesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;Per axis, when the actuator loops have torque sensors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the position loop is not closed.&lt;/strong&gt; True when no actuator driving that axis is closing its loop, &lt;strong&gt;and also when their contributions cancel&lt;/strong&gt; — check it before trusting an axis&amp;rsquo;s position. Also true while that axis&amp;rsquo;s transducer is latched after a reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsReferencing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the transducer is in its post-referencing hold.&lt;/strong&gt; While set, that axis reports open loop, and its setpoint-jump and torque detectors are disabled so the re-based setpoint cannot trip them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsTargetLimited&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The axis position target after the limiters, before the jog delta and the transducer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The limiter output actually followed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogDelta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the accumulated jog offset&lt;/strong&gt;, added to the commanded position. Exactly zero when no jog has been commanded. It is &lt;strong&gt;not&lt;/strong&gt; cleared by releasing the jog or by &lt;code&gt;resetErrors&lt;/code&gt; — see the symptom table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsTargetHardware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The axis position target on the &lt;strong&gt;hardware side&lt;/strong&gt; of the transducer — what enters the axis-to-actuator conversion. For commissioning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsActualHardware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The measured axis position on the hardware side — what leaves the actuator-to-axis conversion.&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;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;How many control cycles pass between updates from your motion source. 1 disables interpolation. Interpolating adds &lt;strong&gt;one source period of delay&lt;/strong&gt; — acceptable when this block runs faster than the planner feeding it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That is the only parameter at this level. The rest is 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;axesLimiters/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The per-axis position, velocity, acceleration and jerk limits.&lt;/strong&gt; The main safety configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: its gain, its zero point, and its referencing.&lt;/strong&gt; &lt;code&gt;&amp;lt;NN&amp;gt;&lt;/code&gt; is 1-based and two digits, so axis 0 is &lt;code&gt;axesTransducer01&lt;/code&gt;. See below.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How fast a commanded axis jog velocity may change, per axis. Default 1.0 axis-unit per second per second.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesJogIntegrator/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bounds on the accumulated jog delta. &lt;code&gt;outputLimiterEnable&lt;/code&gt; with &lt;code&gt;outputLimiterMin&lt;/code&gt; / &lt;code&gt;outputLimiterMax&lt;/code&gt;; all disabled by default, so the delta is unbounded until you bound it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smoothStop/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How the machine is brought to rest on a stop or a detected setpoint jump.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis: how large a setpoint jump must be to count as a fault.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorqueDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis torque supervision, on torque-capable builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorControlLoops/actuatorControlLoop&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A complete actuator control loop per actuator.&lt;/strong&gt; See the &lt;code&gt;ActuatorControlLoop&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart.&lt;/p&gt;
&lt;h3 id=&#34;axis-transducers&#34;&gt;Axis transducers&lt;/h3&gt;
&lt;p&gt;One per axis, under &lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/&lt;/code&gt;. The stage sits
between the axis coordinate your application commands — the &lt;strong&gt;software side&lt;/strong&gt; —
and the coordinate the axis-to-actuator conversion uses, the &lt;strong&gt;hardware side&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Position: &lt;code&gt;hardware = gain × software + offset&lt;/code&gt;, and back
&lt;code&gt;software = (hardware − offset) / gain&lt;/code&gt;. Velocity, acceleration, torque and
jog velocity carry the gain only, with no offset. &lt;code&gt;gain = gainNum / gainDen&lt;/code&gt;.
With &lt;code&gt;gainNum&lt;/code&gt; 1, &lt;code&gt;gainDen&lt;/code&gt; 1 and &lt;code&gt;offset&lt;/code&gt; 0 the stage is an exact identity.&lt;/p&gt;
&lt;/blockquote&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;1&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Numerator of the axis gain. A zero is out of range and is &lt;strong&gt;silently reverted to the last accepted value every cycle&lt;/strong&gt;, so a rejected write leaves the old gain in force with no error.&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;1&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Denominator. Same silent reversion.&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;axis unit, hardware side&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The axis zero point. Written by referencing; it survives a restart only if your configuration writes it back.&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;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Selects which reference value a reference request uses.&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;axis unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The reference value used when the switch above is true. An input, so it can be linked.&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;axis unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The reference value used when the switch is false.&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;axis unit, hardware side&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 transducer reconnects. &lt;strong&gt;The default is wrong for an axis&lt;/strong&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To reference an axis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Jog it with &lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt; and &lt;code&gt;axesHomingGotoJog&lt;/code&gt;. The
commands reach every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Optionally snapshot a known point with
&lt;code&gt;referencing/:fromState.setHardwareSnapshot&lt;/code&gt;. Set
&lt;code&gt;hardwareSnapshotTarget&lt;/code&gt; to snapshot a specific hardware-side value instead
of the live one — but note &lt;strong&gt;0.0 there means &amp;ldquo;no target given&amp;rdquo;&lt;/strong&gt; and falls
back to the live input, so an exact hardware value of 0.0 cannot be
snapshotted this way.&lt;/li&gt;
&lt;li&gt;At the home position, assert
&lt;code&gt;referencing/:fromState.setHardwareReference&lt;/code&gt;. The transducer computes
&lt;code&gt;offset = snapshot − reference × gain&lt;/code&gt; and the axis then reads the reference
value.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;axesIsReferencing&lt;/code&gt; for that axis go false. Until it does, the
transducer has &lt;strong&gt;frozen its hardware-side output&lt;/strong&gt; and the axis is held.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referencing/:fromState.reset&lt;/code&gt; clears a latch that will not resolve.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Per-axis referencing status is read from
&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/referencing/:toState&lt;/code&gt;. &lt;strong&gt;This block&amp;rsquo;s own
&lt;code&gt;:toState.isReferenced&lt;/code&gt; is unrelated&lt;/strong&gt; — it still reports the actuator
referencing state.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commission each actuator loop individually first&lt;/strong&gt;, under
&lt;code&gt;actuatorControlLoops&lt;/code&gt;. Get every transformation, gearbox ratio and window
detector right one actuator at a time. Nothing at this level can be checked
until they are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the two axis-to-actuator matrices from the machine&amp;rsquo;s mechanics. &lt;strong&gt;They
are checked for dimension&lt;/strong&gt; — a mismatched matrix is rejected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With everything disengaged, move each axis by hand and confirm
&lt;code&gt;axesPositionsActual&lt;/code&gt; reads correctly. This is the check that the matrices
are the right way round.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; reads true on every axis while the drives are off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the per-axis limits under &lt;code&gt;axesLimiters&lt;/code&gt; to values the machine cannot be
hurt by. &lt;strong&gt;Start conservative.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; from your motion source&amp;rsquo;s real update rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the setpoint-jump detectors so a plausible planner glitch is caught but
normal motion is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Engage one axis at a time, at low speed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 moves the machine under your motion source&amp;rsquo;s control.&lt;/strong&gt; With
several actuators per axis, a matrix error moves more than one drive.
Have the stop within reach and the axis limits tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; goes false on the axes you engaged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If an axis needs a zero point of its own — a coupled or differential axis —
set its &lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt; &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt;
&lt;strong&gt;before referencing it&lt;/strong&gt;, and set
&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; to a few thousandths of an
axis unit. Read &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt; back; an out-of-range write is
reverted silently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reference that axis with the procedure above and confirm &lt;code&gt;axesIsReferencing&lt;/code&gt;
goes false and &lt;code&gt;axesPositionsActual&lt;/code&gt; reads the reference value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you use axis-level jogging, link &lt;strong&gt;either&lt;/strong&gt; the axis-level jog inputs
&lt;strong&gt;or&lt;/strong&gt; the actuator-level &lt;code&gt;actuatorControlLoops/actuatorHomingJogVelocitiesTarget&lt;/code&gt;
— never both. A configuration that links both applies the jog twice.&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;All the loop tuning is per actuator, one level down. This level is limits,
interpolation and stopping behaviour.&lt;/li&gt;
&lt;li&gt;Set the axis limits from what the mechanism can take, expressed in axis
units. They apply before the conversion to actuator space, so one axis limit
protects every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Raise the limits in steps, re-testing each time, rather than setting them
from a datasheet and hoping.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; to the true ratio between this block&amp;rsquo;s rate
and your motion source&amp;rsquo;s. Too low shows as a step at each source update; too
high shows as staleness.&lt;/li&gt;
&lt;li&gt;If the interpolation delay matters — a machine where this block is inside a
feedback path — run the source at this block&amp;rsquo;s rate and set the factor to 1.&lt;/li&gt;
&lt;li&gt;Tune the smooth stop so an emergency stop is firm but does not itself become
a fault. Test it deliberately.&lt;/li&gt;
&lt;li&gt;Set the setpoint-jump detectors last, once you know what a normal setpoint
stream looks like.&lt;/li&gt;
&lt;li&gt;Re-check the axis limits after any change to the matrices — the same axis
limit means a different actuator motion once the coupling changes.&lt;/li&gt;
&lt;li&gt;Set the axis jog rate on &lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt;, per axis. Releasing
a jog does not stop the delta growing at once: the velocity ramps down at
this rate and the delta keeps accumulating until the ramp reaches zero.&lt;/li&gt;
&lt;li&gt;Bound the jog delta with &lt;code&gt;axesJogIntegrator/outputLimiterEnable&lt;/code&gt; and its
min and max if a runaway jog would be a hazard. It is unbounded by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not tune an axis transducer&amp;rsquo;s &lt;code&gt;gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt; or &lt;code&gt;offset&lt;/code&gt; on an
engaged axis.&lt;/strong&gt; They are read every cycle with no hold and no smoothing, so
a write steps the hardware-side target — and an engaged actuator&amp;rsquo;s target —
in a single cycle. The axis setpoint-jump detectors run on the software side
and never see it.&lt;/li&gt;
&lt;li&gt;Re-reference an axis after any gain change. The offset was computed against
the old gain, so changing the gain afterwards moves the hardware target and
the measured position jumps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/axes-control-limiting-22d534c7.svg&#34; alt=&#34;An axis commanded faster than it can move, and the limited target. Thelimiter holds the axis to its configured velocity and acceleration rather thanfollowing the command.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The gap between the two traces is motion your source asked for and did not get.
If that gap is routine, your source and your limits disagree.&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;An axis moves the wrong actuators&lt;/td&gt;
&lt;td&gt;The axis-to-actuator matrix is wrong or transposed&lt;/td&gt;
&lt;td&gt;Move each axis by hand and check &lt;code&gt;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A matrix was rejected&lt;/td&gt;
&lt;td&gt;Its dimensions do not match the axis and actuator counts&lt;/td&gt;
&lt;td&gt;Check both counts; they need not be equal to each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; stays true on an engaged axis&lt;/td&gt;
&lt;td&gt;No actuator driving that axis is closing its loop, &lt;strong&gt;or their contributions cancel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check each actuator loop&amp;rsquo;s own engage state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis reads a position but is not controlled&lt;/td&gt;
&lt;td&gt;Same cause — this is what &lt;code&gt;axesIsOpenLoop&lt;/code&gt; exists to tell you&lt;/td&gt;
&lt;td&gt;Do not trust the axis until it reads false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine will not follow the commanded speed&lt;/td&gt;
&lt;td&gt;The axis limiters are clamping&lt;/td&gt;
&lt;td&gt;Compare the input and actual traces; raise the limits if the machine can take it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion is stepped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt; is lower than the real ratio&lt;/td&gt;
&lt;td&gt;Set it to the true ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion lags the command&lt;/td&gt;
&lt;td&gt;The interpolator adds one source period&lt;/td&gt;
&lt;td&gt;Run the source at this block&amp;rsquo;s rate and set the factor to 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine stopped by itself&lt;/td&gt;
&lt;td&gt;A setpoint-jump detector fired and the smooth stop ran&lt;/td&gt;
&lt;td&gt;Check the detectors and your setpoint stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stop itself caused a fault&lt;/td&gt;
&lt;td&gt;The smooth stop is too aggressive for the axis limits&lt;/td&gt;
&lt;td&gt;Soften the stop, or widen the limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved more actuators than expected&lt;/td&gt;
&lt;td&gt;Expected: a jog maps onto &lt;strong&gt;every&lt;/strong&gt; actuator that axis drives&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The detectors latch until reset&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on this block&amp;rsquo;s state input. &lt;strong&gt;It clears every actuator and every axis at once&lt;/strong&gt; — there is no per-axis reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals are missing from the tree&lt;/td&gt;
&lt;td&gt;The actuator loops were built without torque support&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more axes or actuators&lt;/td&gt;
&lt;td&gt;Both counts are fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One actuator behaves differently from the rest&lt;/td&gt;
&lt;td&gt;Its own loop is configured differently&lt;/td&gt;
&lt;td&gt;Compare the two loops&#39; sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis is stuck after referencing, reporting &lt;code&gt;axesIsReferencing&lt;/code&gt; forever&lt;/td&gt;
&lt;td&gt;Nothing re-based the software side, so the reconnect condition is never met. The hardware output stays frozen — safe, but held&lt;/td&gt;
&lt;td&gt;Have the application follow &lt;code&gt;axesIsReferencing&lt;/code&gt; and set its axis setpoint to 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;A reference request did nothing&lt;/td&gt;
&lt;td&gt;The transducer is still latched from the previous one; requests are ignored while disconnected&lt;/td&gt;
&lt;td&gt;Wait for &lt;code&gt;axesIsReferencing&lt;/code&gt; to clear, or reset the latch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis jumped when the transducer reconnected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; is still at its default of 2, which is enormous in axis units&lt;/td&gt;
&lt;td&gt;Set it to a few thousandths of an axis unit and re-reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis position jumped after a gain change&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;offset&lt;/code&gt; was computed against the old gain&lt;/td&gt;
&lt;td&gt;Re-reference the axis after every gain change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A write to &lt;code&gt;gainNum&lt;/code&gt; or &lt;code&gt;gainDen&lt;/code&gt; had no effect and reported nothing&lt;/td&gt;
&lt;td&gt;Expected: an out-of-range value is silently reverted to the last accepted one every cycle&lt;/td&gt;
&lt;td&gt;Read both back; use a non-zero numerator and a positive denominator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved the machine twice as far as commanded&lt;/td&gt;
&lt;td&gt;Both the axis-level and the actuator-level jog paths are linked&lt;/td&gt;
&lt;td&gt;Link one of the two, never both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis holds a standing offset that nothing clears&lt;/td&gt;
&lt;td&gt;Expected: the jog delta is cleared &lt;strong&gt;only&lt;/strong&gt; while that axis is in its post-referencing hold. Releasing the jog, an open-loop axis and &lt;code&gt;resetErrors&lt;/code&gt; all leave it alone&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;axesHomingJogDelta&lt;/code&gt;; reference the axis to clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog kept accumulating after being released&lt;/td&gt;
&lt;td&gt;Expected: the velocity ramps down at &lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt; and the delta integrates until it reaches zero&lt;/td&gt;
&lt;td&gt;Raise the rate limit if the overshoot matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A referenced axis reads open loop&lt;/td&gt;
&lt;td&gt;Expected while &lt;code&gt;axesIsReferencing&lt;/code&gt; is set: a frozen hardware side has no usable closed position loop&lt;/td&gt;
&lt;td&gt;Wait for the hold to clear&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point, after every actuator loop is commissioned:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interpolators/sampleFactor = &amp;lt;control rate ÷ motion source rate&amp;gt;
axesLimiters/…             = &amp;lt;conservative per-axis limits&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;Axis motion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesLimiters&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Position, velocity, acceleration and jerk are limited &lt;strong&gt;per axis, in axis units&lt;/strong&gt;, before conversion&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix dimensions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A matrix whose dimensions do not match the axis and actuator counts is &lt;strong&gt;rejected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop closure&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; accounts for &lt;strong&gt;signed cancellation&lt;/strong&gt; — two actuators whose contributions cancel do not count as closing the axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint jumps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A jump beyond the configured window triggers the smooth stop&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stopping&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoothStop&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Brings the machine to rest on a stop request or a detected jump&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpolation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upsamples the axis inputs and adds &lt;strong&gt;one source period of delay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-actuator limits and faults&lt;/td&gt;
&lt;td&gt;Each actuator loop&amp;rsquo;s own sub-tree&lt;/td&gt;
&lt;td&gt;Applied independently, after the axis-space limits&lt;/td&gt;
&lt;td&gt;Each loop&amp;rsquo;s outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;This block&amp;rsquo;s state input&lt;/td&gt;
&lt;td&gt;Asserting &lt;code&gt;resetErrors&lt;/code&gt; there clears &lt;strong&gt;every&lt;/strong&gt; actuator loop&amp;rsquo;s detectors and every axis torque detector at once. &lt;strong&gt;There is no per-axis or per-actuator reset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis gain and offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applied every cycle with &lt;strong&gt;no hold and no smoothing&lt;/strong&gt;. A live write steps the hardware-side target in one cycle, invisibly to the axis setpoint-jump detectors&lt;/td&gt;
&lt;td&gt;Not reported; read &lt;code&gt;axesPositionsTargetHardware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; ≠ 0, &lt;code&gt;gainDen&lt;/code&gt; &amp;gt; 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;An out-of-range value is &lt;strong&gt;silently reverted&lt;/strong&gt; to the last accepted one, every cycle. A rejected write leaves the previous gain in force&lt;/td&gt;
&lt;td&gt;Not reported; read both values back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-referencing hold&lt;/td&gt;
&lt;td&gt;The reconnect condition&lt;/td&gt;
&lt;td&gt;The hardware-side output is &lt;strong&gt;frozen&lt;/strong&gt; until the recomputed value lands within &lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; for more than 100 consecutive cycles. It is a re-basing wait, not a timer, so &lt;strong&gt;it does not time out&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsReferencing&lt;/code&gt;, &lt;code&gt;referencing/:toState.isHardwareDisconnected&lt;/code&gt;&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 transducer whose hardware side carries encoder ticks. &lt;strong&gt;An axis transducer&amp;rsquo;s hardware side carries axis units&lt;/strong&gt;, where 2 would let a whole offset step through&lt;/td&gt;
&lt;td&gt;Not reported; set it per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesJogIntegrator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unbounded by default.&lt;/strong&gt; Cleared only while that axis is in its post-referencing hold — not by releasing the jog, not by an open-loop axis, and not by &lt;code&gt;resetErrors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogDelta&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog path&lt;/td&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;The axis-level and actuator-level jog paths are both live. Linking both applies the jog &lt;strong&gt;twice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Present only when the actuator loops were built with torque support&lt;/td&gt;
&lt;td&gt;Missing paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis and actuator counts&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Independent of each other, and not changeable 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 itself&lt;/strong&gt;. The transducers log during referencing —
both the per-axis ones and those inside each actuator loop. 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: AxesControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/axes-control/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/axes-control/</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/control-loops/axes-control-3.34/&#34;&gt;3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-3.32/&#34;&gt;3.32&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/axes-control-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;AxesControl&lt;/code&gt; is the multi-axis orchestrator. It takes the motion you command
in &lt;strong&gt;axis space&lt;/strong&gt;, conditions and limits it, converts it into &lt;strong&gt;actuator
space&lt;/strong&gt;, runs one control loop per actuator, and converts the measurements
back.&lt;/p&gt;
&lt;p&gt;It is where the distinction between an axis and an actuator lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;axis&lt;/strong&gt; is what you command — a joint of the kinematic model, a Cartesian
direction, a virtual degree of freedom.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;actuator&lt;/strong&gt; is a physical drive with its own encoder, drive mode and
control loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two matrices relate them, so one axis may drive several actuators and one
actuator may be shared by several axes — differential drives, tandem axes, belt
couplings.&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;axesPositionsInput, axesVelocitiesInput, axesAccelerationsInput&amp;quot;]) --&amp;gt; B[&amp;quot;AxesControl&amp;quot;]
    i2([&amp;quot;axesTorquesInput&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;axesHomingJogVelocitiesInput, axesHomingGotoJog&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;axesPositionsActual, axesVelocitiesActual, axesAccelerationsActual&amp;quot;])
    B --&amp;gt; o2([&amp;quot;sensorTorquesActual&amp;quot;])
    B --&amp;gt; o3([&amp;quot;axesIsOpenLoop, axesIsReferencing&amp;quot;])
    B --&amp;gt; o4([&amp;quot;axesPositionsTargetLimited, axesPositionsReference, axesVelocitiesReference, axesAccelerationsReference&amp;quot;])
    B --&amp;gt; o5([&amp;quot;axesHomingJogDelta&amp;quot;])
    B --&amp;gt; o6([&amp;quot;axesPositionsTargetHardware, axesPositionsActualHardware&amp;quot;])
    B --&amp;gt; s1([&amp;quot;actuatorControlLoops — one per actuator&amp;quot;])
    B --&amp;gt; s2([&amp;quot;axesLimiters, smoothStop&amp;quot;])
    B --&amp;gt; s3([&amp;quot;axesTransducers — one per axis&amp;quot;])
    B --&amp;gt; s4([&amp;quot;axesJogRateLimiter, axesJogIntegrator&amp;quot;])
    B --&amp;gt; s5([&amp;quot;interpolators, axesSetpointJumpDetectors, axesTorqueDetectors&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The number of axes and the number of actuators are fixed when the
controller is built&lt;/strong&gt;, and they need not be equal. So are the two matrices&#39;
dimensions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Almost all the configuration is in the sub-trees.&lt;/strong&gt; The per-axis limits are
under &lt;code&gt;axesLimiters&lt;/code&gt;, and every actuator has a complete control loop of its
own under &lt;code&gt;actuatorControlLoops&lt;/code&gt;. Budget commissioning time per actuator, not
per machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; is the signal to watch.&lt;/strong&gt; It tells you, per axis, whether
the loop is genuinely closed. An axis driven by two actuators whose
contributions cancel is &lt;strong&gt;not&lt;/strong&gt; closed, and this output is what says so.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Each axis has its own transducer&lt;/strong&gt;, under
&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt;. It gives the axis a gain and a zero
point of its own, so a coupled or differential axis can be referenced as an
axis rather than one actuator at a time. With the defaults it is an exact
identity, so a plain one-actuator-per-axis machine is unaffected and can skip
it entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;inside-the-loop&#34;&gt;Inside the loop&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/axes-control-blocks-bd794f23.svg&#34; alt=&#34;AxesControl block diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The round trip on one sheet. Setpoints flow right on four lanes — position,
velocity, acceleration, torque — through the interpolators, the axis jog sums,
&lt;code&gt;axesLimiters&lt;/code&gt;, &lt;code&gt;smoothStop&lt;/code&gt;, the axis transducer (position) and the transducer
gain (the other three, the &lt;code&gt;×&lt;/code&gt; squares), the axes-to-actuators product &lt;code&gt;dot()&lt;/code&gt;
and into the actuator control loops. The measured state comes back left on four
lanes through the actuators-to-axes product, the same transducer (position) and
the reciprocal gain (&lt;code&gt;÷&lt;/code&gt;), leaving as &lt;code&gt;axesPositionsActual&lt;/code&gt;,
&lt;code&gt;axesVelocitiesActual&lt;/code&gt;, &lt;code&gt;axesAccelerationsActual&lt;/code&gt; and &lt;code&gt;sensorTorquesActual&lt;/code&gt;. A
per-axis or per-actuator array is drawn once, under its array path, because the
count is a configuration choice. Square blocks are sub-modules, &lt;code&gt;dot()&lt;/code&gt; a
function call, grey rounded boxes logic in this block&amp;rsquo;s own code, the yellow
square a switch resting on its default contact, and a pointed tag a signal that
would otherwise cross the sheet.&lt;/em&gt;&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;axesPositionsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded position per axis. Interpolated, limited, then converted to actuator space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded velocity per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded acceleration per axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorquesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The commanded torque per axis. &lt;strong&gt;Only present when the actuator loops were built with torque support.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;A jog speed per axis, for homing. &lt;strong&gt;Maps onto every actuator that axis drives.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingGotoJog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true or false&lt;/td&gt;
&lt;td&gt;Enables that jog, per axis.&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;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;Where each axis is, transformed back from the actuator measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sensorTorquesActual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;torque unit&lt;/td&gt;
&lt;td&gt;Per axis, when the actuator loops have torque sensors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the position loop is not closed.&lt;/strong&gt; True when no actuator driving that axis is closing its loop, &lt;strong&gt;and also when their contributions cancel&lt;/strong&gt; — check it before trusting an axis&amp;rsquo;s position. Also true while that axis&amp;rsquo;s transducer is latched after a reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsReferencing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the transducer is in its post-referencing hold.&lt;/strong&gt; While set, that axis reports open loop, and its setpoint-jump and torque detectors are disabled so the re-based setpoint cannot trip them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsTargetLimited&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The axis position target after the limiters, before the jog delta and the transducer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The limiter output actually followed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesVelocitiesReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesAccelerationsReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit per second²&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogDelta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: the accumulated jog offset&lt;/strong&gt;, added to the commanded position. Exactly zero when no jog has been commanded. It is &lt;strong&gt;not&lt;/strong&gt; cleared by releasing the jog or by &lt;code&gt;resetErrors&lt;/code&gt; — see the symptom table.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsTargetHardware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The axis position target on the &lt;strong&gt;hardware side&lt;/strong&gt; of the transducer — what enters the axis-to-actuator conversion. For commissioning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesPositionsActualHardware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;axis unit&lt;/td&gt;
&lt;td&gt;The measured axis position on the hardware side — what leaves the actuator-to-axis conversion.&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;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1 upward&lt;/td&gt;
&lt;td&gt;How many control cycles pass between updates from your motion source. 1 disables interpolation. Interpolating adds &lt;strong&gt;one source period of delay&lt;/strong&gt; — acceptable when this block runs faster than the planner feeding it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That is the only parameter at this level. The rest is 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;axesLimiters/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The per-axis position, velocity, acceleration and jerk limits.&lt;/strong&gt; The main safety configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per axis: its gain, its zero point, and its referencing.&lt;/strong&gt; &lt;code&gt;&amp;lt;NN&amp;gt;&lt;/code&gt; is 1-based and two digits, so axis 0 is &lt;code&gt;axesTransducer01&lt;/code&gt;. See below.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How fast a commanded axis jog velocity may change, per axis. Default 1.0 axis-unit per second per second.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesJogIntegrator/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bounds on the accumulated jog delta. &lt;code&gt;outputLimiterEnable&lt;/code&gt; with &lt;code&gt;outputLimiterMin&lt;/code&gt; / &lt;code&gt;outputLimiterMax&lt;/code&gt;; all disabled by default, so the delta is unbounded until you bound it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;smoothStop/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How the machine is brought to rest on a stop or a detected setpoint jump.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis: how large a setpoint jump must be to count as a fault.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesTorqueDetectors/windowDetector&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per axis torque supervision, on torque-capable builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actuatorControlLoops/actuatorControlLoop&amp;lt;N&amp;gt;/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A complete actuator control loop per actuator.&lt;/strong&gt; See the &lt;code&gt;ActuatorControlLoop&lt;/code&gt; page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart.&lt;/p&gt;
&lt;h3 id=&#34;axis-transducers&#34;&gt;Axis transducers&lt;/h3&gt;
&lt;p&gt;One per axis, under &lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/&lt;/code&gt;. The stage sits
between the axis coordinate your application commands — the &lt;strong&gt;software side&lt;/strong&gt; —
and the coordinate the axis-to-actuator conversion uses, the &lt;strong&gt;hardware side&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Position: &lt;code&gt;hardware = gain × software + offset&lt;/code&gt;, and back
&lt;code&gt;software = (hardware − offset) / gain&lt;/code&gt;. Velocity, acceleration, torque and
jog velocity carry the gain only, with no offset. &lt;code&gt;gain = gainNum / gainDen&lt;/code&gt;.
With &lt;code&gt;gainNum&lt;/code&gt; 1, &lt;code&gt;gainDen&lt;/code&gt; 1 and &lt;code&gt;offset&lt;/code&gt; 0 the stage is an exact identity.&lt;/p&gt;
&lt;/blockquote&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;1&lt;/td&gt;
&lt;td&gt;non-zero&lt;/td&gt;
&lt;td&gt;Numerator of the axis gain. A zero is out of range and is &lt;strong&gt;silently reverted to the last accepted value every cycle&lt;/strong&gt;, so a rejected write leaves the old gain in force with no error.&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;1&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Denominator. Same silent reversion.&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;axis unit, hardware side&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The axis zero point. Written by referencing; it survives a restart only if your configuration writes it back.&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;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Selects which reference value a reference request uses.&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;axis unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The reference value used when the switch above is true. An input, so it can be linked.&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;axis unit&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The reference value used when the switch is false.&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;axis unit, hardware side&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 transducer reconnects. &lt;strong&gt;The default is wrong for an axis&lt;/strong&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To reference an axis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Jog it with &lt;code&gt;axesHomingJogVelocitiesInput&lt;/code&gt; and &lt;code&gt;axesHomingGotoJog&lt;/code&gt;. The
commands reach every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Optionally snapshot a known point with
&lt;code&gt;referencing/:fromState.setHardwareSnapshot&lt;/code&gt;. Set
&lt;code&gt;hardwareSnapshotTarget&lt;/code&gt; to snapshot a specific hardware-side value instead
of the live one — but note &lt;strong&gt;0.0 there means &amp;ldquo;no target given&amp;rdquo;&lt;/strong&gt; and falls
back to the live input, so an exact hardware value of 0.0 cannot be
snapshotted this way.&lt;/li&gt;
&lt;li&gt;At the home position, assert
&lt;code&gt;referencing/:fromState.setHardwareReference&lt;/code&gt;. The transducer computes
&lt;code&gt;offset = snapshot − reference × gain&lt;/code&gt; and the axis then reads the reference
value.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;axesIsReferencing&lt;/code&gt; for that axis go false. Until it does, the
transducer has &lt;strong&gt;frozen its hardware-side output&lt;/strong&gt; and the axis is held.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;referencing/:fromState.reset&lt;/code&gt; clears a latch that will not resolve.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Per-axis referencing status is read from
&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;/referencing/:toState&lt;/code&gt;. &lt;strong&gt;This block&amp;rsquo;s own
&lt;code&gt;:toState.isReferenced&lt;/code&gt; is unrelated&lt;/strong&gt; — it still reports the actuator
referencing state.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commission each actuator loop individually first&lt;/strong&gt;, under
&lt;code&gt;actuatorControlLoops&lt;/code&gt;. Get every transformation, gearbox ratio and window
detector right one actuator at a time. Nothing at this level can be checked
until they are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the two axis-to-actuator matrices from the machine&amp;rsquo;s mechanics. &lt;strong&gt;They
are checked for dimension&lt;/strong&gt; — a mismatched matrix is rejected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With everything disengaged, move each axis by hand and confirm
&lt;code&gt;axesPositionsActual&lt;/code&gt; reads correctly. This is the check that the matrices
are the right way round.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; reads true on every axis while the drives are off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the per-axis limits under &lt;code&gt;axesLimiters&lt;/code&gt; to values the machine cannot be
hurt by. &lt;strong&gt;Start conservative.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; from your motion source&amp;rsquo;s real update rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the setpoint-jump detectors so a plausible planner glitch is caught but
normal motion is not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Engage one axis at a time, at low speed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 8 moves the machine under your motion source&amp;rsquo;s control.&lt;/strong&gt; With
several actuators per axis, a matrix error moves more than one drive.
Have the stop within reach and the axis limits tight.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;axesIsOpenLoop&lt;/code&gt; goes false on the axes you engaged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If an axis needs a zero point of its own — a coupled or differential axis —
set its &lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt; &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt;
&lt;strong&gt;before referencing it&lt;/strong&gt;, and set
&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; to a few thousandths of an
axis unit. Read &lt;code&gt;gainNum&lt;/code&gt; and &lt;code&gt;gainDen&lt;/code&gt; back; an out-of-range write is
reverted silently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reference that axis with the procedure above and confirm &lt;code&gt;axesIsReferencing&lt;/code&gt;
goes false and &lt;code&gt;axesPositionsActual&lt;/code&gt; reads the reference value.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you use axis-level jogging, link &lt;strong&gt;either&lt;/strong&gt; the axis-level jog inputs
&lt;strong&gt;or&lt;/strong&gt; the actuator-level &lt;code&gt;actuatorControlLoops/actuatorHomingJogVelocitiesTarget&lt;/code&gt;
— never both. A configuration that links both applies the jog twice.&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;All the loop tuning is per actuator, one level down. This level is limits,
interpolation and stopping behaviour.&lt;/li&gt;
&lt;li&gt;Set the axis limits from what the mechanism can take, expressed in axis
units. They apply before the conversion to actuator space, so one axis limit
protects every actuator that axis drives.&lt;/li&gt;
&lt;li&gt;Raise the limits in steps, re-testing each time, rather than setting them
from a datasheet and hoping.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;interpolators/sampleFactor&lt;/code&gt; to the true ratio between this block&amp;rsquo;s rate
and your motion source&amp;rsquo;s. Too low shows as a step at each source update; too
high shows as staleness.&lt;/li&gt;
&lt;li&gt;If the interpolation delay matters — a machine where this block is inside a
feedback path — run the source at this block&amp;rsquo;s rate and set the factor to 1.&lt;/li&gt;
&lt;li&gt;Tune the smooth stop so an emergency stop is firm but does not itself become
a fault. Test it deliberately.&lt;/li&gt;
&lt;li&gt;Set the setpoint-jump detectors last, once you know what a normal setpoint
stream looks like.&lt;/li&gt;
&lt;li&gt;Re-check the axis limits after any change to the matrices — the same axis
limit means a different actuator motion once the coupling changes.&lt;/li&gt;
&lt;li&gt;Set the axis jog rate on &lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt;, per axis. Releasing
a jog does not stop the delta growing at once: the velocity ramps down at
this rate and the delta keeps accumulating until the ramp reaches zero.&lt;/li&gt;
&lt;li&gt;Bound the jog delta with &lt;code&gt;axesJogIntegrator/outputLimiterEnable&lt;/code&gt; and its
min and max if a runaway jog would be a hazard. It is unbounded by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not tune an axis transducer&amp;rsquo;s &lt;code&gt;gainNum&lt;/code&gt;, &lt;code&gt;gainDen&lt;/code&gt; or &lt;code&gt;offset&lt;/code&gt; on an
engaged axis.&lt;/strong&gt; They are read every cycle with no hold and no smoothing, so
a write steps the hardware-side target — and an engaged actuator&amp;rsquo;s target —
in a single cycle. The axis setpoint-jump detectors run on the software side
and never see it.&lt;/li&gt;
&lt;li&gt;Re-reference an axis after any gain change. The offset was computed against
the old gain, so changing the gain afterwards moves the hardware target and
the measured position jumps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/axes-control-limiting-22d534c7.svg&#34; alt=&#34;An axis commanded faster than it can move, and the limited target. Thelimiter holds the axis to its configured velocity and acceleration rather thanfollowing the command.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The gap between the two traces is motion your source asked for and did not get.
If that gap is routine, your source and your limits disagree.&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;An axis moves the wrong actuators&lt;/td&gt;
&lt;td&gt;The axis-to-actuator matrix is wrong or transposed&lt;/td&gt;
&lt;td&gt;Move each axis by hand and check &lt;code&gt;axesPositionsActual&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A matrix was rejected&lt;/td&gt;
&lt;td&gt;Its dimensions do not match the axis and actuator counts&lt;/td&gt;
&lt;td&gt;Check both counts; they need not be equal to each other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; stays true on an engaged axis&lt;/td&gt;
&lt;td&gt;No actuator driving that axis is closing its loop, &lt;strong&gt;or their contributions cancel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check each actuator loop&amp;rsquo;s own engage state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis reads a position but is not controlled&lt;/td&gt;
&lt;td&gt;Same cause — this is what &lt;code&gt;axesIsOpenLoop&lt;/code&gt; exists to tell you&lt;/td&gt;
&lt;td&gt;Do not trust the axis until it reads false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine will not follow the commanded speed&lt;/td&gt;
&lt;td&gt;The axis limiters are clamping&lt;/td&gt;
&lt;td&gt;Compare the input and actual traces; raise the limits if the machine can take it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion is stepped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt; is lower than the real ratio&lt;/td&gt;
&lt;td&gt;Set it to the true ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion lags the command&lt;/td&gt;
&lt;td&gt;The interpolator adds one source period&lt;/td&gt;
&lt;td&gt;Run the source at this block&amp;rsquo;s rate and set the factor to 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The machine stopped by itself&lt;/td&gt;
&lt;td&gt;A setpoint-jump detector fired and the smooth stop ran&lt;/td&gt;
&lt;td&gt;Check the detectors and your setpoint stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The stop itself caused a fault&lt;/td&gt;
&lt;td&gt;The smooth stop is too aggressive for the axis limits&lt;/td&gt;
&lt;td&gt;Soften the stop, or widen the limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved more actuators than expected&lt;/td&gt;
&lt;td&gt;Expected: a jog maps onto &lt;strong&gt;every&lt;/strong&gt; actuator that axis drives&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A fault will not clear&lt;/td&gt;
&lt;td&gt;The detectors latch until reset&lt;/td&gt;
&lt;td&gt;Assert &lt;code&gt;resetErrors&lt;/code&gt; on this block&amp;rsquo;s state input. &lt;strong&gt;It clears every actuator and every axis at once&lt;/strong&gt; — there is no per-axis reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals are missing from the tree&lt;/td&gt;
&lt;td&gt;The actuator loops were built without torque support&lt;/td&gt;
&lt;td&gt;Fixed when the controller is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more axes or actuators&lt;/td&gt;
&lt;td&gt;Both counts are fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One actuator behaves differently from the rest&lt;/td&gt;
&lt;td&gt;Its own loop is configured differently&lt;/td&gt;
&lt;td&gt;Compare the two loops&#39; sub-trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis is stuck after referencing, reporting &lt;code&gt;axesIsReferencing&lt;/code&gt; forever&lt;/td&gt;
&lt;td&gt;Nothing re-based the software side, so the reconnect condition is never met. The hardware output stays frozen — safe, but held&lt;/td&gt;
&lt;td&gt;Have the application follow &lt;code&gt;axesIsReferencing&lt;/code&gt; and set its axis setpoint to 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;A reference request did nothing&lt;/td&gt;
&lt;td&gt;The transducer is still latched from the previous one; requests are ignored while disconnected&lt;/td&gt;
&lt;td&gt;Wait for &lt;code&gt;axesIsReferencing&lt;/code&gt; to clear, or reset the latch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis jumped when the transducer reconnected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; is still at its default of 2, which is enormous in axis units&lt;/td&gt;
&lt;td&gt;Set it to a few thousandths of an axis unit and re-reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis position jumped after a gain change&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;offset&lt;/code&gt; was computed against the old gain&lt;/td&gt;
&lt;td&gt;Re-reference the axis after every gain change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A write to &lt;code&gt;gainNum&lt;/code&gt; or &lt;code&gt;gainDen&lt;/code&gt; had no effect and reported nothing&lt;/td&gt;
&lt;td&gt;Expected: an out-of-range value is silently reverted to the last accepted one every cycle&lt;/td&gt;
&lt;td&gt;Read both back; use a non-zero numerator and a positive denominator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog moved the machine twice as far as commanded&lt;/td&gt;
&lt;td&gt;Both the axis-level and the actuator-level jog paths are linked&lt;/td&gt;
&lt;td&gt;Link one of the two, never both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An axis holds a standing offset that nothing clears&lt;/td&gt;
&lt;td&gt;Expected: the jog delta is cleared &lt;strong&gt;only&lt;/strong&gt; while that axis is in its post-referencing hold. Releasing the jog, an open-loop axis and &lt;code&gt;resetErrors&lt;/code&gt; all leave it alone&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;axesHomingJogDelta&lt;/code&gt;; reference the axis to clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A jog kept accumulating after being released&lt;/td&gt;
&lt;td&gt;Expected: the velocity ramps down at &lt;code&gt;axesJogRateLimiter/rateLimit&lt;/code&gt; and the delta integrates until it reaches zero&lt;/td&gt;
&lt;td&gt;Raise the rate limit if the overshoot matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A referenced axis reads open loop&lt;/td&gt;
&lt;td&gt;Expected while &lt;code&gt;axesIsReferencing&lt;/code&gt; is set: a frozen hardware side has no usable closed position loop&lt;/td&gt;
&lt;td&gt;Wait for the hold to clear&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point, after every actuator loop is commissioned:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;interpolators/sampleFactor = &amp;lt;control rate ÷ motion source rate&amp;gt;
axesLimiters/…             = &amp;lt;conservative per-axis limits&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;Axis motion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesLimiters&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Position, velocity, acceleration and jerk are limited &lt;strong&gt;per axis, in axis units&lt;/strong&gt;, before conversion&lt;/td&gt;
&lt;td&gt;The limiters&#39; own outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix dimensions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A matrix whose dimensions do not match the axis and actuator counts is &lt;strong&gt;rejected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop closure&lt;/td&gt;
&lt;td&gt;Computed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt; accounts for &lt;strong&gt;signed cancellation&lt;/strong&gt; — two actuators whose contributions cancel do not count as closing the axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsOpenLoop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint jumps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesSetpointJumpDetectors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A jump beyond the configured window triggers the smooth stop&lt;/td&gt;
&lt;td&gt;Each detector&amp;rsquo;s flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stopping&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoothStop&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Brings the machine to rest on a stop request or a detected jump&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpolation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interpolators/sampleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upsamples the axis inputs and adds &lt;strong&gt;one source period of delay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-actuator limits and faults&lt;/td&gt;
&lt;td&gt;Each actuator loop&amp;rsquo;s own sub-tree&lt;/td&gt;
&lt;td&gt;Applied independently, after the axis-space limits&lt;/td&gt;
&lt;td&gt;Each loop&amp;rsquo;s outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault reset&lt;/td&gt;
&lt;td&gt;This block&amp;rsquo;s state input&lt;/td&gt;
&lt;td&gt;Asserting &lt;code&gt;resetErrors&lt;/code&gt; there clears &lt;strong&gt;every&lt;/strong&gt; actuator loop&amp;rsquo;s detectors and every axis torque detector at once. &lt;strong&gt;There is no per-axis or per-actuator reset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis gain and offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesTransducers/axesTransducer&amp;lt;NN&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applied every cycle with &lt;strong&gt;no hold and no smoothing&lt;/strong&gt;. A live write steps the hardware-side target in one cycle, invisibly to the axis setpoint-jump detectors&lt;/td&gt;
&lt;td&gt;Not reported; read &lt;code&gt;axesPositionsTargetHardware&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gainNum&lt;/code&gt; ≠ 0, &lt;code&gt;gainDen&lt;/code&gt; &amp;gt; 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;An out-of-range value is &lt;strong&gt;silently reverted&lt;/strong&gt; to the last accepted one, every cycle. A rejected write leaves the previous gain in force&lt;/td&gt;
&lt;td&gt;Not reported; read both values back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-referencing hold&lt;/td&gt;
&lt;td&gt;The reconnect condition&lt;/td&gt;
&lt;td&gt;The hardware-side output is &lt;strong&gt;frozen&lt;/strong&gt; until the recomputed value lands within &lt;code&gt;referencing/reconnectToleranceHardwareSide&lt;/code&gt; for more than 100 consecutive cycles. It is a re-basing wait, not a timer, so &lt;strong&gt;it does not time out&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesIsReferencing&lt;/code&gt;, &lt;code&gt;referencing/:toState.isHardwareDisconnected&lt;/code&gt;&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 transducer whose hardware side carries encoder ticks. &lt;strong&gt;An axis transducer&amp;rsquo;s hardware side carries axis units&lt;/strong&gt;, where 2 would let a whole offset step through&lt;/td&gt;
&lt;td&gt;Not reported; set it per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog delta&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesJogIntegrator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unbounded by default.&lt;/strong&gt; Cleared only while that axis is in its post-referencing hold — not by releasing the jog, not by an open-loop axis, and not by &lt;code&gt;resetErrors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;axesHomingJogDelta&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jog path&lt;/td&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;The axis-level and actuator-level jog paths are both live. Linking both applies the jog &lt;strong&gt;twice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Torque signals&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Present only when the actuator loops were built with torque support&lt;/td&gt;
&lt;td&gt;Missing paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Axis and actuator counts&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Independent of each other, and not changeable 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 itself&lt;/strong&gt;. The transducers log during referencing —
both the per-axis ones and those inside each actuator loop. 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: PID</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/pid-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/pid-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/control-loops/pid/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid-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/control-loops/pid/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;PID&lt;/code&gt; closes a control loop: it takes an error and produces a control output
from a proportional, an integral and a derivative term. Use it wherever a loop
is closed outside the actuator control loop — a pressure loop, a temperature
loop, a force loop.&lt;/p&gt;
&lt;p&gt;Three things distinguish it from a textbook PID. Its integral only accumulates
while the error is large enough and the machine is slow enough. It has three
independent protections against integral wind-up. And its derivative is
filtered, so sensor noise is not amplified without bound. It is &lt;strong&gt;single
channel&lt;/strong&gt;: one instance closes one 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;error — reference minus actual&amp;quot;]) --&amp;gt; B[&amp;quot;PID&amp;quot;]
    i2([&amp;quot;actual — feeds the velocity gate only&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;iReset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the control signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;iTerm — integral contribution&amp;quot;])
    B --&amp;gt; o3([&amp;quot;dTerm — derivative contribution&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$u = k_p e + k_i !\int! e,dt + k_d \frac{de}{dt}$. &lt;code&gt;nd&lt;/code&gt; is the derivative
filter frequency in rad/s: the D action rolls off above it, and its gain at
high frequency is capped at $k_d \times$ &lt;code&gt;nd&lt;/code&gt;. &lt;strong&gt;Keep &lt;code&gt;nd&lt;/code&gt; below
$2/$ task period&lt;/strong&gt; [s] — 2000 on a 1 ms task — and above 0. The integral term
reaches &lt;code&gt;output&lt;/code&gt; one cycle after it is accumulated.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable, no disable and no &lt;code&gt;isEnabled&lt;/code&gt;.&lt;/strong&gt; It is always
running. &lt;strong&gt;&lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; default to ±0.1&lt;/strong&gt;, which is small enough to look
like a broken integral gain on most machines — set them before you set &lt;code&gt;ki&lt;/code&gt;.&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;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The control error: reference minus actual. A single value, not an array. This is the only input the control law acts on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The measured value. It &lt;strong&gt;feeds the velocity gate only&lt;/strong&gt; and plays no part in computing the output. Leave it at 0 if you are not using the velocity window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true, the integral term is held at zero and stops accumulating, from the same cycle you raise it. The block becomes a proportional-derivative controller for as long as you hold it. It is a level, not a pulse. &lt;strong&gt;It does not clear &lt;code&gt;dTerm&lt;/code&gt;.&lt;/strong&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;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The control signal, after the output limits are applied. A single value. Starts from zero after every controller start, but the integral state is &lt;strong&gt;not&lt;/strong&gt; cleared by a stop — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The integral term&amp;rsquo;s accumulated value. It is published &lt;strong&gt;one cycle ahead&lt;/strong&gt; of its effect on &lt;code&gt;output&lt;/code&gt;, so the two will not reconcile sample-for-sample on a trace. Watch it to see wind-up as it happens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The filtered derivative term. Not cleared by &lt;code&gt;iReset&lt;/code&gt;, and not cleared by a stop.&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;kp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Proportional gain. Raising it makes the loop stiffer and faster, and eventually makes it oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Integral gain. It removes steady-state error. It does nothing useful until &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; are set wide enough — see below.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Derivative gain. It damps overshoot and amplifies noise. Set &lt;code&gt;nd&lt;/code&gt; before you use it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;above 0, below 2/task period [s]&lt;/td&gt;
&lt;td&gt;Derivative filter frequency. Higher gives a sharper, noisier D action; lower gives a softer, slower one. &lt;strong&gt;Not checked&lt;/strong&gt; — see Limits and errors. At the default of 1.0 the D action is filtered so heavily that &lt;code&gt;kd&lt;/code&gt; barely acts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the error is &lt;strong&gt;above&lt;/strong&gt; this or below the negative threshold. At the default of 0 it accumulates whenever the error is not exactly zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same gate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the rate of change of &lt;code&gt;actual&lt;/code&gt; is &lt;strong&gt;inside&lt;/strong&gt; this window. The default admits everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same window. &lt;strong&gt;Writing 0 to both closes the gate permanently&lt;/strong&gt; and the integral never accumulates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;must be greater than &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hard upper bound on the integral term. &lt;strong&gt;Raise this before tuning &lt;code&gt;ki&lt;/code&gt;&lt;/strong&gt; — the default binds almost immediately on a real machine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;must be less than &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hard lower bound. &lt;strong&gt;Setting &lt;code&gt;iMin&lt;/code&gt; above &lt;code&gt;iMax&lt;/code&gt; breaks the block&lt;/strong&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;must be less than &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower output limit. Infinity means no limit, which is the legacy behaviour. Set this and its pair &lt;strong&gt;before&lt;/strong&gt; any gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;must be greater than &lt;code&gt;controlOutputMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper output limit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backCalculationGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Second anti-windup layer. Above 0 it actively unwinds the integral while the output is saturated. 0 is off. Too large a value makes the integral oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlErrorDeadBand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Errors smaller than this do not accumulate in the integral. &lt;strong&gt;It applies to the integral path only&lt;/strong&gt; — the proportional and derivative terms still see the full error.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All fourteen parameters are persistent and survive a controller restart. The
inputs and outputs do not. &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;Set &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt; to what your actuator can
accept. Do this &lt;strong&gt;first&lt;/strong&gt;, before any gain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1 is what makes the anti-windup work at all.&lt;/strong&gt; With the limits left
at infinity the block never knows it is saturated, so the directional
anti-windup and &lt;code&gt;backCalculationGain&lt;/code&gt; both have nothing to act on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to the largest integral contribution you will accept —
often a fraction of the output range. Leave them at ±0.1 and the integral
will saturate at once.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;kp&lt;/code&gt;, &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; to 0, and &lt;code&gt;nd&lt;/code&gt; to something sensible for your task
rate — 50 to 200 rad/s is a normal starting range on a 1 ms task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;error&lt;/code&gt; from your comparison. Link &lt;code&gt;actual&lt;/code&gt; only if you intend to use
the velocity window; otherwise leave it at 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kp&lt;/code&gt; from 0 until the loop responds usefully. &lt;code&gt;output&lt;/code&gt; should track
&lt;code&gt;error&lt;/code&gt; × &lt;code&gt;kp&lt;/code&gt; exactly while &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; are still 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;ki&lt;/code&gt; from 0 until steady-state error disappears. Watch &lt;code&gt;iTerm&lt;/code&gt; — if it
sits pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;, go back to step 2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kd&lt;/code&gt; from 0 only if you need to damp overshoot. Watch &lt;code&gt;dTerm&lt;/code&gt; on a
trace; if it is noisy, lower &lt;code&gt;nd&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drive the loop into saturation deliberately and confirm it comes back out
promptly when the error reverses. If it hangs, see the symptom table.&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 limits before the gains: &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt;
first, then &lt;code&gt;iMin&lt;/code&gt; and &lt;code&gt;iMax&lt;/code&gt;. Every anti-windup behaviour depends on them.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;kp&lt;/code&gt; alone. Raise it until the response is fast enough, then back off
until any oscillation is gone. Leave &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; at 0 throughout.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;ki&lt;/code&gt; next. Raise it until steady-state error is removed within an
acceptable time. Too much shows as a slow oscillation the proportional gain
cannot explain.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;kd&lt;/code&gt; last, and only for overshoot. Read &lt;code&gt;dTerm&lt;/code&gt; on a trace before you
trust it — if the trace is noise, &lt;code&gt;kd&lt;/code&gt; is amplifying your sensor and you
should lower &lt;code&gt;nd&lt;/code&gt; or leave &lt;code&gt;kd&lt;/code&gt; at 0.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;nd&lt;/code&gt; against your noise, not against your response. Start at 100 rad/s
on a 1 ms task and lower it until &lt;code&gt;dTerm&lt;/code&gt; is smooth. &lt;strong&gt;Never go above
$2/$ task period&lt;/strong&gt; and never to 0.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;controlErrorDeadBand&lt;/code&gt; only to stop the integral hunting around a small
residual error. It does not quiet the proportional or derivative terms.&lt;/li&gt;
&lt;li&gt;Use the error thresholds and the velocity window only when the integral must
be suppressed during motion — they exist to stop wind-up while the machine
is moving fast. Leave them at their defaults otherwise.&lt;/li&gt;
&lt;li&gt;Turn on &lt;code&gt;backCalculationGain&lt;/code&gt; only if the directional anti-windup alone is
not getting you out of saturation fast enough. Start small; a large value
drives the integral hard the other way.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;nd&lt;/code&gt; after any task-rate change. Its upper limit scales with the
task period.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/pid-step-c1f68e77.svg&#34; alt=&#34;Controller output for a unit step error at three gain sets: proportional onlyholds at 1.0, adding integral gain ramps it up to 3.5 over half a second, andadding derivative gain puts a spike of 1.0 on the first cycle that decays inabout 20 ms.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read each gain&amp;rsquo;s contribution off the gap between the curves.&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;Steady-state error never goes away&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt; is 0, or &lt;code&gt;iTerm&lt;/code&gt; is pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;ki&lt;/code&gt;; if &lt;code&gt;iTerm&lt;/code&gt; is pinned, raise &lt;code&gt;iMax&lt;/code&gt; and lower &lt;code&gt;iMin&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raising &lt;code&gt;ki&lt;/code&gt; seems to do nothing&lt;/td&gt;
&lt;td&gt;Expected with the ±0.1 default limits: the integral saturates almost immediately&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to real values for your output unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop overshoots and rings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kp&lt;/code&gt; too high, or &lt;code&gt;ki&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;kp&lt;/code&gt; first; if the oscillation is slow, lower &lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output buzzes or is audibly rough&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt; amplifying sensor noise&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;nd&lt;/code&gt;, or set &lt;code&gt;kd&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; is pure noise on a trace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; too high for the noise on &lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; froze at a value and never moves&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;nd&lt;/code&gt; above 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; or &lt;code&gt;dTerm&lt;/code&gt; grew without bound&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; above 2/task period, or negative&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;nd&lt;/code&gt; within range, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop stays saturated long after the error reversed&lt;/td&gt;
&lt;td&gt;Wind-up: the output limits are still at infinity, so the anti-windup has nothing to work against&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still slow to leave saturation with the limits set&lt;/td&gt;
&lt;td&gt;Directional anti-windup alone is not enough for this loop&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;backCalculationGain&lt;/code&gt; from 0, a little at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral oscillates after saturation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backCalculationGain&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower it, or return it to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large kick on &lt;code&gt;output&lt;/code&gt; on the first cycle after a start&lt;/td&gt;
&lt;td&gt;Expected with a non-zero &lt;code&gt;kd&lt;/code&gt;: the derivative sees the initial error as a step&lt;/td&gt;
&lt;td&gt;Ramp the reference in, or start with the error near zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A full-scale &lt;code&gt;output&lt;/code&gt; immediately after a restart&lt;/td&gt;
&lt;td&gt;Expected: the integral state is not cleared by a stop, so a loop that was saturated resumes saturated&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; do not add up on a trace&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;iTerm&lt;/code&gt; is published one cycle ahead of its effect&lt;/td&gt;
&lt;td&gt;Compare &lt;code&gt;iTerm&lt;/code&gt; against the next sample of &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral never accumulates at all&lt;/td&gt;
&lt;td&gt;The velocity window is closed — writing 0 to both ends admits nothing&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iActualVelocityWindowPos&lt;/code&gt; to +infinity and the negative one to −infinity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral stops accumulating whenever the machine moves&lt;/td&gt;
&lt;td&gt;Expected if the velocity window is set: that is what it is for&lt;/td&gt;
&lt;td&gt;Widen the window, or leave it at its defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral does not accumulate near the setpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;controlErrorDeadBand&lt;/code&gt; or the error thresholds are set&lt;/td&gt;
&lt;td&gt;Reduce the dead band; remember it affects the integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small errors still drive the proportional term after setting a dead band&lt;/td&gt;
&lt;td&gt;Expected: the dead band applies to the integral path only&lt;/td&gt;
&lt;td&gt;Use a dead-zone block upstream if you need to quiet the whole controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; and &lt;code&gt;iTerm&lt;/code&gt; went to a non-numeric value and &lt;code&gt;iReset&lt;/code&gt; did not clear it&lt;/td&gt;
&lt;td&gt;A non-numeric value reached the derivative path, which &lt;code&gt;iReset&lt;/code&gt; does not clear&lt;/td&gt;
&lt;td&gt;Restart the controller, then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to close several loops&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a loop on a 1 ms task, with the limits in the
actuator&amp;rsquo;s own units:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;controlOutputMin     = -10.0
controlOutputMax     =  10.0
iMin                 =  -5.0
iMax                 =   5.0
nd                   = 100.0
kp                   =   1.0
ki                   =   0.0
kd                   =   0.0
backCalculationGain  =   0.0
controlErrorDeadBand =   0.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Work steps 2 to 4 of Tuning from there. 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;iMin&lt;/code&gt; &amp;lt; &lt;code&gt;iMax&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, and breaking it breaks the block.&lt;/strong&gt; The setters force the signs for you, but a direct parameter write does not. Always keep &lt;code&gt;iMin&lt;/code&gt; below &lt;code&gt;iMax&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;controlOutputMin&lt;/code&gt; &amp;lt; &lt;code&gt;controlOutputMax&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; The same hazard, with no sign forcing at all&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; within 0 and 2/task period [s]&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; Above the range the derivative grows without bound; at 0 it freezes; below 0 it diverges at once. 2000 is the ceiling 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;&lt;code&gt;controlOutputMin&lt;/code&gt;, &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped to the range. At the infinite defaults it is unbounded, and the anti-windup is inactive&lt;/td&gt;
&lt;td&gt;Not reported; compare &lt;code&gt;output&lt;/code&gt; against your limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral term&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;, &lt;code&gt;iMax&lt;/code&gt;, plus directional anti-windup and &lt;code&gt;backCalculationGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped, and actively prevented from winding further into an active output limit&lt;/td&gt;
&lt;td&gt;Not reported; watch &lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral state across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not cleared by a stop or a start.&lt;/strong&gt; A loop that was saturated when it stopped resumes saturated. Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derivative state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared by &lt;code&gt;iReset&lt;/code&gt;, a stop, or a start. Only a controller restart clears it&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 loop 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: PID</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/pid/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/pid/</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/control-loops/pid/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid-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;PID&lt;/code&gt; closes a control loop: it takes an error and produces a control output
from a proportional, an integral and a derivative term. Use it wherever a loop
is closed outside the actuator control loop — a pressure loop, a temperature
loop, a force loop.&lt;/p&gt;
&lt;p&gt;Three things distinguish it from a textbook PID. Its integral only accumulates
while the error is large enough and the machine is slow enough. It has three
independent protections against integral wind-up. And its derivative is
filtered, so sensor noise is not amplified without bound. It is &lt;strong&gt;single
channel&lt;/strong&gt;: one instance closes one 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;error — reference minus actual&amp;quot;]) --&amp;gt; B[&amp;quot;PID&amp;quot;]
    i2([&amp;quot;actual — feeds the velocity gate only&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;iReset, integratorFreeze, controllerReset&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the control signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;iTerm — integral contribution&amp;quot;])
    B --&amp;gt; o3([&amp;quot;dTerm — derivative contribution&amp;quot;])
    B --&amp;gt; o4([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$u = k_p e + k_i !\int! e,dt + k_d \frac{de}{dt}$. &lt;code&gt;nd&lt;/code&gt; is the derivative
filter frequency in rad/s: the D action rolls off above it, and its gain at
high frequency is capped at $k_d \times$ &lt;code&gt;nd&lt;/code&gt;. &lt;strong&gt;Keep &lt;code&gt;nd&lt;/code&gt; below
$2/$ task period&lt;/strong&gt; [s] — 2000 on a 1 ms task — and above 0. The integral term
reaches &lt;code&gt;output&lt;/code&gt; one cycle after it is accumulated.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;enable&lt;/code&gt; defaults to true&lt;/strong&gt;, so the block runs unless you switch it off — the
default is deliberate, so that adding the parameter did not silently mute every
existing PID. While it is off, or the &lt;code&gt;disable&lt;/code&gt; input is on, the output is zero
&lt;em&gt;and the whole state is held cleared&lt;/em&gt;, so re-enabling starts from rest rather
than dumping a stale integral into the plant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; default to ±infinity&lt;/strong&gt;, so there is no integrator clamp
unless you configure one. Wind-up is held off by the output saturation, the
directional conditional integration and &lt;code&gt;backCalculationGain&lt;/code&gt;; the clamp is a
hard backstop on top of those. &lt;em&gt;This changed:&lt;/em&gt; they used to default to ±0.1,
which on any real machine looked like a broken &lt;code&gt;ki&lt;/code&gt; rather than a backstop. If
you were relying on that ±0.1, set it explicitly.&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;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The control error: reference minus actual. A single value, not an array. This is the only input the control law acts on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The measured value. It &lt;strong&gt;feeds the velocity gate only&lt;/strong&gt; and plays no part in computing the output. Leave it at 0 if you are not using the velocity window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true, the integral term is held at zero and stops accumulating, from the same cycle you raise it. The block becomes a proportional-derivative controller for as long as you hold it. It is a level, not a pulse. &lt;strong&gt;It does not clear &lt;code&gt;dTerm&lt;/code&gt;.&lt;/strong&gt;&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;While true the output is &lt;strong&gt;zero&lt;/strong&gt; and the integral term, derivative term and error history are held cleared, so re-enabling starts from rest and cannot dump a stale integral into the plant. The error and actual history keep tracking the live inputs while disabled, so re-enabling produces &lt;strong&gt;no derivative spike&lt;/strong&gt; and no spurious velocity-gate trip. Use this for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent. It is a level, not a pulse.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integratorFreeze&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true the integral term &lt;strong&gt;holds its value&lt;/strong&gt; — it keeps contributing to the output but stops accumulating. This is the difference from &lt;code&gt;iReset&lt;/code&gt;, which discharges it to zero. A level, not a pulse.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controllerReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true the whole controller state is cleared — integral term, derivative term and error history — at the start of every cycle. Broader than &lt;code&gt;iReset&lt;/code&gt;, which touches the integral term only. It does &lt;strong&gt;not&lt;/strong&gt; suppress the output: the controller runs on from the zeroed history, so &lt;code&gt;output&lt;/code&gt; is the proportional term plus a derivative formed against a zero previous error. That is the difference from &lt;code&gt;disable&lt;/code&gt;, which holds the output at zero. A level, not a pulse.&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;output unit&lt;/td&gt;
&lt;td&gt;The control signal, after the output limits are applied. A single value. Starts from zero after every controller start, but the integral state is &lt;strong&gt;not&lt;/strong&gt; cleared by a stop — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The integral term&amp;rsquo;s accumulated value. It is published &lt;strong&gt;one cycle ahead&lt;/strong&gt; of its effect on &lt;code&gt;output&lt;/code&gt;, so the two will not reconcile sample-for-sample on a trace. Watch it to see wind-up as it happens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The filtered derivative term. Not cleared by &lt;code&gt;iReset&lt;/code&gt;, and not cleared by a stop. &lt;strong&gt;Is&lt;/strong&gt; cleared by &lt;code&gt;disable&lt;/code&gt;, &lt;code&gt;enable&lt;/code&gt; = false and &lt;code&gt;controllerReset&lt;/code&gt;.&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 the &lt;code&gt;disable&lt;/code&gt; input is false. While it is false the output is zero and the state is held cleared.&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;Runs the controller. While false the output is zero and the state is held cleared, exactly as for the &lt;code&gt;disable&lt;/code&gt; input. &lt;strong&gt;Defaults to true&lt;/strong&gt;, so a PID that never writes this parameter behaves as it did before the parameter existed — note the actuator loop&amp;rsquo;s inline PID controller defaults the other way and relies on its configuration to switch it on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Proportional gain. Raising it makes the loop stiffer and faster, and eventually makes it oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Integral gain. It removes steady-state error. It does nothing useful until &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; are set wide enough — see below.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Derivative gain. It damps overshoot and amplifies noise. Set &lt;code&gt;nd&lt;/code&gt; before you use it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;clamped&lt;/strong&gt; to [0, 2/task period [s]] — stay strictly below the ceiling&lt;/td&gt;
&lt;td&gt;Derivative filter frequency. Higher gives a sharper, noisier D action; lower gives a softer, slower one. Clamped every cycle to the filter&amp;rsquo;s stability bound, so a value that would make the derivative diverge is refused rather than accepted — 2000 is the ceiling on a 1 ms task. The clamp is silent; read the leaf back to see the applied value, and expect a warning in the log at startup if your configured value was above the bound. &lt;strong&gt;At exactly the ceiling the filter pole sits at −1&lt;/strong&gt;, so &lt;code&gt;dTerm&lt;/code&gt; alternates sign without decaying — treat the bound as a limit to stay under, not a setting. At the default of 1.0 the D action is filtered so heavily that &lt;code&gt;kd&lt;/code&gt; barely acts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the error is &lt;strong&gt;above&lt;/strong&gt; this or below the negative threshold. At the default of 0 it accumulates whenever the error is not exactly zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same gate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the rate of change of &lt;code&gt;actual&lt;/code&gt; is &lt;strong&gt;inside&lt;/strong&gt; this window. The default admits everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same window. &lt;strong&gt;Writing 0 to both closes the gate permanently&lt;/strong&gt; and the integral never accumulates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+infinity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Hard upper bound on the integral term. At the default there is no bound. If written below &lt;code&gt;iMin&lt;/code&gt;, the two are swapped.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−infinity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Hard lower bound. At the default there is no bound. Note the C++ setter forces the sign (&lt;code&gt;setIMin(5)&lt;/code&gt; stores −5), so through the API the window always brackets zero; a direct parameter write does not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;must be less than &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower output limit. Infinity means no limit, which is the legacy behaviour. Set this and its pair &lt;strong&gt;before&lt;/strong&gt; any gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;must be greater than &lt;code&gt;controlOutputMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper output limit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backCalculationGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Second anti-windup layer. Above 0 it actively unwinds the integral while the output is saturated. 0 is off. Too large a value makes the integral oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlErrorDeadBand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Errors smaller than this do not accumulate in the integral. &lt;strong&gt;It applies to the integral path only&lt;/strong&gt; — the proportional and derivative terms still see the full error.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All fifteen parameters are persistent and survive a controller restart. The
inputs and outputs do not. &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;Set &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt; to what your actuator can
accept. Do this &lt;strong&gt;first&lt;/strong&gt;, before any gain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1 is what makes the anti-windup work at all.&lt;/strong&gt; With the limits left
at infinity the block never knows it is saturated, so the directional
anti-windup and &lt;code&gt;backCalculationGain&lt;/code&gt; both have nothing to act on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to the largest integral contribution you will accept —
often a fraction of the output range. Leave them at ±0.1 and the integral
will saturate at once.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;kp&lt;/code&gt;, &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; to 0, and &lt;code&gt;nd&lt;/code&gt; to something sensible for your task
rate — 50 to 200 rad/s is a normal starting range on a 1 ms task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;error&lt;/code&gt; from your comparison. Link &lt;code&gt;actual&lt;/code&gt; only if you intend to use
the velocity window; otherwise leave it at 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kp&lt;/code&gt; from 0 until the loop responds usefully. &lt;code&gt;output&lt;/code&gt; should track
&lt;code&gt;error&lt;/code&gt; × &lt;code&gt;kp&lt;/code&gt; exactly while &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; are still 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;ki&lt;/code&gt; from 0 until steady-state error disappears. Watch &lt;code&gt;iTerm&lt;/code&gt; — if it
sits pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;, go back to step 2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kd&lt;/code&gt; from 0 only if you need to damp overshoot. Watch &lt;code&gt;dTerm&lt;/code&gt; on a
trace; if it is noisy, lower &lt;code&gt;nd&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drive the loop into saturation deliberately and confirm it comes back out
promptly when the error reverses. If it hangs, see the symptom table.&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 limits before the gains: &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt;
first, then &lt;code&gt;iMin&lt;/code&gt; and &lt;code&gt;iMax&lt;/code&gt;. Every anti-windup behaviour depends on them.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;kp&lt;/code&gt; alone. Raise it until the response is fast enough, then back off
until any oscillation is gone. Leave &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; at 0 throughout.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;ki&lt;/code&gt; next. Raise it until steady-state error is removed within an
acceptable time. Too much shows as a slow oscillation the proportional gain
cannot explain.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;kd&lt;/code&gt; last, and only for overshoot. Read &lt;code&gt;dTerm&lt;/code&gt; on a trace before you
trust it — if the trace is noise, &lt;code&gt;kd&lt;/code&gt; is amplifying your sensor and you
should lower &lt;code&gt;nd&lt;/code&gt; or leave &lt;code&gt;kd&lt;/code&gt; at 0.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;nd&lt;/code&gt; against your noise, not against your response. Start at 100 rad/s
on a 1 ms task and lower it until &lt;code&gt;dTerm&lt;/code&gt; is smooth. &lt;strong&gt;Never go above
$2/$ task period&lt;/strong&gt; and never to 0.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;controlErrorDeadBand&lt;/code&gt; only to stop the integral hunting around a small
residual error. It does not quiet the proportional or derivative terms.&lt;/li&gt;
&lt;li&gt;Use the error thresholds and the velocity window only when the integral must
be suppressed during motion — they exist to stop wind-up while the machine
is moving fast. Leave them at their defaults otherwise.&lt;/li&gt;
&lt;li&gt;Turn on &lt;code&gt;backCalculationGain&lt;/code&gt; only if the directional anti-windup alone is
not getting you out of saturation fast enough. Start small; a large value
drives the integral hard the other way.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;nd&lt;/code&gt; after any task-rate change. Its upper limit scales with the
task period.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/pid-step-c1f68e77.svg&#34; alt=&#34;Controller output for a unit step error at three gain sets: proportional onlyholds at 1.0, adding integral gain ramps it up to 3.5 over half a second, andadding derivative gain puts a spike of 1.0 on the first cycle that decays inabout 20 ms.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read each gain&amp;rsquo;s contribution off the gap between the curves.&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;Steady-state error never goes away&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt; is 0, or &lt;code&gt;iTerm&lt;/code&gt; is pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;ki&lt;/code&gt;; if &lt;code&gt;iTerm&lt;/code&gt; is pinned, raise &lt;code&gt;iMax&lt;/code&gt; and lower &lt;code&gt;iMin&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raising &lt;code&gt;ki&lt;/code&gt; seems to do nothing&lt;/td&gt;
&lt;td&gt;Expected with the ±0.1 default limits: the integral saturates almost immediately&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to real values for your output unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop overshoots and rings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kp&lt;/code&gt; too high, or &lt;code&gt;ki&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;kp&lt;/code&gt; first; if the oscillation is slow, lower &lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output buzzes or is audibly rough&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt; amplifying sensor noise&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;nd&lt;/code&gt;, or set &lt;code&gt;kd&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; is pure noise on a trace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; too high for the noise on &lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; froze at a value and never moves&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;nd&lt;/code&gt; above 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; or &lt;code&gt;dTerm&lt;/code&gt; grew without bound&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; above 2/task period, or negative&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;nd&lt;/code&gt; within range, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop stays saturated long after the error reversed&lt;/td&gt;
&lt;td&gt;Wind-up: the output limits are still at infinity, so the anti-windup has nothing to work against&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;controlOutputMin&lt;/code&gt; and &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still slow to leave saturation with the limits set&lt;/td&gt;
&lt;td&gt;Directional anti-windup alone is not enough for this loop&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;backCalculationGain&lt;/code&gt; from 0, a little at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral oscillates after saturation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backCalculationGain&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower it, or return it to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large kick on &lt;code&gt;output&lt;/code&gt; on the first cycle after a start&lt;/td&gt;
&lt;td&gt;Expected with a non-zero &lt;code&gt;kd&lt;/code&gt;: the derivative sees the initial error as a step&lt;/td&gt;
&lt;td&gt;Ramp the reference in, or start with the error near zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A full-scale &lt;code&gt;output&lt;/code&gt; immediately after a restart&lt;/td&gt;
&lt;td&gt;Expected: the integral state is not cleared by a stop, so a loop that was saturated resumes saturated&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; do not add up on a trace&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;iTerm&lt;/code&gt; is published one cycle ahead of its effect&lt;/td&gt;
&lt;td&gt;Compare &lt;code&gt;iTerm&lt;/code&gt; against the next sample of &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral never accumulates at all&lt;/td&gt;
&lt;td&gt;The velocity window is closed — writing 0 to both ends admits nothing&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iActualVelocityWindowPos&lt;/code&gt; to +infinity and the negative one to −infinity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral stops accumulating whenever the machine moves&lt;/td&gt;
&lt;td&gt;Expected if the velocity window is set: that is what it is for&lt;/td&gt;
&lt;td&gt;Widen the window, or leave it at its defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral does not accumulate near the setpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;controlErrorDeadBand&lt;/code&gt; or the error thresholds are set&lt;/td&gt;
&lt;td&gt;Reduce the dead band; remember it affects the integral only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small errors still drive the proportional term after setting a dead band&lt;/td&gt;
&lt;td&gt;Expected: the dead band applies to the integral path only&lt;/td&gt;
&lt;td&gt;Use a dead-zone block upstream if you need to quiet the whole controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; and &lt;code&gt;iTerm&lt;/code&gt; went to a non-numeric value and &lt;code&gt;iReset&lt;/code&gt; did not clear it&lt;/td&gt;
&lt;td&gt;A non-numeric value reached the derivative path, which &lt;code&gt;iReset&lt;/code&gt; does not clear&lt;/td&gt;
&lt;td&gt;Restart the controller, then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to close several loops&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a loop on a 1 ms task, with the limits in the
actuator&amp;rsquo;s own units:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;controlOutputMin     = -10.0
controlOutputMax     =  10.0
iMin                 =  -5.0
iMax                 =   5.0
nd                   = 100.0
kp                   =   1.0
ki                   =   0.0
kd                   =   0.0
backCalculationGain  =   0.0
controlErrorDeadBand =   0.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Work steps 2 to 4 of Tuning from there. 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;iMin&lt;/code&gt; &amp;lt; &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The block&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Swapped&lt;/strong&gt; if you invert them, rather than refused — the same thing &lt;code&gt;Limiter&lt;/code&gt; does with an inverted window. The C++ setters also force the signs, so only a direct parameter write can invert it&lt;/td&gt;
&lt;td&gt;Not reported; read both leaves back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputMin&lt;/code&gt; &amp;lt; &lt;code&gt;controlOutputMax&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; The same hazard, with no sign forcing at all&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nd&lt;/code&gt; within 0 and 2/task period [s] — the filter&amp;rsquo;s &lt;strong&gt;stability bound&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The block&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Clamped&lt;/strong&gt;, silently, every cycle, at both ends: negative values become 0, values above the bound become the bound. The derivative can no longer be configured into divergence. At exactly the bound the pole is −1, so &lt;code&gt;dTerm&lt;/code&gt; alternates without decaying — stay strictly below it. 2000 is the ceiling on a 1 ms task&lt;/td&gt;
&lt;td&gt;Silent in the RT path; read &lt;code&gt;nd&lt;/code&gt; back. &lt;strong&gt;A warning is logged at startup&lt;/strong&gt; when the configured value is above the bound&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;&lt;code&gt;controlOutputMin&lt;/code&gt;, &lt;code&gt;controlOutputMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped to the range. At the infinite defaults it is unbounded, and the anti-windup is inactive&lt;/td&gt;
&lt;td&gt;Not reported; compare &lt;code&gt;output&lt;/code&gt; against your limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral term&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;, &lt;code&gt;iMax&lt;/code&gt;, plus directional anti-windup and &lt;code&gt;backCalculationGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped where a clamp is configured — there is none at the default — and actively prevented from winding further into an active output limit&lt;/td&gt;
&lt;td&gt;Not reported; watch &lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral state across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not cleared by a stop or a start.&lt;/strong&gt; A loop that was saturated when it stopped resumes saturated. Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop, or hold &lt;code&gt;disable&lt;/code&gt;, which does clear it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derivative state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared by &lt;code&gt;iReset&lt;/code&gt;, a stop, or a start. &lt;code&gt;disable&lt;/code&gt;, &lt;code&gt;enable&lt;/code&gt; = false and &lt;code&gt;controllerReset&lt;/code&gt; all clear it, and they seed the error history from the live error so re-enabling does not kick the derivative&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 loop 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 only thing the block logs is the startup warning about &lt;code&gt;nd&lt;/code&gt; exceeding its
stability bound. 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: PID_VariableGainControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control-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/control-loops/pid-variable-gain-control/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control-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/control-loops/pid-variable-gain-control/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;PID_VariableGainControl&lt;/code&gt; closes a control loop with a proportional, integral
and derivative term. Its integral only accumulates while the error is large
enough and the machine is slow enough, and its derivative is filtered so sensor
noise is not amplified without bound. It is &lt;strong&gt;single channel&lt;/strong&gt;: one instance
closes one loop.&lt;/p&gt;
&lt;p&gt;The block is named for a second, variable-gain integrator intended to reject
low-frequency disturbance without the usual phase-lag penalty. &lt;strong&gt;That branch is
not exposed&lt;/strong&gt; — no parameter enables it and it contributes nothing. For new
work use &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;, which is the same controller with output limits,
stronger anti-windup and more conventional parameter naming.&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;error — reference minus actual&amp;quot;]) --&amp;gt; B[&amp;quot;PID_VariableGainControl&amp;quot;]
    i2([&amp;quot;actual — feeds the velocity gate only&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;iReset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the control signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;iTerm — integral contribution&amp;quot;])
    B --&amp;gt; o3([&amp;quot;dTerm — derivative contribution&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$u = k_p e + k_i !\int! e,dt + k_d \frac{de}{dt}$. &lt;code&gt;Nd&lt;/code&gt; is the derivative
filter frequency in rad/s: the D action rolls off above it and its
high-frequency gain is capped at $k_d \times$ &lt;code&gt;Nd&lt;/code&gt;. Keep &lt;code&gt;Nd&lt;/code&gt; below
$1/$ task period [s] for a clean response — 1000 on a 1 ms task — and never
above $2/$ task period, where it grows without bound.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The derivative filter is &lt;code&gt;Nd&lt;/code&gt; here, with a capital N.&lt;/strong&gt; &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;
registers the same quantity as &lt;code&gt;nd&lt;/code&gt;. &lt;strong&gt;Configuration files are not
interchangeable between the two blocks on this point.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable, no disable and no output limit.&lt;/strong&gt; &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt;
default to ±0.1 and are the &lt;strong&gt;only&lt;/strong&gt; protection against integral wind-up — set
them before you set &lt;code&gt;ki&lt;/code&gt;.&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;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The control error: reference minus actual. A single value, not an array. This is the only input the control law acts on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The measured value. It &lt;strong&gt;feeds the velocity gate only&lt;/strong&gt; and plays no part in computing the output. Leave it at 0 if you are not using the velocity window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true, the integral term is held at zero and stops accumulating. The block becomes a proportional-derivative controller for as long as you hold it. It is a level, not a pulse. &lt;strong&gt;It does not clear &lt;code&gt;dTerm&lt;/code&gt;.&lt;/strong&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;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The control signal — the sum of all terms, with &lt;strong&gt;no limit applied&lt;/strong&gt;. Starts from zero after every controller start, but the integral state is not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The integral term&amp;rsquo;s accumulated value, after this cycle&amp;rsquo;s update, so it and &lt;code&gt;output&lt;/code&gt; reconcile sample for sample. Watch it to see wind-up as it happens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The filtered derivative term. Not cleared by &lt;code&gt;iReset&lt;/code&gt;, and not cleared by a stop.&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;kp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Proportional gain. Raising it makes the loop stiffer and faster, and eventually makes it oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Integral gain. It removes steady-state error. It does nothing useful until &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; are set wide enough.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Derivative gain. It damps overshoot and amplifies noise. Set &lt;code&gt;Nd&lt;/code&gt; before you use it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;above 0, below 1/task period [s] in practice&lt;/td&gt;
&lt;td&gt;Derivative filter frequency, &lt;strong&gt;capital N&lt;/strong&gt;. Higher gives a sharper, noisier D action. &lt;strong&gt;Not checked&lt;/strong&gt; — see Limits and errors. At the default of 1.0 the D action is filtered so heavily that &lt;code&gt;kd&lt;/code&gt; barely acts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the error is &lt;strong&gt;above&lt;/strong&gt; this or below the negative threshold. At the default of 0 it accumulates whenever the error is not exactly zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same gate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the rate of change of &lt;code&gt;actual&lt;/code&gt; is &lt;strong&gt;inside&lt;/strong&gt; this window. The default admits everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same window. &lt;strong&gt;Writing 0 to both closes the gate permanently&lt;/strong&gt; and the integral never accumulates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper bound on the integral term, and &lt;strong&gt;the block&amp;rsquo;s only wind-up protection.&lt;/strong&gt; Raise it before tuning &lt;code&gt;ki&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;below &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower bound. Setting it above &lt;code&gt;iMax&lt;/code&gt; pins the integral at &lt;code&gt;iMin&lt;/code&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All ten parameters are persistent and survive a controller restart. The inputs
and outputs do not. &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;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to the largest integral contribution you will accept.
Do this &lt;strong&gt;first&lt;/strong&gt;. There is no output limit in this block, so these two are
all that stands between a wound integrator and the actuator.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1 is the only wind-up protection you get.&lt;/strong&gt; Unlike &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;,
this block has no output clamp and no back-calculation. If a wound
integrator reaching the actuator would be unsafe, use &lt;code&gt;PID&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;kp&lt;/code&gt;, &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; to 0, and &lt;code&gt;Nd&lt;/code&gt; to something sensible for your task
rate — 50 to 200 rad/s is a normal starting range on a 1 ms task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;error&lt;/code&gt; from your comparison. Link &lt;code&gt;actual&lt;/code&gt; only if you intend to use
the velocity window; otherwise leave it at 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kp&lt;/code&gt; from 0 until the loop responds usefully. &lt;code&gt;output&lt;/code&gt; should track
&lt;code&gt;error&lt;/code&gt; × &lt;code&gt;kp&lt;/code&gt; exactly while &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; are still 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;ki&lt;/code&gt; from 0 until steady-state error disappears. Watch &lt;code&gt;iTerm&lt;/code&gt; — if it
sits pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;, go back to step 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kd&lt;/code&gt; from 0 only if you need to damp overshoot. Watch &lt;code&gt;dTerm&lt;/code&gt; on a
trace; if it is noisy, lower &lt;code&gt;Nd&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bound &lt;code&gt;output&lt;/code&gt; in a limiter downstream. This block will not do it for 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;Set &lt;code&gt;iMin&lt;/code&gt; and &lt;code&gt;iMax&lt;/code&gt; before any gain, per Setup step 1.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;kp&lt;/code&gt; alone. Raise it until the response is fast enough, then back off
until any oscillation is gone. Leave &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; at 0 throughout.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;ki&lt;/code&gt; next. Raise it until steady-state error is removed within an
acceptable time. Too much shows as a slow oscillation the proportional gain
cannot explain.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;kd&lt;/code&gt; last, and only for overshoot. Read &lt;code&gt;dTerm&lt;/code&gt; on a trace before you
trust it — if the trace is noise, &lt;code&gt;kd&lt;/code&gt; is amplifying your sensor and you
should lower &lt;code&gt;Nd&lt;/code&gt; or leave &lt;code&gt;kd&lt;/code&gt; at 0.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;Nd&lt;/code&gt; against your noise, not against your response. Start at 100 rad/s
on a 1 ms task and lower it until &lt;code&gt;dTerm&lt;/code&gt; is smooth.&lt;/li&gt;
&lt;li&gt;Use the error thresholds and the velocity window only when the integral must
be suppressed during motion. Leave them at their defaults otherwise.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;Nd&lt;/code&gt; after any task-rate change. Its usable range scales with the
task period.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/pid-variable-gain-control-clamp-84eef2f2.svg&#34; alt=&#34;Controller output for a unit step error with kp 1 and ki 5 at three integralclamps: iMax 5 lets the output ramp to 3.5, iMax 1 flattens at 2.0, and thedefault iMax 0.1 flattens at 1.1 within 20ms.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read where each curve flattens as the point its integral term hit &lt;code&gt;iMax&lt;/code&gt;.&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;Steady-state error never goes away&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt; is 0, or &lt;code&gt;iTerm&lt;/code&gt; is pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;ki&lt;/code&gt;; if &lt;code&gt;iTerm&lt;/code&gt; is pinned, raise &lt;code&gt;iMax&lt;/code&gt; and lower &lt;code&gt;iMin&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raising &lt;code&gt;ki&lt;/code&gt; seems to do nothing&lt;/td&gt;
&lt;td&gt;Expected with the ±0.1 default limits: the integral saturates almost immediately&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to real values for your output unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went far beyond what the actuator can take&lt;/td&gt;
&lt;td&gt;Expected: this block has no output limit&lt;/td&gt;
&lt;td&gt;Add a limiter downstream, or use &lt;code&gt;PID&lt;/code&gt;, which clamps its own output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop stays saturated long after the error reversed&lt;/td&gt;
&lt;td&gt;Wind-up, and this block has no anti-windup beyond the integral clamp&lt;/td&gt;
&lt;td&gt;Tighten &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt;, or move to &lt;code&gt;PID&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop overshoots and rings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kp&lt;/code&gt; too high, or &lt;code&gt;ki&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;kp&lt;/code&gt; first; if the oscillation is slow, lower &lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output buzzes or is audibly rough&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt; amplifying sensor noise&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt;, or set &lt;code&gt;kd&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; is pure noise on a trace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; too high for the noise on &lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; froze at a value and never moves&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;Nd&lt;/code&gt; above 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; oscillates cycle to cycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; above 1/task period&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt; below 1000 on a 1 ms task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; or &lt;code&gt;dTerm&lt;/code&gt; grew without bound&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; above 2/task period, or negative&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;Nd&lt;/code&gt; within range, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A configuration copied from a &lt;code&gt;PID&lt;/code&gt; block did not apply the derivative filter&lt;/td&gt;
&lt;td&gt;Expected: this block spells it &lt;code&gt;Nd&lt;/code&gt;, &lt;code&gt;PID&lt;/code&gt; spells it &lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;Nd&lt;/code&gt; explicitly; the two are not interchangeable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt; sits at &lt;code&gt;iMin&lt;/code&gt; no matter what the error does&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt; was set above &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;iMin&lt;/code&gt; below &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large kick on &lt;code&gt;output&lt;/code&gt; on the first cycle after a start&lt;/td&gt;
&lt;td&gt;Expected with a non-zero &lt;code&gt;kd&lt;/code&gt;: the derivative sees the initial error as a step&lt;/td&gt;
&lt;td&gt;Ramp the reference in, or start with the error near zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A full-scale &lt;code&gt;output&lt;/code&gt; immediately after a restart&lt;/td&gt;
&lt;td&gt;Expected: the integral state is not cleared by a stop&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral never accumulates at all&lt;/td&gt;
&lt;td&gt;The velocity window is closed — writing 0 to both ends admits nothing&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iActualVelocityWindowPos&lt;/code&gt; to +infinity and the negative one to −infinity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral stops accumulating whenever the machine moves&lt;/td&gt;
&lt;td&gt;Expected if the velocity window is set: that is what it is for&lt;/td&gt;
&lt;td&gt;Widen the window, or leave it at its defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot find the variable-gain parameters&lt;/td&gt;
&lt;td&gt;Expected: that branch is not exposed and contributes nothing&lt;/td&gt;
&lt;td&gt;Use the ordinary &lt;code&gt;ki&lt;/code&gt; integral, or &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; and &lt;code&gt;iTerm&lt;/code&gt; went to a non-numeric value and &lt;code&gt;iReset&lt;/code&gt; did not clear it&lt;/td&gt;
&lt;td&gt;A non-numeric value reached the derivative path, which &lt;code&gt;iReset&lt;/code&gt; does not clear&lt;/td&gt;
&lt;td&gt;Restart the controller, then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to close several loops&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a loop on a 1 ms task, with the integral
bounds in the actuator&amp;rsquo;s own units:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;iMin = -5.0
iMax =  5.0
Nd   = 100.0
kp   =   1.0
ki   =   0.0
kd   =   0.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Work steps 2 to 4 of Tuning from there. 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;output&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;Unbounded.&lt;/strong&gt; This block has no output limit, no back-calculation and no directional anti-windup. Bound it in a limiter downstream, or use &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral term&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;, &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped to the range. This is the block&amp;rsquo;s &lt;strong&gt;only&lt;/strong&gt; wind-up protection&lt;/td&gt;
&lt;td&gt;Not reported; watch &lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt; &amp;lt; &lt;code&gt;iMax&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; With &lt;code&gt;iMin&lt;/code&gt; above &lt;code&gt;iMax&lt;/code&gt; the integral is pinned at &lt;code&gt;iMin&lt;/code&gt; and the gain has no effect&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; within 0 and 2/task period [s]&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; Above 1/task period the derivative oscillates, above 2/task period it grows without bound, at 0 it freezes, below 0 it diverges at once&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable-gain integrator&lt;/td&gt;
&lt;td&gt;Not exposed&lt;/td&gt;
&lt;td&gt;The branch named in the block&amp;rsquo;s title has &lt;strong&gt;no parameters and no way to enable it.&lt;/strong&gt; It contributes nothing to &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral state across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not cleared by a stop or a start.&lt;/strong&gt; A loop that was wound when it stopped resumes wound. Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derivative state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared by &lt;code&gt;iReset&lt;/code&gt;, a stop, or a start. Only a controller restart clears it&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 loop 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: PID_VariableGainControl</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control/</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/control-loops/pid-variable-gain-control/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid-variable-gain-control-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;PID_VariableGainControl&lt;/code&gt; closes a control loop with a proportional, integral
and derivative term. Its integral only accumulates while the error is large
enough and the machine is slow enough, and its derivative is filtered so sensor
noise is not amplified without bound. It is &lt;strong&gt;single channel&lt;/strong&gt;: one instance
closes one loop.&lt;/p&gt;
&lt;p&gt;The block is named for a second, variable-gain integrator intended to reject
low-frequency disturbance without the usual phase-lag penalty. &lt;strong&gt;That branch is
not exposed&lt;/strong&gt; — no parameter enables it and it contributes nothing. For new
work use &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;, which is the same controller with output limits,
stronger anti-windup and more conventional parameter naming.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deprecated — scheduled for removal.&lt;/strong&gt; &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt; is the PID to use for
new work. With the variable-gain branch dead, this block is &lt;code&gt;PID&lt;/code&gt; &lt;em&gt;without&lt;/em&gt;
the anti-windup rework — no output saturation, no back-calculation, no
directional conditional integration, no integrator deadband — and with the
derivative filter spelled &lt;code&gt;Nd&lt;/code&gt; rather than &lt;code&gt;nd&lt;/code&gt;. Its only consumer,
&lt;code&gt;FeedbackController&lt;/code&gt;, is deprecated with it. Nothing else in the library
instantiates either. This block is kept only until the last machine is off
it, and will then be deleted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Swapping to &lt;code&gt;PID&lt;/code&gt; is close to a rename.&lt;/strong&gt; &lt;code&gt;kp&lt;/code&gt;, &lt;code&gt;ki&lt;/code&gt;, &lt;code&gt;kd&lt;/code&gt;, &lt;code&gt;iMin&lt;/code&gt;, &lt;code&gt;iMax&lt;/code&gt;,
&lt;code&gt;iPositionErrorThreshold*&lt;/code&gt; and &lt;code&gt;iActualVelocityWindow*&lt;/code&gt; all carry over
unchanged, and the proportional, integral and derivative arithmetic is the
same. Two things to watch: the derivative filter leaf is &lt;code&gt;nd&lt;/code&gt; there, not &lt;code&gt;Nd&lt;/code&gt;;
and &lt;code&gt;PID&lt;/code&gt; integrates the &lt;strong&gt;deadband residual&lt;/strong&gt; of the error rather than the raw
error, so a non-zero &lt;code&gt;controlErrorDeadBand&lt;/code&gt; changes the integral — it defaults
to 0, which reproduces this block exactly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not try to revive the variable-gain branch as it stands.&lt;/strong&gt; &lt;code&gt;iTermVGC&lt;/code&gt; has
no clamp of any kind — &lt;code&gt;iMax&lt;/code&gt;/&lt;code&gt;iMin&lt;/code&gt; bound the ordinary integral term only — so
a one-sided error outside the deadband charges it without bound, and &lt;code&gt;iReset&lt;/code&gt;
does not discharge it. Its only discharge path is a sign change of the error,
and that test is asymmetric about zero. The technique itself is sound and
published (Heertjes and van de Wouw 2007; Heertjes et al. 2019, 2020); the
implementation is not finished.&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;error — reference minus actual&amp;quot;]) --&amp;gt; B[&amp;quot;PID_VariableGainControl&amp;quot;]
    i2([&amp;quot;actual — feeds the velocity gate only&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;iReset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the control signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;iTerm — integral contribution&amp;quot;])
    B --&amp;gt; o3([&amp;quot;dTerm — derivative contribution&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$u = k_p e + k_i !\int! e,dt + k_d \frac{de}{dt}$. &lt;code&gt;Nd&lt;/code&gt; is the derivative
filter frequency in rad/s: the D action rolls off above it and its
high-frequency gain is capped at $k_d \times$ &lt;code&gt;Nd&lt;/code&gt;. Keep &lt;code&gt;Nd&lt;/code&gt; below
$1/$ task period [s] for a clean response — 1000 on a 1 ms task — and never
above $2/$ task period, where it grows without bound.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The derivative filter is &lt;code&gt;Nd&lt;/code&gt; here, with a capital N.&lt;/strong&gt; &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;
registers the same quantity as &lt;code&gt;nd&lt;/code&gt;. &lt;strong&gt;Configuration files are not
interchangeable between the two blocks on this point.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable, no disable and no output limit.&lt;/strong&gt; &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt;
default to ±0.1 and are the &lt;strong&gt;only&lt;/strong&gt; protection against integral wind-up — set
them before you set &lt;code&gt;ki&lt;/code&gt;.&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;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The control error: reference minus actual. A single value, not an array. This is the only input the control law acts on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The measured value. It &lt;strong&gt;feeds the velocity gate only&lt;/strong&gt; and plays no part in computing the output. Leave it at 0 if you are not using the velocity window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iReset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;While true, the integral term is held at zero and stops accumulating. The block becomes a proportional-derivative controller for as long as you hold it. It is a level, not a pulse. &lt;strong&gt;It does not clear &lt;code&gt;dTerm&lt;/code&gt;.&lt;/strong&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;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The control signal — the sum of all terms, with &lt;strong&gt;no limit applied&lt;/strong&gt;. Starts from zero after every controller start, but the integral state is not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The integral term&amp;rsquo;s accumulated value, after this cycle&amp;rsquo;s update, so it and &lt;code&gt;output&lt;/code&gt; reconcile sample for sample. Watch it to see wind-up as it happens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The filtered derivative term. Not cleared by &lt;code&gt;iReset&lt;/code&gt;, and not cleared by a stop.&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;kp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Proportional gain. Raising it makes the loop stiffer and faster, and eventually makes it oscillate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Integral gain. It removes steady-state error. It does nothing useful until &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; are set wide enough.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per signal unit per second&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Derivative gain. It damps overshoot and amplifies noise. Set &lt;code&gt;Nd&lt;/code&gt; before you use it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;above 0, below 1/task period [s] in practice&lt;/td&gt;
&lt;td&gt;Derivative filter frequency, &lt;strong&gt;capital N&lt;/strong&gt;. Higher gives a sharper, noisier D action. &lt;strong&gt;Not checked&lt;/strong&gt; — see Limits and errors. At the default of 1.0 the D action is filtered so heavily that &lt;code&gt;kd&lt;/code&gt; barely acts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the error is &lt;strong&gt;above&lt;/strong&gt; this or below the negative threshold. At the default of 0 it accumulates whenever the error is not exactly zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iPositionErrorThresholdNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same gate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowPos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;+infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The integral accumulates only while the rate of change of &lt;code&gt;actual&lt;/code&gt; is &lt;strong&gt;inside&lt;/strong&gt; this window. The default admits everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iActualVelocityWindowNeg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;−infinity&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The lower half of the same window. &lt;strong&gt;Writing 0 to both closes the gate permanently&lt;/strong&gt; and the integral never accumulates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper bound on the integral term, and &lt;strong&gt;the block&amp;rsquo;s only wind-up protection.&lt;/strong&gt; Raise it before tuning &lt;code&gt;ki&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;below &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower bound. Setting it above &lt;code&gt;iMax&lt;/code&gt; pins the integral at &lt;code&gt;iMin&lt;/code&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All ten parameters are persistent and survive a controller restart. The inputs
and outputs do not. &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;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to the largest integral contribution you will accept.
Do this &lt;strong&gt;first&lt;/strong&gt;. There is no output limit in this block, so these two are
all that stands between a wound integrator and the actuator.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 1 is the only wind-up protection you get.&lt;/strong&gt; Unlike &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;,
this block has no output clamp and no back-calculation. If a wound
integrator reaching the actuator would be unsafe, use &lt;code&gt;PID&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;kp&lt;/code&gt;, &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; to 0, and &lt;code&gt;Nd&lt;/code&gt; to something sensible for your task
rate — 50 to 200 rad/s is a normal starting range on a 1 ms task.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;error&lt;/code&gt; from your comparison. Link &lt;code&gt;actual&lt;/code&gt; only if you intend to use
the velocity window; otherwise leave it at 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kp&lt;/code&gt; from 0 until the loop responds usefully. &lt;code&gt;output&lt;/code&gt; should track
&lt;code&gt;error&lt;/code&gt; × &lt;code&gt;kp&lt;/code&gt; exactly while &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; are still 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;ki&lt;/code&gt; from 0 until steady-state error disappears. Watch &lt;code&gt;iTerm&lt;/code&gt; — if it
sits pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;, go back to step 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;kd&lt;/code&gt; from 0 only if you need to damp overshoot. Watch &lt;code&gt;dTerm&lt;/code&gt; on a
trace; if it is noisy, lower &lt;code&gt;Nd&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bound &lt;code&gt;output&lt;/code&gt; in a limiter downstream. This block will not do it for 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;Set &lt;code&gt;iMin&lt;/code&gt; and &lt;code&gt;iMax&lt;/code&gt; before any gain, per Setup step 1.&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;kp&lt;/code&gt; alone. Raise it until the response is fast enough, then back off
until any oscillation is gone. Leave &lt;code&gt;ki&lt;/code&gt; and &lt;code&gt;kd&lt;/code&gt; at 0 throughout.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;ki&lt;/code&gt; next. Raise it until steady-state error is removed within an
acceptable time. Too much shows as a slow oscillation the proportional gain
cannot explain.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;kd&lt;/code&gt; last, and only for overshoot. Read &lt;code&gt;dTerm&lt;/code&gt; on a trace before you
trust it — if the trace is noise, &lt;code&gt;kd&lt;/code&gt; is amplifying your sensor and you
should lower &lt;code&gt;Nd&lt;/code&gt; or leave &lt;code&gt;kd&lt;/code&gt; at 0.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;Nd&lt;/code&gt; against your noise, not against your response. Start at 100 rad/s
on a 1 ms task and lower it until &lt;code&gt;dTerm&lt;/code&gt; is smooth.&lt;/li&gt;
&lt;li&gt;Use the error thresholds and the velocity window only when the integral must
be suppressed during motion. Leave them at their defaults otherwise.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;Nd&lt;/code&gt; after any task-rate change. Its usable range scales with the
task period.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/pid-variable-gain-control-clamp-84eef2f2.svg&#34; alt=&#34;Controller output for a unit step error with kp 1 and ki 5 at three integralclamps: iMax 5 lets the output ramp to 3.5, iMax 1 flattens at 2.0, and thedefault iMax 0.1 flattens at 1.1 within 20ms.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read where each curve flattens as the point its integral term hit &lt;code&gt;iMax&lt;/code&gt;.&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;Steady-state error never goes away&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ki&lt;/code&gt; is 0, or &lt;code&gt;iTerm&lt;/code&gt; is pinned at &lt;code&gt;iMax&lt;/code&gt; or &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;ki&lt;/code&gt;; if &lt;code&gt;iTerm&lt;/code&gt; is pinned, raise &lt;code&gt;iMax&lt;/code&gt; and lower &lt;code&gt;iMin&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raising &lt;code&gt;ki&lt;/code&gt; seems to do nothing&lt;/td&gt;
&lt;td&gt;Expected with the ±0.1 default limits: the integral saturates almost immediately&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt; to real values for your output unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went far beyond what the actuator can take&lt;/td&gt;
&lt;td&gt;Expected: this block has no output limit&lt;/td&gt;
&lt;td&gt;Add a limiter downstream, or use &lt;code&gt;PID&lt;/code&gt;, which clamps its own output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop stays saturated long after the error reversed&lt;/td&gt;
&lt;td&gt;Wind-up, and this block has no anti-windup beyond the integral clamp&lt;/td&gt;
&lt;td&gt;Tighten &lt;code&gt;iMax&lt;/code&gt; and &lt;code&gt;iMin&lt;/code&gt;, or move to &lt;code&gt;PID&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The loop overshoots and rings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kp&lt;/code&gt; too high, or &lt;code&gt;ki&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;kp&lt;/code&gt; first; if the oscillation is slow, lower &lt;code&gt;ki&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output buzzes or is audibly rough&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kd&lt;/code&gt; amplifying sensor noise&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt;, or set &lt;code&gt;kd&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; is pure noise on a trace&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; too high for the noise on &lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; froze at a value and never moves&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;Nd&lt;/code&gt; above 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dTerm&lt;/code&gt; oscillates cycle to cycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; above 1/task period&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;Nd&lt;/code&gt; below 1000 on a 1 ms task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; or &lt;code&gt;dTerm&lt;/code&gt; grew without bound&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; above 2/task period, or negative&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;Nd&lt;/code&gt; within range, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A configuration copied from a &lt;code&gt;PID&lt;/code&gt; block did not apply the derivative filter&lt;/td&gt;
&lt;td&gt;Expected: this block spells it &lt;code&gt;Nd&lt;/code&gt;, &lt;code&gt;PID&lt;/code&gt; spells it &lt;code&gt;nd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;Nd&lt;/code&gt; explicitly; the two are not interchangeable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iTerm&lt;/code&gt; sits at &lt;code&gt;iMin&lt;/code&gt; no matter what the error does&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt; was set above &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;iMin&lt;/code&gt; below &lt;code&gt;iMax&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large kick on &lt;code&gt;output&lt;/code&gt; on the first cycle after a start&lt;/td&gt;
&lt;td&gt;Expected with a non-zero &lt;code&gt;kd&lt;/code&gt;: the derivative sees the initial error as a step&lt;/td&gt;
&lt;td&gt;Ramp the reference in, or start with the error near zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A full-scale &lt;code&gt;output&lt;/code&gt; immediately after a restart&lt;/td&gt;
&lt;td&gt;Expected: the integral state is not cleared by a stop&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral never accumulates at all&lt;/td&gt;
&lt;td&gt;The velocity window is closed — writing 0 to both ends admits nothing&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;iActualVelocityWindowPos&lt;/code&gt; to +infinity and the negative one to −infinity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral stops accumulating whenever the machine moves&lt;/td&gt;
&lt;td&gt;Expected if the velocity window is set: that is what it is for&lt;/td&gt;
&lt;td&gt;Widen the window, or leave it at its defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot find the variable-gain parameters&lt;/td&gt;
&lt;td&gt;Expected: that branch is not exposed and contributes nothing&lt;/td&gt;
&lt;td&gt;Use the ordinary &lt;code&gt;ki&lt;/code&gt; integral, or &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; and &lt;code&gt;iTerm&lt;/code&gt; went to a non-numeric value and &lt;code&gt;iReset&lt;/code&gt; did not clear it&lt;/td&gt;
&lt;td&gt;A non-numeric value reached the derivative path, which &lt;code&gt;iReset&lt;/code&gt; does not clear&lt;/td&gt;
&lt;td&gt;Restart the controller, then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to close several loops&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a loop on a 1 ms task, with the integral
bounds in the actuator&amp;rsquo;s own units:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;iMin = -5.0
iMax =  5.0
Nd   = 100.0
kp   =   1.0
ki   =   0.0
kd   =   0.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Work steps 2 to 4 of Tuning from there. 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;output&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;Unbounded.&lt;/strong&gt; This block has no output limit, no back-calculation and no directional anti-windup. Bound it in a limiter downstream, or use &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral term&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iMax&lt;/code&gt;, &lt;code&gt;iMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clamped to the range. This is the block&amp;rsquo;s &lt;strong&gt;only&lt;/strong&gt; wind-up protection&lt;/td&gt;
&lt;td&gt;Not reported; watch &lt;code&gt;iTerm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iMin&lt;/code&gt; &amp;lt; &lt;code&gt;iMax&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; With &lt;code&gt;iMin&lt;/code&gt; above &lt;code&gt;iMax&lt;/code&gt; the integral is pinned at &lt;code&gt;iMin&lt;/code&gt; and the gain has no effect&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Nd&lt;/code&gt; within 0 and 2/task period [s]&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; Above 1/task period the derivative oscillates, above 2/task period it grows without bound, at 0 it freezes, below 0 it diverges at once&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable-gain integrator&lt;/td&gt;
&lt;td&gt;Not exposed&lt;/td&gt;
&lt;td&gt;The branch named in the block&amp;rsquo;s title has &lt;strong&gt;no parameters and no way to enable it.&lt;/strong&gt; It contributes nothing to &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral state across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not cleared by a stop or a start.&lt;/strong&gt; A loop that was wound when it stopped resumes wound. Pulse &lt;code&gt;iReset&lt;/code&gt; before releasing the loop&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derivative state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared by &lt;code&gt;iReset&lt;/code&gt;, a stop, or a start. Only a controller restart clears it&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 loop 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: FeedforwardController</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller-3.34/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller-3.34/</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/control-loops/feedforward-controller-3.34/&#34; selected&gt;3.30–3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/&#34;&gt;3.34 (latest)&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/control-loops/feedforward-controller/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;FeedforwardController&lt;/code&gt; commands the force or torque an axis needs &lt;strong&gt;before&lt;/strong&gt;
the feedback loop has to ask for it. It models the machine — friction, inertia,
gravity, position-dependent disturbance — and adds the result open loop, so the
feedback controller is left correcting the model&amp;rsquo;s small error instead of
driving the whole motion. Less work for the loop means less lag and less
tracking error.&lt;/p&gt;
&lt;p&gt;Every plant coefficient can vary with position: mass, position disturbance and
the friction scaling are all lookup tables on &lt;code&gt;positionTarget&lt;/code&gt;. An axis whose
inertia or friction changes over its stroke 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;positionTarget — reference position&amp;quot;]) --&amp;gt; B[&amp;quot;FeedforwardController&amp;quot;]
    i2([&amp;quot;velocityTarget — reference velocity&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;accelerationTarget — reference acceleration&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;externalFeedForwardForce&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;externalMass — mass or inertia estimate&amp;quot;]) --&amp;gt; B
    i6([&amp;quot;gearRatio&amp;quot;]) --&amp;gt; B
    i7([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;positionFeedForwardForce&amp;quot;])
    B --&amp;gt; o2([&amp;quot;velocityFeedForwardForce — friction&amp;quot;])
    B --&amp;gt; o3([&amp;quot;accelerationFeedForwardForce — inertia&amp;quot;])
    B --&amp;gt; o4([&amp;quot;totalFeedForwardForce&amp;quot;])
    B --&amp;gt; o5([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt; = &lt;code&gt;totalFeedForwardGain&lt;/code&gt; × (position + friction +
inertia + &lt;code&gt;externalFeedForwardGain&lt;/code&gt; × external). The inertia term is
(acceleration − &lt;code&gt;gravity&lt;/code&gt;) × mass, and &lt;strong&gt;&lt;code&gt;gravity&lt;/code&gt; is negative by default&lt;/strong&gt;
(−9.8066), so switching &lt;code&gt;subtractGravityOn&lt;/code&gt; on &lt;strong&gt;adds&lt;/strong&gt; a constant holding
force of 9.8066 × mass. &lt;code&gt;totalFeedForwardGain&lt;/code&gt; is capped at 1.0 —
feedforward is a model of your machine and cannot usefully exceed it.
&lt;code&gt;externalFeedForwardGain&lt;/code&gt; reaches 1.5, so a known-low external estimate can
be over-commanded by 50%.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block starts disabled&lt;/strong&gt; (&lt;code&gt;enable&lt;/code&gt; defaults to false) and its total is
zero until you switch it on. The three component outputs report &lt;strong&gt;whether or
not it is enabled&lt;/strong&gt;, so you can commission every term with the output
disconnected.&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;positionTarget&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;Reference position. Drives the position lookup, the mass lookup, the friction gain lookup and the disturbance correction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityTarget&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;Reference velocity. Drives friction compensation, and its sign sets the backlash direction. &lt;strong&gt;Deliberately unfiltered&lt;/strong&gt; — filtering it buys nothing for friction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationTarget&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;Reference acceleration. Drives the inertia term, through the optional smoothing filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardForce&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;An extra feedforward force from outside the block. It is scaled on the way through and &lt;strong&gt;never modified in place&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMass&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;An external mass or inertia estimate. It contributes &lt;strong&gt;only&lt;/strong&gt; when &lt;code&gt;useMassEstimate&lt;/code&gt; is true &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;externalMassGain&lt;/code&gt; is above 0 — both default off. A negative value cannot invert the inertia term.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor revolutions per axis unit&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Scales the position fed to the disturbance correction. It is a mechanical constant, supplied by the axis layer rather than tuned here.&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;totalFeedForwardForce&lt;/code&gt; to zero regardless of &lt;code&gt;enable&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;positionFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The position-dependent component: the position lookup plus the disturbance correction. &lt;strong&gt;Published even while the block is disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The friction component, after the position-dependent friction scaling. &lt;strong&gt;Published even while disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The inertia component, including the gravity offset when enabled. &lt;strong&gt;Published even while disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The sum of all four components, scaled by &lt;code&gt;totalFeedForwardGain&lt;/code&gt;. &lt;strong&gt;Zero while the block is disabled&lt;/strong&gt; — this is the only output that is gated.&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;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Master switch. False forces &lt;code&gt;totalFeedForwardForce&lt;/code&gt; to zero and leaves the component outputs reporting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 – 1&lt;/td&gt;
&lt;td&gt;Scales the sum of all terms. Use it to ramp feedforward in during commissioning. Values above 1 are corrected to 1 — a plant model cannot usefully be over-commanded as a whole.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityDeadzone&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&lt;/td&gt;
&lt;td&gt;Velocities smaller than this are treated as zero before friction compensation. It also suppresses the backlash direction, so a velocity inside the dead zone leaves the direction unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationDeadzone&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&lt;/td&gt;
&lt;td&gt;Accelerations smaller than this are treated as zero before the inertia term. Applied &lt;strong&gt;after&lt;/strong&gt; the smoothing filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMassGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Scales &lt;code&gt;externalMass&lt;/code&gt; before it joins the inertia term. &lt;strong&gt;Zero by default, so &lt;code&gt;externalMass&lt;/code&gt; does nothing until you set this.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useMassEstimate&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;When false, &lt;code&gt;externalMass&lt;/code&gt; is ignored entirely. The second of the two switches that gate the external mass.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subtractGravityOn&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;When true, the inertia term includes a constant gravity offset. Use it for a vertically actuated axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gravity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s²&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−9.8066&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Gravitational acceleration, negative along the positive axis direction. The inertia term &lt;strong&gt;subtracts&lt;/strong&gt; it, so the default adds a positive holding force. Reverse the sign if your positive direction points down.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 – 1.5&lt;/td&gt;
&lt;td&gt;Scales &lt;code&gt;externalFeedForwardForce&lt;/code&gt;. 0 switches the external term off. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backlashCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;−0.1 – 0.1&lt;/td&gt;
&lt;td&gt;Offsets the position fed to the disturbance correction by this much, in the direction of travel. The offset holds its direction at standstill. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All ten parameters are persistent and survive a controller restart. The inputs
and outputs do not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters exist below this block&lt;/strong&gt;, in six sub-trees, and most of the real
configuration lives there: &lt;code&gt;positionLookup&lt;/code&gt;, &lt;code&gt;massLookup&lt;/code&gt;,
&lt;code&gt;frictionGainLookup&lt;/code&gt;, &lt;code&gt;frictionModel&lt;/code&gt;, &lt;code&gt;positionDisturbanceCorrection&lt;/code&gt; and
&lt;code&gt;accelerationFIRFilter&lt;/code&gt;. See &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/&#34;&gt;&lt;code&gt;friction-model.md&lt;/code&gt;&lt;/a&gt; and
&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; for the two that need the most work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The friction gain overrides an external link.&lt;/strong&gt; This block writes
&lt;code&gt;frictionModel/outputScalingFactor&lt;/code&gt; on every cycle, so linking anything into
that input has no effect. Configure the &lt;code&gt;frictionGainLookup&lt;/code&gt; table instead.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;enable&lt;/code&gt; false and &lt;code&gt;totalFeedForwardGain&lt;/code&gt; at 0. Every component output
still reports, so you can commission each term with nothing reaching the
actuator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;positionTarget&lt;/code&gt;, &lt;code&gt;velocityTarget&lt;/code&gt; and &lt;code&gt;accelerationTarget&lt;/code&gt; from your
setpoint generator, and confirm all three follow it on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inertia first.&lt;/strong&gt; Configure &lt;code&gt;massLookup&lt;/code&gt; with your axis&amp;rsquo;s mass or inertia —
a single point is enough if it does not vary over the stroke. Command a
known acceleration and check &lt;code&gt;accelerationFeedForwardForce&lt;/code&gt; equals mass ×
acceleration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the axis is vertical, set &lt;code&gt;gravity&lt;/code&gt; for your sign convention and switch
&lt;code&gt;subtractGravityOn&lt;/code&gt; on. &lt;code&gt;accelerationFeedForwardForce&lt;/code&gt; should now show a
constant offset at standstill equal to 9.8066 × mass.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Friction second.&lt;/strong&gt; Configure &lt;code&gt;frictionModel&lt;/code&gt; for your axis. Move at a
steady velocity and check &lt;code&gt;velocityFeedForwardForce&lt;/code&gt; against the force the
feedback loop was previously supplying.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;frictionGainLookup&lt;/code&gt; alone at first. It ships as a single point
returning 1.0, which leaves friction compensation unscaled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true, then raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 toward 1 in
steps, watching the feedback controller&amp;rsquo;s own output shrink.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 puts real force into the actuator.&lt;/strong&gt; On a loaded vertical axis
with &lt;code&gt;subtractGravityOn&lt;/code&gt; set, going from gain 0 to 1 in one step commands
the full gravity term at once. Ramp it, and keep the axis clear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm the feedback controller&amp;rsquo;s output has dropped. That reduction is what
feedforward bought you; if it has not moved, the model is wrong somewhere in
steps 3 to 5.&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 against the &lt;strong&gt;plant&lt;/strong&gt;, not against the error. Feedforward is a model of
your machine — mass, friction, gravity — and each term is measured, not
dialled in until the error looks small.&lt;/li&gt;
&lt;li&gt;Work one term at a time, in this order: inertia, gravity, friction,
position, external. Set &lt;code&gt;totalFeedForwardGain&lt;/code&gt; to 0 and read the component
outputs while you do it.&lt;/li&gt;
&lt;li&gt;Measure the mass. Command a known acceleration with the loop closed and no
feedforward, and read the force the feedback controller supplies. Divide by
the acceleration. Put that number in &lt;code&gt;massLookup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the force differs at different positions, add more points to
&lt;code&gt;massLookup&lt;/code&gt; rather than averaging.&lt;/li&gt;
&lt;li&gt;Measure friction the same way: move at a steady velocity, read the feedback
controller&amp;rsquo;s output, and configure &lt;code&gt;frictionModel&lt;/code&gt; to reproduce it. Check
both directions — friction is rarely symmetric.&lt;/li&gt;
&lt;li&gt;If friction varies over the stroke, tune &lt;code&gt;frictionModel&lt;/code&gt; for the
&lt;strong&gt;worst-case position&lt;/strong&gt;, then use &lt;code&gt;frictionGainLookup&lt;/code&gt; to fade it down
everywhere else. The gain is clamped to 0–1, so the worst case is the
ceiling by construction.&lt;/li&gt;
&lt;li&gt;Raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; last, and only to 1.0. If you feel you need
more than 1.0, a term is undermodelled — go back to step 3.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;velocityDeadzone&lt;/code&gt; and &lt;code&gt;accelerationDeadzone&lt;/code&gt; to stop the terms
chattering around zero, not to shape the response.&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;accelerationFIRFilter&lt;/code&gt; only if a stepping inertia term is visibly
exciting the structure. &lt;strong&gt;Keep its coefficients summing to 1.0&lt;/strong&gt; — their sum
scales the whole inertia term, and the controller logs a warning at startup
if it exceeds 1.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;backlashCompensation&lt;/code&gt; last, and only if the axis has measurable
backlash. It shifts the disturbance correction in the direction of travel.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/feedforward-controller-terms-f21f39e9.svg&#34; alt=&#34;Feedforward terms over a trapezoidal move with a 2 kg mass: the inertia termis a square wave of plus and minus 10 N, the friction term steps to 10 N whilemoving and returns to zero at rest, and the total peaks near 26 N duringacceleration.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read each term&amp;rsquo;s contribution off the gap between the curves.&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;totalFeedForwardForce&lt;/code&gt; is zero and the components are not&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;enable&lt;/code&gt; is false or &lt;code&gt;disable&lt;/code&gt; is true, and only the total is gated&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isEnabled&lt;/code&gt;; set &lt;code&gt;enable&lt;/code&gt; true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The feedback controller still does all the work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt; is 0, or the model is undermodelled&lt;/td&gt;
&lt;td&gt;Raise the gain toward 1; if it is already 1, remeasure mass and friction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracking got worse after enabling feedforward&lt;/td&gt;
&lt;td&gt;A term is over-commanded, most often mass&lt;/td&gt;
&lt;td&gt;Halve the value in &lt;code&gt;massLookup&lt;/code&gt; and re-measure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis lurches the moment feedforward is enabled&lt;/td&gt;
&lt;td&gt;Expected on a loaded vertical axis: the gravity term appears at full value with no fade&lt;/td&gt;
&lt;td&gt;Ramp &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 instead of switching &lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis drifts down on a vertical axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;subtractGravityOn&lt;/code&gt; is off, or &lt;code&gt;gravity&lt;/code&gt; has the wrong sign for your convention&lt;/td&gt;
&lt;td&gt;Switch it on; if the drift doubles, reverse the sign of &lt;code&gt;gravity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The gravity offset is twice what it should be&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gravity&lt;/code&gt; has the wrong sign — the term subtracts it, so a positive value fights instead of holding&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gravity&lt;/code&gt; negative for a conventional upward-positive axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMass&lt;/code&gt; seems to be ignored&lt;/td&gt;
&lt;td&gt;Expected: it needs both &lt;code&gt;useMassEstimate&lt;/code&gt; true &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;externalMassGain&lt;/code&gt; above 0&lt;/td&gt;
&lt;td&gt;Set both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is smaller than mass × acceleration&lt;/td&gt;
&lt;td&gt;The smoothing filter&amp;rsquo;s coefficients sum to less than 1&lt;/td&gt;
&lt;td&gt;Set them to sum to exactly 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is larger than mass × acceleration&lt;/td&gt;
&lt;td&gt;The smoothing filter&amp;rsquo;s coefficients sum to more than 1&lt;/td&gt;
&lt;td&gt;Set them to sum to 1.0. A warning was logged at startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is noisy or steps hard&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accelerationTarget&lt;/code&gt; is stepping&lt;/td&gt;
&lt;td&gt;Enable &lt;code&gt;accelerationFIRFilter&lt;/code&gt; with coefficients summing to 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction compensation is too strong at one end of the stroke&lt;/td&gt;
&lt;td&gt;Friction varies over the stroke&lt;/td&gt;
&lt;td&gt;Tune &lt;code&gt;frictionModel&lt;/code&gt; for the worst case, then fade it with &lt;code&gt;frictionGainLookup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linking a value into the friction model&amp;rsquo;s output scaling did nothing&lt;/td&gt;
&lt;td&gt;Expected: this block writes that input every cycle&lt;/td&gt;
&lt;td&gt;Configure the &lt;code&gt;frictionGainLookup&lt;/code&gt; table instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A friction gain step arrived as a ramp&lt;/td&gt;
&lt;td&gt;Expected: the friction model rate-limits the scaling factor&lt;/td&gt;
&lt;td&gt;Raise the friction model&amp;rsquo;s own scaling-factor rate if you need it faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The friction term chatters around standstill&lt;/td&gt;
&lt;td&gt;No velocity dead zone, or a discontinuous friction model&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;velocityDeadzone&lt;/code&gt;, or switch the friction model to its continuous form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position term reads zero&lt;/td&gt;
&lt;td&gt;Expected with an unconfigured lookup, or the table is not monotonic&lt;/td&gt;
&lt;td&gt;Configure &lt;code&gt;positionLookup&lt;/code&gt;; a non-monotonic table falls back to zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backlash offset flipped sign when the axis stopped&lt;/td&gt;
&lt;td&gt;Not possible — the direction is held at standstill&lt;/td&gt;
&lt;td&gt;Check the offset is within ±0.1; larger values are corrected silently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backlash offset never applied at all&lt;/td&gt;
&lt;td&gt;The axis has not moved yet, or every velocity was inside the dead zone&lt;/td&gt;
&lt;td&gt;Move the axis once; the direction is zero until then&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt; reads back as 1 after writing more&lt;/td&gt;
&lt;td&gt;Expected: it is capped at 1.0&lt;/td&gt;
&lt;td&gt;If you need more, a term is undermodelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardGain&lt;/code&gt; reads back as 1.5&lt;/td&gt;
&lt;td&gt;Expected: that is its ceiling&lt;/td&gt;
&lt;td&gt;Fix the external estimate instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The external force faded away over several seconds&lt;/td&gt;
&lt;td&gt;Not possible in this version — the gain is applied without modifying the input&lt;/td&gt;
&lt;td&gt;Check whatever produces &lt;code&gt;externalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An output went to a non-numeric value&lt;/td&gt;
&lt;td&gt;A non-numeric value reached an input or a lookup table&lt;/td&gt;
&lt;td&gt;Fix the source and restart the controller; this block has no reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need feedforward on several axes&lt;/td&gt;
&lt;td&gt;Not possible — this block handles one axis&lt;/td&gt;
&lt;td&gt;Use one instance per axis, which is what the axes layer does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a horizontal axis with a 2 kg mass on a 1 ms
task, with friction and position terms not yet configured:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable                  = true
totalFeedForwardGain    = 0.0
subtractGravityOn       = false
gravity                 = -9.8066
useMassEstimate         = false
externalMassGain        = 0.0
externalFeedForwardGain = 1.0
velocityDeadzone        = 0.0
accelerationDeadzone    = 0.0
backlashCompensation    = 0.0
massLookup: single point y = 2.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 as Setup step 7 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;totalFeedForwardGain&lt;/code&gt; within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A larger value is replaced by 1.0. Feedforward models the plant and cannot usefully exceed it&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;externalFeedForwardGain&lt;/code&gt; within 0 – 1.5&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the range is 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;backlashCompensation&lt;/code&gt; within ±0.1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the range is replaced by the nearest edge. The bound is the same whether the axis is linear or rotary&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction gain within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A table value outside the range is replaced by the nearest edge, then rate-limited by the friction model before it applies&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total mass&lt;/td&gt;
&lt;td&gt;Floored at 0&lt;/td&gt;
&lt;td&gt;A negative mass — from the lookup or from &lt;code&gt;externalMass&lt;/code&gt; — is replaced by 0, so the inertia term can never invert and fight the commanded acceleration&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smoothing filter gain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked at runtime.&lt;/strong&gt; The filter&amp;rsquo;s coefficients scale the whole inertia term by their sum. A sum above 1.0 is &lt;strong&gt;logged as a warning at startup only&lt;/strong&gt; — a coefficient edited while running is not re-checked&lt;/td&gt;
&lt;td&gt;Logged at startup; not reported at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the model computes reaches the output. 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;Component outputs&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded, and reported whether the block is enabled or not&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block 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; &lt;code&gt;enable&lt;/code&gt; false zeroes the total but leaves the friction model and filters holding their state&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 logs one warning, at startup only: that the acceleration smoothing
filter&amp;rsquo;s coefficients sum to more than 1.0 and are therefore amplifying the
inertia term. 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: FeedforwardController</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/</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/control-loops/feedforward-controller-3.34/&#34;&gt;3.30–3.34&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/control-loops/feedforward-controller/&#34; selected&gt;3.34 (latest)&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;FeedforwardController&lt;/code&gt; commands the force or torque an axis needs &lt;strong&gt;before&lt;/strong&gt;
the feedback loop has to ask for it. It models the machine — friction, inertia,
gravity, position-dependent disturbance — and adds the result open loop, so the
feedback controller is left correcting the model&amp;rsquo;s small error instead of
driving the whole motion. Less work for the loop means less lag and less
tracking error.&lt;/p&gt;
&lt;p&gt;Every plant coefficient can vary with position: mass, position disturbance and
the friction scaling are all lookup tables on &lt;code&gt;positionTarget&lt;/code&gt;. An axis whose
inertia or friction changes over its stroke 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;positionTarget — reference position&amp;quot;]) --&amp;gt; B[&amp;quot;FeedforwardController&amp;quot;]
    i2([&amp;quot;velocityTarget — reference velocity&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;accelerationTarget — reference acceleration&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;externalFeedForwardForce&amp;quot;]) --&amp;gt; B
    i5([&amp;quot;externalMass — mass or inertia estimate&amp;quot;]) --&amp;gt; B
    i6([&amp;quot;gearRatio&amp;quot;]) --&amp;gt; B
    i7([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;positionFeedForwardForce&amp;quot;])
    B --&amp;gt; o2([&amp;quot;velocityFeedForwardForce — friction&amp;quot;])
    B --&amp;gt; o3([&amp;quot;accelerationFeedForwardForce — inertia&amp;quot;])
    B --&amp;gt; o4([&amp;quot;totalFeedForwardForce&amp;quot;])
    B --&amp;gt; o5([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt; = &lt;code&gt;totalFeedForwardGain&lt;/code&gt; × (position + friction +
inertia + &lt;code&gt;externalFeedForwardGain&lt;/code&gt; × external). The inertia term is
(acceleration − &lt;code&gt;gravity&lt;/code&gt;) × mass, and &lt;strong&gt;&lt;code&gt;gravity&lt;/code&gt; is negative by default&lt;/strong&gt;
(−9.8066), so switching &lt;code&gt;subtractGravityOn&lt;/code&gt; on &lt;strong&gt;adds&lt;/strong&gt; a constant holding
force of 9.8066 × mass. &lt;code&gt;totalFeedForwardGain&lt;/code&gt; is capped at 1.0 —
feedforward is a model of your machine and cannot usefully exceed it.
&lt;code&gt;externalFeedForwardGain&lt;/code&gt; reaches 1.5, so a known-low external estimate can
be over-commanded by 50%.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block starts disabled&lt;/strong&gt; (&lt;code&gt;enable&lt;/code&gt; defaults to false) and its total is
zero until you switch it on. The three component outputs report &lt;strong&gt;whether or
not it is enabled&lt;/strong&gt;, so you can commission every term with the output
disconnected.&lt;/p&gt;
&lt;h2 id=&#34;inside-the-block&#34;&gt;Inside the block&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/feedforward-controller-blocks-5dac4377.svg&#34; alt=&#34;FeedforwardController block diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The four terms, each in its own group, and their sum. Position:
&lt;code&gt;positionLookup&lt;/code&gt; plus the periodic &lt;code&gt;positionDisturbanceCorrection&lt;/code&gt;, which sees
the target with the backlash offset added and scaled by &lt;code&gt;gearRatio&lt;/code&gt;. Velocity:
the &lt;code&gt;frictionModel&lt;/code&gt;, its output scaled by the position-dependent
&lt;code&gt;frictionGainLookup&lt;/code&gt; (clamped to 0 … 1), fed with the dead-zoned velocity.
Acceleration: the filtered, dead-zoned acceleration minus &lt;code&gt;gravity&lt;/code&gt; (when
&lt;code&gt;subtractGravityOn&lt;/code&gt;) times the mass — &lt;code&gt;massLookup&lt;/code&gt; plus the gained
&lt;code&gt;externalMass&lt;/code&gt; when &lt;code&gt;useMassEstimate&lt;/code&gt; — held at or above zero. External: the
force times its gain. The sum is scaled by &lt;code&gt;totalFeedForwardGain&lt;/code&gt; and the last
switch passes it only while &lt;code&gt;isEnabled&lt;/code&gt;; the three component outputs are
reported regardless. Yellow squares are switches resting on their default
contact; white squares are arithmetic; &lt;code&gt;name()&lt;/code&gt; blocks are functions.&lt;/em&gt;&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;positionTarget&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;Reference position. Drives the position lookup, the mass lookup, the friction gain lookup and the disturbance correction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityTarget&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;Reference velocity. Drives friction compensation, and its sign sets the backlash direction. &lt;strong&gt;Deliberately unfiltered&lt;/strong&gt; — filtering it buys nothing for friction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationTarget&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;Reference acceleration. Drives the inertia term, through the optional smoothing filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardForce&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;An extra feedforward force from outside the block. It is scaled on the way through and &lt;strong&gt;never modified in place&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMass&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;An external mass or inertia estimate. It contributes &lt;strong&gt;only&lt;/strong&gt; when &lt;code&gt;useMassEstimate&lt;/code&gt; is true &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;externalMassGain&lt;/code&gt; is above 0 — both default off. A negative value cannot invert the inertia term.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gearRatio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;motor revolutions per axis unit&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Scales the position fed to the disturbance correction. It is a mechanical constant, supplied by the axis layer rather than tuned here.&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;totalFeedForwardForce&lt;/code&gt; to zero regardless of &lt;code&gt;enable&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;positionFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The position-dependent component: the position lookup plus the disturbance correction. &lt;strong&gt;Published even while the block is disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The friction component, after the position-dependent friction scaling. &lt;strong&gt;Published even while disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The inertia component, including the gravity offset when enabled. &lt;strong&gt;Published even while disabled.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N or N·m&lt;/td&gt;
&lt;td&gt;The sum of all four components, scaled by &lt;code&gt;totalFeedForwardGain&lt;/code&gt;. &lt;strong&gt;Zero while the block is disabled&lt;/strong&gt; — this is the only output that is gated.&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;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Master switch. False forces &lt;code&gt;totalFeedForwardForce&lt;/code&gt; to zero and leaves the component outputs reporting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 – 1&lt;/td&gt;
&lt;td&gt;Scales the sum of all terms. Use it to ramp feedforward in during commissioning. Values above 1 are corrected to 1 — a plant model cannot usefully be over-commanded as a whole.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;velocityDeadzone&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&lt;/td&gt;
&lt;td&gt;Velocities smaller than this are treated as zero before friction compensation. It also suppresses the backlash direction, so a velocity inside the dead zone leaves the direction unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accelerationDeadzone&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&lt;/td&gt;
&lt;td&gt;Accelerations smaller than this are treated as zero before the inertia term. Applied &lt;strong&gt;after&lt;/strong&gt; the smoothing filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMassGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Scales &lt;code&gt;externalMass&lt;/code&gt; before it joins the inertia term. &lt;strong&gt;Zero by default, so &lt;code&gt;externalMass&lt;/code&gt; does nothing until you set this.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useMassEstimate&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;When false, &lt;code&gt;externalMass&lt;/code&gt; is ignored entirely. The second of the two switches that gate the external mass.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subtractGravityOn&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;When true, the inertia term includes a constant gravity offset. Use it for a vertically actuated axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gravity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s²&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−9.8066&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Gravitational acceleration, negative along the positive axis direction. The inertia term &lt;strong&gt;subtracts&lt;/strong&gt; it, so the default adds a positive holding force. Reverse the sign if your positive direction points down.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0 – 1.5&lt;/td&gt;
&lt;td&gt;Scales &lt;code&gt;externalFeedForwardForce&lt;/code&gt;. 0 switches the external term off. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backlashCompensation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m or rad&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;−0.1 – 0.1&lt;/td&gt;
&lt;td&gt;Offsets the position fed to the disturbance correction by this much, in the direction of travel. The offset holds its direction at standstill. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All ten parameters are persistent and survive a controller restart. The inputs
and outputs do not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters exist below this block&lt;/strong&gt;, in six sub-trees, and most of the real
configuration lives there: &lt;code&gt;positionLookup&lt;/code&gt;, &lt;code&gt;massLookup&lt;/code&gt;,
&lt;code&gt;frictionGainLookup&lt;/code&gt;, &lt;code&gt;frictionModel&lt;/code&gt;, &lt;code&gt;positionDisturbanceCorrection&lt;/code&gt; and
&lt;code&gt;accelerationFIRFilter&lt;/code&gt;. See &lt;a href=&#34;/docs/developing-control-applications/control-blocks/physical-models-and-compensation/friction-model/&#34;&gt;&lt;code&gt;friction-model.md&lt;/code&gt;&lt;/a&gt; and
&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; for the two that need the most work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The friction gain overrides an external link.&lt;/strong&gt; This block writes
&lt;code&gt;frictionModel/outputScalingFactor&lt;/code&gt; on every cycle, so linking anything into
that input has no effect. Configure the &lt;code&gt;frictionGainLookup&lt;/code&gt; table instead.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;enable&lt;/code&gt; false and &lt;code&gt;totalFeedForwardGain&lt;/code&gt; at 0. Every component output
still reports, so you can commission each term with nothing reaching the
actuator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;positionTarget&lt;/code&gt;, &lt;code&gt;velocityTarget&lt;/code&gt; and &lt;code&gt;accelerationTarget&lt;/code&gt; from your
setpoint generator, and confirm all three follow it on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inertia first.&lt;/strong&gt; Configure &lt;code&gt;massLookup&lt;/code&gt; with your axis&amp;rsquo;s mass or inertia —
a single point is enough if it does not vary over the stroke. Command a
known acceleration and check &lt;code&gt;accelerationFeedForwardForce&lt;/code&gt; equals mass ×
acceleration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the axis is vertical, set &lt;code&gt;gravity&lt;/code&gt; for your sign convention and switch
&lt;code&gt;subtractGravityOn&lt;/code&gt; on. &lt;code&gt;accelerationFeedForwardForce&lt;/code&gt; should now show a
constant offset at standstill equal to 9.8066 × mass.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Friction second.&lt;/strong&gt; Configure &lt;code&gt;frictionModel&lt;/code&gt; for your axis. Move at a
steady velocity and check &lt;code&gt;velocityFeedForwardForce&lt;/code&gt; against the force the
feedback loop was previously supplying.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;frictionGainLookup&lt;/code&gt; alone at first. It ships as a single point
returning 1.0, which leaves friction compensation unscaled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true, then raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 toward 1 in
steps, watching the feedback controller&amp;rsquo;s own output shrink.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 puts real force into the actuator.&lt;/strong&gt; On a loaded vertical axis
with &lt;code&gt;subtractGravityOn&lt;/code&gt; set, going from gain 0 to 1 in one step commands
the full gravity term at once. Ramp it, and keep the axis clear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm the feedback controller&amp;rsquo;s output has dropped. That reduction is what
feedforward bought you; if it has not moved, the model is wrong somewhere in
steps 3 to 5.&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 against the &lt;strong&gt;plant&lt;/strong&gt;, not against the error. Feedforward is a model of
your machine — mass, friction, gravity — and each term is measured, not
dialled in until the error looks small.&lt;/li&gt;
&lt;li&gt;Work one term at a time, in this order: inertia, gravity, friction,
position, external. Set &lt;code&gt;totalFeedForwardGain&lt;/code&gt; to 0 and read the component
outputs while you do it.&lt;/li&gt;
&lt;li&gt;Measure the mass. Command a known acceleration with the loop closed and no
feedforward, and read the force the feedback controller supplies. Divide by
the acceleration. Put that number in &lt;code&gt;massLookup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the force differs at different positions, add more points to
&lt;code&gt;massLookup&lt;/code&gt; rather than averaging.&lt;/li&gt;
&lt;li&gt;Measure friction the same way: move at a steady velocity, read the feedback
controller&amp;rsquo;s output, and configure &lt;code&gt;frictionModel&lt;/code&gt; to reproduce it. Check
both directions — friction is rarely symmetric.&lt;/li&gt;
&lt;li&gt;If friction varies over the stroke, tune &lt;code&gt;frictionModel&lt;/code&gt; for the
&lt;strong&gt;worst-case position&lt;/strong&gt;, then use &lt;code&gt;frictionGainLookup&lt;/code&gt; to fade it down
everywhere else. The gain is clamped to 0–1, so the worst case is the
ceiling by construction.&lt;/li&gt;
&lt;li&gt;Raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; last, and only to 1.0. If you feel you need
more than 1.0, a term is undermodelled — go back to step 3.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;velocityDeadzone&lt;/code&gt; and &lt;code&gt;accelerationDeadzone&lt;/code&gt; to stop the terms
chattering around zero, not to shape the response.&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;accelerationFIRFilter&lt;/code&gt; only if a stepping inertia term is visibly
exciting the structure. &lt;strong&gt;Keep its coefficients summing to 1.0&lt;/strong&gt; — their sum
scales the whole inertia term, and the controller logs a warning at startup
if it exceeds 1.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;backlashCompensation&lt;/code&gt; last, and only if the axis has measurable
backlash. It shifts the disturbance correction in the direction of travel.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/feedforward-controller-terms-f21f39e9.svg&#34; alt=&#34;Feedforward terms over a trapezoidal move with a 2 kg mass: the inertia termis a square wave of plus and minus 10 N, the friction term steps to 10 N whilemoving and returns to zero at rest, and the total peaks near 26 N duringacceleration.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read each term&amp;rsquo;s contribution off the gap between the curves.&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;totalFeedForwardForce&lt;/code&gt; is zero and the components are not&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;enable&lt;/code&gt; is false or &lt;code&gt;disable&lt;/code&gt; is true, and only the total is gated&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isEnabled&lt;/code&gt;; set &lt;code&gt;enable&lt;/code&gt; true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The feedback controller still does all the work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt; is 0, or the model is undermodelled&lt;/td&gt;
&lt;td&gt;Raise the gain toward 1; if it is already 1, remeasure mass and friction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracking got worse after enabling feedforward&lt;/td&gt;
&lt;td&gt;A term is over-commanded, most often mass&lt;/td&gt;
&lt;td&gt;Halve the value in &lt;code&gt;massLookup&lt;/code&gt; and re-measure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis lurches the moment feedforward is enabled&lt;/td&gt;
&lt;td&gt;Expected on a loaded vertical axis: the gravity term appears at full value with no fade&lt;/td&gt;
&lt;td&gt;Ramp &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 instead of switching &lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis drifts down on a vertical axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;subtractGravityOn&lt;/code&gt; is off, or &lt;code&gt;gravity&lt;/code&gt; has the wrong sign for your convention&lt;/td&gt;
&lt;td&gt;Switch it on; if the drift doubles, reverse the sign of &lt;code&gt;gravity&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The gravity offset is twice what it should be&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gravity&lt;/code&gt; has the wrong sign — the term subtracts it, so a positive value fights instead of holding&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gravity&lt;/code&gt; negative for a conventional upward-positive axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalMass&lt;/code&gt; seems to be ignored&lt;/td&gt;
&lt;td&gt;Expected: it needs both &lt;code&gt;useMassEstimate&lt;/code&gt; true &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;externalMassGain&lt;/code&gt; above 0&lt;/td&gt;
&lt;td&gt;Set both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is smaller than mass × acceleration&lt;/td&gt;
&lt;td&gt;The smoothing filter&amp;rsquo;s coefficients sum to less than 1&lt;/td&gt;
&lt;td&gt;Set them to sum to exactly 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is larger than mass × acceleration&lt;/td&gt;
&lt;td&gt;The smoothing filter&amp;rsquo;s coefficients sum to more than 1&lt;/td&gt;
&lt;td&gt;Set them to sum to 1.0. A warning was logged at startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The inertia term is noisy or steps hard&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accelerationTarget&lt;/code&gt; is stepping&lt;/td&gt;
&lt;td&gt;Enable &lt;code&gt;accelerationFIRFilter&lt;/code&gt; with coefficients summing to 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction compensation is too strong at one end of the stroke&lt;/td&gt;
&lt;td&gt;Friction varies over the stroke&lt;/td&gt;
&lt;td&gt;Tune &lt;code&gt;frictionModel&lt;/code&gt; for the worst case, then fade it with &lt;code&gt;frictionGainLookup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linking a value into the friction model&amp;rsquo;s output scaling did nothing&lt;/td&gt;
&lt;td&gt;Expected: this block writes that input every cycle&lt;/td&gt;
&lt;td&gt;Configure the &lt;code&gt;frictionGainLookup&lt;/code&gt; table instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A friction gain step arrived as a ramp&lt;/td&gt;
&lt;td&gt;Expected: the friction model rate-limits the scaling factor&lt;/td&gt;
&lt;td&gt;Raise the friction model&amp;rsquo;s own scaling-factor rate if you need it faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The friction term chatters around standstill&lt;/td&gt;
&lt;td&gt;No velocity dead zone, or a discontinuous friction model&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;velocityDeadzone&lt;/code&gt;, or switch the friction model to its continuous form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The position term reads zero&lt;/td&gt;
&lt;td&gt;Expected with an unconfigured lookup, or the table is not monotonic&lt;/td&gt;
&lt;td&gt;Configure &lt;code&gt;positionLookup&lt;/code&gt;; a non-monotonic table falls back to zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backlash offset flipped sign when the axis stopped&lt;/td&gt;
&lt;td&gt;Not possible — the direction is held at standstill&lt;/td&gt;
&lt;td&gt;Check the offset is within ±0.1; larger values are corrected silently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backlash offset never applied at all&lt;/td&gt;
&lt;td&gt;The axis has not moved yet, or every velocity was inside the dead zone&lt;/td&gt;
&lt;td&gt;Move the axis once; the direction is zero until then&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardGain&lt;/code&gt; reads back as 1 after writing more&lt;/td&gt;
&lt;td&gt;Expected: it is capped at 1.0&lt;/td&gt;
&lt;td&gt;If you need more, a term is undermodelled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;externalFeedForwardGain&lt;/code&gt; reads back as 1.5&lt;/td&gt;
&lt;td&gt;Expected: that is its ceiling&lt;/td&gt;
&lt;td&gt;Fix the external estimate instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The external force faded away over several seconds&lt;/td&gt;
&lt;td&gt;Not possible in this version — the gain is applied without modifying the input&lt;/td&gt;
&lt;td&gt;Check whatever produces &lt;code&gt;externalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An output went to a non-numeric value&lt;/td&gt;
&lt;td&gt;A non-numeric value reached an input or a lookup table&lt;/td&gt;
&lt;td&gt;Fix the source and restart the controller; this block has no reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need feedforward on several axes&lt;/td&gt;
&lt;td&gt;Not possible — this block handles one axis&lt;/td&gt;
&lt;td&gt;Use one instance per axis, which is what the axes layer does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a horizontal axis with a 2 kg mass on a 1 ms
task, with friction and position terms not yet configured:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable                  = true
totalFeedForwardGain    = 0.0
subtractGravityOn       = false
gravity                 = -9.8066
useMassEstimate         = false
externalMassGain        = 0.0
externalFeedForwardGain = 1.0
velocityDeadzone        = 0.0
accelerationDeadzone    = 0.0
backlashCompensation    = 0.0
massLookup: single point y = 2.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Raise &lt;code&gt;totalFeedForwardGain&lt;/code&gt; from 0 as Setup step 7 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;totalFeedForwardGain&lt;/code&gt; within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A larger value is replaced by 1.0. Feedforward models the plant and cannot usefully exceed it&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;externalFeedForwardGain&lt;/code&gt; within 0 – 1.5&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the range is 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;backlashCompensation&lt;/code&gt; within ±0.1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the range is replaced by the nearest edge. The bound is the same whether the axis is linear or rotary&lt;/td&gt;
&lt;td&gt;Silently; read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friction gain within 0 – 1&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A table value outside the range is replaced by the nearest edge, then rate-limited by the friction model before it applies&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total mass&lt;/td&gt;
&lt;td&gt;Floored at 0&lt;/td&gt;
&lt;td&gt;A negative mass — from the lookup or from &lt;code&gt;externalMass&lt;/code&gt; — is replaced by 0, so the inertia term can never invert and fight the commanded acceleration&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smoothing filter gain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked at runtime.&lt;/strong&gt; The filter&amp;rsquo;s coefficients scale the whole inertia term by their sum. A sum above 1.0 is &lt;strong&gt;logged as a warning at startup only&lt;/strong&gt; — a coefficient edited while running is not re-checked&lt;/td&gt;
&lt;td&gt;Logged at startup; not reported at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;totalFeedForwardForce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the model computes reaches the output. 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;Component outputs&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded, and reported whether the block is enabled or not&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block 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; &lt;code&gt;enable&lt;/code&gt; false zeroes the total but leaves the friction model and filters holding their state&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 logs one warning, at startup only: that the acceleration smoothing
filter&amp;rsquo;s coefficients sum to more than 1.0 and are therefore amplifying the
inertia term. 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: StateController</title>
      <link>/docs/developing-control-applications/control-blocks/control-loops/state-controller/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/control-loops/state-controller/</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;StateController&lt;/code&gt; closes a loop on the &lt;strong&gt;whole state vector&lt;/strong&gt; at once, with an
independent gain per state, rather than on a single error signal. Given a pole
placement or LQR design done offline, it gives a response no cascade of
single-loop controllers can. Pair it with &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/observer/&#34;&gt;&lt;code&gt;Observer&lt;/code&gt;&lt;/a&gt; when the
states you need are not all measured.&lt;/p&gt;
&lt;p&gt;It is purely algebraic — no integration, no memory. Its output depends only on
this cycle&amp;rsquo;s inputs, and &lt;strong&gt;it has no integral action&lt;/strong&gt;, so a constant
disturbance leaves a constant error. Add a &lt;a href=&#34;/docs/developing-control-applications/control-blocks/control-loops/pid/&#34;&gt;&lt;code&gt;PID&lt;/code&gt;&lt;/a&gt; alongside it if you
need that error removed.&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;stateVector — the measured or estimated states&amp;quot;]) --&amp;gt; B[&amp;quot;StateController&amp;quot;]
    B --&amp;gt; o1([&amp;quot;stateErrorVector — error per state&amp;quot;])
    B --&amp;gt; o2([&amp;quot;controlOutputVector — the control output&amp;quot;])
    B --&amp;gt; o3([&amp;quot;controlOutput — first element, as a single value&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The control law is $u = -K(x - x_d)$, with $x_d$ = &lt;code&gt;stateTargetVector&lt;/code&gt; and
$K$ = &lt;code&gt;controllerGainVector&lt;/code&gt;. The closed-loop poles are set by your plant and
that gain together, and the design is done &lt;strong&gt;offline&lt;/strong&gt; — this block only
applies the result. &lt;code&gt;controllerGainVector&lt;/code&gt; at zero makes the block inert.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;controllerGainVector&lt;/code&gt; is written as one flat list, filled column by
column.&lt;/strong&gt; For a 4-state 2-input system the first four values are the gains for
the &lt;em&gt;first&lt;/em&gt; input.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Both limiters default to zero at both ends.&lt;/strong&gt; Switching either on without
setting its bounds forces the limited quantity to zero — see Limits and
errors. &lt;strong&gt;The block has no enable and no disable.&lt;/strong&gt;&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;stateVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;unbounded, or the input limiter&amp;rsquo;s bounds&lt;/td&gt;
&lt;td&gt;The measured or estimated state, one element per state. &lt;strong&gt;While the input limiter is on, this path is overwritten with the clamped value&lt;/strong&gt;, so a trace shows the limited state and not what was linked in.&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;stateErrorVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;The state error, one element per state: &lt;code&gt;stateVector&lt;/code&gt; minus &lt;code&gt;stateTargetVector&lt;/code&gt;. The most useful signal for confirming the controller sees what you think it does.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator units&lt;/td&gt;
&lt;td&gt;The control output, one element per input. It appears at full value on the first cycle — there is no ramp, because the block has no memory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator units&lt;/td&gt;
&lt;td&gt;Element 0 of &lt;code&gt;controlOutputVector&lt;/code&gt;, as a single value, for the common single-input case.&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;controllerGainVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator units per state unit&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The gain, and the whole of the design.&lt;/strong&gt; States × inputs, column-major. Comes from your offline pole placement or LQR. Zero makes the block inert.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateTargetVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The state to drive toward. &lt;strong&gt;This is configuration, not a linked signal&lt;/strong&gt; — it cannot be driven from another block, only written directly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterEnable&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;Bounds each element of &lt;code&gt;stateVector&lt;/code&gt; before the error is formed. &lt;strong&gt;Set the bounds before switching this on.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;all zeros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;below &lt;code&gt;inputLimiterMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower bound per state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;all zeros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above &lt;code&gt;inputLimiterMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper bound per state. Equal to the minimum — as both defaults are — forces every state to that value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLimiterEnable&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;Bounds each element of &lt;code&gt;controlOutputVector&lt;/code&gt;. This is the only thing that limits what reaches the actuator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLimiterMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator units&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;all zeros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;below &lt;code&gt;outputLimiterMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lower bound per input.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLimiterMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;actuator units&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;all zeros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;above &lt;code&gt;outputLimiterMin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upper bound per input. Same trap as the input limiter.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All eight parameters are persistent and survive a controller restart. The input
and outputs do not. &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;Do the design offline. Choose the closed-loop poles you want from your plant
model and compute the gain. This block applies a gain; it cannot help you
find one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read the length of &lt;code&gt;stateVector&lt;/code&gt; from the parameter tree and confirm it
matches your model&amp;rsquo;s state count. It is fixed when the machine is built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;controllerGainVector&lt;/code&gt; at zeros, and leave both limiter enables false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;stateVector&lt;/code&gt; from your measurement or from an observer&amp;rsquo;s estimate.
Confirm each element on a trace against what you expect it to be — a state
in the wrong slot is the most common wiring error and produces a plausible,
wrong controller.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;stateTargetVector&lt;/code&gt; to the state you want held. Confirm
&lt;code&gt;stateErrorVector&lt;/code&gt; reads the difference you expect, element by element.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;outputLimiterMin&lt;/code&gt; and &lt;code&gt;outputLimiterMax&lt;/code&gt; to what the actuator can
accept, then set &lt;code&gt;outputLimiterEnable&lt;/code&gt; true. Confirm
&lt;code&gt;controlOutputVector&lt;/code&gt; is still zero — the gain is still zeros.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write &lt;code&gt;controllerGainVector&lt;/code&gt; from your design, &lt;strong&gt;column by column&lt;/strong&gt;. For a
single-input system it is simply one value per state, in state order.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 produces full control output on the very first cycle.&lt;/strong&gt; The block
is algebraic, so there is no ramp and no transient — if the state is away
from the target when you write the gain, the actuator gets the whole
commanded output at once. Write it with the axis at rest and near its
target, and rely on step 6&amp;rsquo;s limiter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm the closed loop behaves as the offline design predicted. If it does
not, check step 4 and the column order in step 7 before touching the gain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;p&gt;There is nothing to tune here in the usual sense. The gain comes from an
offline design, and this block applies it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Verify the state wiring before the gain, per Setup step 4. Every diagnosis
below assumes the states are in the right slots.&lt;/li&gt;
&lt;li&gt;Confirm the sign. A gain of the wrong sign is positive feedback, and the
symptom is an axis that runs away as soon as the gain is written. Set the
output limiter first so this is survivable.&lt;/li&gt;
&lt;li&gt;Check the column order with a deliberately &lt;strong&gt;asymmetric&lt;/strong&gt; gain on a
multi-input system. A symmetric test gain cannot tell a transposed matrix
from a correct one.&lt;/li&gt;
&lt;li&gt;Scale the whole gain vector down by a factor of 2 or 4 for the first trial,
then work back up to the designed value. This is the safe way to approach a
design you have not run on hardware.&lt;/li&gt;
&lt;li&gt;Compare the closed-loop response against your offline prediction. A
mismatch in shape means the state wiring or the column order; a mismatch in
magnitude means the gain or the plant model.&lt;/li&gt;
&lt;li&gt;Accept the steady-state error, or add integral action elsewhere. This block
has none and cannot acquire any.&lt;/li&gt;
&lt;li&gt;Use the input limiter only to protect against a bad estimate — a wild value
from an observer that has not converged, say. It is not a tuning knob, and
it changes what the controller sees.&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/state-controller-limit-85d60e25.svg&#34; alt=&#34;Control output against state error at gain 2, with three output limits: theunbounded line runs straight through, the plus-or-minus 3 limit clips beyond anerror of 1.5, and the plus-or-minus 1 limit clips beyond0.5.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read where each line flattens as the point the output limiter took over.&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;Everything reads zero&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;controllerGainVector&lt;/code&gt; defaults to zeros&lt;/td&gt;
&lt;td&gt;Write the gain from your design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every state reads zero and the controller drives hard&lt;/td&gt;
&lt;td&gt;The input limiter is on with its default bounds of zero, so it forces every state to zero&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;inputLimiterMin&lt;/code&gt; and &lt;code&gt;inputLimiterMax&lt;/code&gt;, or switch the limiter off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputVector&lt;/code&gt; is stuck at zero with a non-zero gain and error&lt;/td&gt;
&lt;td&gt;The output limiter is on with its default bounds of zero&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;outputLimiterMin&lt;/code&gt; and &lt;code&gt;outputLimiterMax&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis ran away as soon as the gain was written&lt;/td&gt;
&lt;td&gt;The gain has the wrong sign — this is positive feedback&lt;/td&gt;
&lt;td&gt;Negate the gain; set the output limiter before retrying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response is nothing like the offline design&lt;/td&gt;
&lt;td&gt;A state is in the wrong slot, or the gain is transposed&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;stateErrorVector&lt;/code&gt; element by element, then the column order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One state seems to affect the wrong actuator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;controllerGainVector&lt;/code&gt; is transposed&lt;/td&gt;
&lt;td&gt;Rewrite it column-major: states × inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A constant error never goes away&lt;/td&gt;
&lt;td&gt;Expected: this block has no integral action&lt;/td&gt;
&lt;td&gt;Add a PID alongside it, or add an integrating state to your model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVector&lt;/code&gt; on a trace does not match what is linked into it&lt;/td&gt;
&lt;td&gt;Expected: the input limiter overwrites this path in place&lt;/td&gt;
&lt;td&gt;Switch the input limiter off, or read the source signal instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output jumped to full value the instant the gain was written&lt;/td&gt;
&lt;td&gt;Expected: the block is algebraic and has no ramp&lt;/td&gt;
&lt;td&gt;Write the gain at rest, near the target, with the output limiter set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is noisy&lt;/td&gt;
&lt;td&gt;The state estimate is noisy, and the gain passes it straight through&lt;/td&gt;
&lt;td&gt;Fix the estimate; this block has no filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response changed after a task-rate change&lt;/td&gt;
&lt;td&gt;Not possible — this block is task-rate independent&lt;/td&gt;
&lt;td&gt;Look for the change elsewhere in the loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A limiter behaved oddly with the minimum above the maximum&lt;/td&gt;
&lt;td&gt;The minimum wins, so the value is pinned at the minimum&lt;/td&gt;
&lt;td&gt;Keep every minimum below its maximum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutput&lt;/code&gt; disagrees with &lt;code&gt;controlOutputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not possible — it is element 0 of that vector&lt;/td&gt;
&lt;td&gt;Check you are reading the element you mean&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 input is clean&lt;/td&gt;
&lt;td&gt;Fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need a different number of states&lt;/td&gt;
&lt;td&gt;Not possible — the dimensions are fixed when the machine is built&lt;/td&gt;
&lt;td&gt;It needs a configuration change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a two-state axis — position and velocity — with one
actuator input, on any task rate. The gain is scaled to a quarter of a designed
value for the first trial:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;controllerGainVector = 25.0, 1.5
stateTargetVector    = 0.0, 0.0
outputLimiterEnable  = true
outputLimiterMin     = -10.0
outputLimiterMax     = 10.0
inputLimiterEnable   = false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Work up to the designed gain as Tuning step 4 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;Both limiters&#39; bounds&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 both default to zero at each end.&lt;/strong&gt; Enabling a limiter without setting its bounds forces the limited quantity to zero. Set the bounds first, always&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limiter minimum below maximum&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. With the minimum above the maximum the value is pinned at the minimum&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controlOutputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputLimiterMin&lt;/code&gt;, &lt;code&gt;outputLimiterMax&lt;/code&gt; while enabled&lt;/td&gt;
&lt;td&gt;Clamped per element. With the limiter off it is &lt;strong&gt;unbounded&lt;/strong&gt; — whatever the gain and error produce reaches the actuator&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputLimiterMin&lt;/code&gt;, &lt;code&gt;inputLimiterMax&lt;/code&gt; while enabled&lt;/td&gt;
&lt;td&gt;Clamped per element, &lt;strong&gt;and the input path is overwritten with the clamped value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closed-loop stability&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked, and cannot be — the block knows nothing about your plant. A gain that destabilises the machine is accepted without comment. Your offline design is the only safeguard&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gain element order&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Column-major, always. A row-major list silently produces a transposed gain&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steady-state error&lt;/td&gt;
&lt;td&gt;Inherent&lt;/td&gt;
&lt;td&gt;There is no integral action, so a constant disturbance leaves a constant error&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateTargetVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Configuration only. It cannot be driven from another block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dimensions&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed when the machine is built. Read the array lengths to discover them&lt;/td&gt;
&lt;td&gt;Not reported&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;The block has no memory, so there is nothing to reset and nothing survives a stop&lt;/td&gt;
&lt;td&gt;Not applicable&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>
