<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title> – Estimation and maths</title>
    <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/</link>
    <description>Recent content in Estimation and maths on </description>
    <generator>Hugo -- gohugo.io</generator>
    
	  <atom:link href="/docs/developing-control-applications/control-blocks/estimation-and-maths/index.xml" rel="self" type="application/rss+xml" />
    
    
      
        
      
    
    
    <item>
      <title>Docs: Observer</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/observer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/observer/</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;Observer&lt;/code&gt; estimates every state of a plant, including the ones no sensor
measures — a velocity from position alone, a load torque from motor current, a
temperature inside a body from its surface. It runs a model of the plant,
compares the model&amp;rsquo;s predicted measurement against the real one, and uses the
difference to pull the model back toward the truth every cycle.&lt;/p&gt;
&lt;p&gt;It is a plant model plus one correction term. Without that term the model
drifts away from reality; with it, the estimate converges and stays converged.&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;inputVector — the plant&#39;s input&amp;quot;]) --&amp;gt; B[&amp;quot;Observer&amp;quot;]
    i2([&amp;quot;measurementVector — what the sensors read&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;stateVector — the estimated states&amp;quot;])
    B --&amp;gt; o2([&amp;quot;stateDotVector — rate of change of the estimate&amp;quot;])
    B --&amp;gt; o3([&amp;quot;outputVector — the predicted measurement&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The model is $\dot{\hat{x}} = A\hat{x} + Bu + L(y_m - \hat{y})$ and
$\hat{y} = C\hat{x} + Du$. &lt;strong&gt;Tuning is entirely &lt;code&gt;observerGain&lt;/code&gt;&lt;/strong&gt; ($L$): it
sets how fast the estimate is pulled toward the measurement. Place the
observer 3 to 10 times faster than the plant. Faster converges sooner &lt;strong&gt;and
passes more sensor noise into every estimate&lt;/strong&gt;, and past a point it makes the
block diverge — see Limits and errors. &lt;code&gt;observerGain&lt;/code&gt; at zero leaves an
open-loop simulation that ignores the measurement entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every matrix is written as one flat list, filled column by column.&lt;/strong&gt; For a
4-state 2-measurement system the first four values of &lt;code&gt;observerGain&lt;/code&gt; are the
gains from the &lt;em&gt;first&lt;/em&gt; measurement to all four states.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable, no disable and no reset you can reach.&lt;/strong&gt; It always
runs, and its estimate cannot be re-initialised from the parameter tree — see
Limits and errors.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;plant input units&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;What is being commanded into the real plant, one element per input. The observer needs this to predict how the plant will respond.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measurementVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor units&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;What the sensors actually read, one element per measurement. This is the truth the estimate is corrected against. Note the block&amp;rsquo;s &lt;em&gt;predicted&lt;/em&gt; measurement is &lt;code&gt;outputVector&lt;/code&gt; — do not confuse the two.&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;stateVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;The estimated state, one element per state — &lt;strong&gt;the reason to use this block.&lt;/strong&gt; It starts at zero after a controller start and converges from there. It is not cleared by a stop, so after a restart it briefly holds an estimate unrelated to the machine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateDotVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units per second&lt;/td&gt;
&lt;td&gt;Rate of change of the estimate on this cycle.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor units&lt;/td&gt;
&lt;td&gt;The measurement the model &lt;strong&gt;predicts&lt;/strong&gt;, for comparison against &lt;code&gt;measurementVector&lt;/code&gt;. Subtract the two to see how well the observer is tracking: that difference should be small and settle toward zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;observerGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units per sensor unit per second&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 one thing to tune.&lt;/strong&gt; How hard the measurement pulls the estimate. Higher converges faster and passes more noise; too high makes the estimate grow without bound. States × measurements, column-major. Zero leaves an open-loop simulation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Plant dynamics, states × states, column-major. Continuous-time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;state units per input unit per second&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Input matrix, states × inputs, column-major.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit per state unit&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Which states the sensors see, and how. Measurements × states, column-major. &lt;strong&gt;A state no row of this matrix reaches cannot be estimated at any gain.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;sensor unit per input unit&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Direct feed-through from input to predicted measurement, measurements × inputs, column-major. Usually zeros.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVectorInit&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;Intended as the state a reset restores. &lt;strong&gt;It has no effect&lt;/strong&gt; — the reset is not reachable and the estimate always starts at zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All six 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;Write down your model&amp;rsquo;s dimensions: states, inputs, measurements. Read the
lengths of &lt;code&gt;stateVector&lt;/code&gt;, &lt;code&gt;inputVector&lt;/code&gt; and &lt;code&gt;measurementVector&lt;/code&gt; from the
parameter tree and confirm they match. They are fixed when the machine is
built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check that every state you want estimated is reachable through &lt;code&gt;matrixC&lt;/code&gt;. A
state that no measurement depends on, directly or through the dynamics,
cannot be estimated at any gain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm your matrices are continuous-time.&lt;/strong&gt; A discretised model will be
wrong by a factor of the task period.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write &lt;code&gt;matrixA&lt;/code&gt; through &lt;code&gt;matrixD&lt;/code&gt; as flat lists, &lt;strong&gt;column by column&lt;/strong&gt;. For a
2×2 matrix the order is row 1 column 1, row 2 column 1, row 1 column 2, row
2 column 2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;observerGain&lt;/code&gt; at zeros. Link &lt;code&gt;inputVector&lt;/code&gt; from whatever commands the
real plant, and &lt;code&gt;measurementVector&lt;/code&gt; from the sensors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compare &lt;code&gt;outputVector&lt;/code&gt; against &lt;code&gt;measurementVector&lt;/code&gt; on one trace. With zero
gain the model runs open loop, so the two will drift apart — but they should
start out the same shape. &lt;strong&gt;If they are not, the model is wrong; fix that
before adding any gain.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;observerGain&lt;/code&gt; from zero until &lt;code&gt;outputVector&lt;/code&gt; tracks
&lt;code&gt;measurementVector&lt;/code&gt; closely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 is where this block becomes unstable if you push it.&lt;/strong&gt; Too high a
gain does not merely pass noise — it makes the estimate grow without
bound, and only a controller restart clears it. Raise it in steps and
watch &lt;code&gt;stateVector&lt;/code&gt; between each.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;stateVector&lt;/code&gt; settles and stays settled with the machine at rest.&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 model right first, with &lt;code&gt;observerGain&lt;/code&gt; at zero, per Setup step 6.
No gain can rescue a wrong model — it will give you a confidently wrong
estimate with a small-looking error.&lt;/li&gt;
&lt;li&gt;Work out your plant&amp;rsquo;s fastest pole, in rad/s. Aim to place the observer 3 to
10 times faster than that. That factor, not a number, is the target.&lt;/li&gt;
&lt;li&gt;Raise &lt;code&gt;observerGain&lt;/code&gt; from zero in steps of roughly double. After each step,
watch &lt;code&gt;stateVector&lt;/code&gt; for a few seconds with the machine at rest.&lt;/li&gt;
&lt;li&gt;Stop as soon as noise appears on &lt;code&gt;stateVector&lt;/code&gt;. Every measurement&amp;rsquo;s noise
reaches every estimated state through the gain, and the states no sensor
measures are usually the noisiest.&lt;/li&gt;
&lt;li&gt;Judge convergence by subtracting &lt;code&gt;outputVector&lt;/code&gt; from &lt;code&gt;measurementVector&lt;/code&gt;.
That difference is the observer&amp;rsquo;s error signal: it should be small in steady
state and settle quickly after a disturbance.&lt;/li&gt;
&lt;li&gt;Test convergence deliberately: start the controller with the machine away
from zero, and time how long &lt;code&gt;stateVector&lt;/code&gt; takes to reach the truth. That
time is your observer&amp;rsquo;s response.&lt;/li&gt;
&lt;li&gt;Check the gain against your task period. A gain fast enough to converge in
a few task periods will make the block diverge — the practical ceiling is
an observer response no faster than about a fifth of the task period.&lt;/li&gt;
&lt;li&gt;If you need both fast convergence and quiet estimates, and your model is
uncertain, use a Kalman filter instead — it trades the two off explicitly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/observer-convergence-37405e66.svg&#34; alt=&#34;Estimation error after a wrong initial state at three observer gains: gain 2settles in about 0.5 s, gain 10 in about 0.1 s, and gain 50 in about0.02 s.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the convergence time off any curve as the point it crosses 0.37.&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;stateVector&lt;/code&gt; grows without bound&lt;/td&gt;
&lt;td&gt;&lt;code&gt;observerGain&lt;/code&gt; too high for the task period, or a wrong model&lt;/td&gt;
&lt;td&gt;Lower the gain, restart the controller, and re-check step 6 of Setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything reads zero&lt;/td&gt;
&lt;td&gt;Expected with unconfigured matrices — all default to zero&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;matrixA&lt;/code&gt; through &lt;code&gt;matrixD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputVector&lt;/code&gt; never comes near &lt;code&gt;measurementVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;observerGain&lt;/code&gt; is zero, so the model runs open loop&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;observerGain&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputVector&lt;/code&gt; tracks but one state never settles&lt;/td&gt;
&lt;td&gt;That state is not reachable through &lt;code&gt;matrixC&lt;/code&gt;, so no gain can estimate it&lt;/td&gt;
&lt;td&gt;Add a measurement, or accept that the state is not observable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate is noisy&lt;/td&gt;
&lt;td&gt;Gain too high — every measurement&amp;rsquo;s noise reaches every state&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;observerGain&lt;/code&gt; and accept slower convergence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate converges too slowly&lt;/td&gt;
&lt;td&gt;Gain too low&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;observerGain&lt;/code&gt;, watching for noise and divergence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate looks confident and is wrong&lt;/td&gt;
&lt;td&gt;The model is wrong: a bad model produces a small error and a false estimate&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;observerGain&lt;/code&gt; to zero and re-verify the model open loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate is smooth but lags the machine&lt;/td&gt;
&lt;td&gt;Gain too low, or the model&amp;rsquo;s dynamics are too slow&lt;/td&gt;
&lt;td&gt;Raise the gain; if that adds noise, fix the model instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response has the right timescale but the wrong shape&lt;/td&gt;
&lt;td&gt;A matrix was written row by row instead of column by column&lt;/td&gt;
&lt;td&gt;Rewrite it column-major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One measurement seems to affect the wrong states&lt;/td&gt;
&lt;td&gt;&lt;code&gt;observerGain&lt;/code&gt; is transposed&lt;/td&gt;
&lt;td&gt;Rewrite it column-major: states × measurements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response is far faster or slower than your offline design&lt;/td&gt;
&lt;td&gt;Discretised matrices were supplied; this block wants continuous-time ones&lt;/td&gt;
&lt;td&gt;Convert back to continuous time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A wrong estimate immediately after a controller restart&lt;/td&gt;
&lt;td&gt;Expected: the estimate is not cleared by a stop and starts from wherever it was&lt;/td&gt;
&lt;td&gt;Wait for it to converge, or gate the consumer for a few observer time constants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVectorInit&lt;/code&gt; seems to be ignored&lt;/td&gt;
&lt;td&gt;Expected: it has no effect, because the reset that would apply it is not reachable&lt;/td&gt;
&lt;td&gt;Ignore this parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot re-initialise the estimate&lt;/td&gt;
&lt;td&gt;Expected: there is no reset input&lt;/td&gt;
&lt;td&gt;Restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A value went non-numeric and stayed there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached a matrix or an input&lt;/td&gt;
&lt;td&gt;Restart the controller; there is no way to clear it in service&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 estimating velocity from a position measurement on a 1 ms
task. Two states — position and velocity — one input, one measurement:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;matrixA      = 0, 0, 1, 0
matrixB      = 0, 1
matrixC      = 1, 0
matrixD      = 0
observerGain = 200, 10000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;matrixA&lt;/code&gt; is column-major. Start with &lt;code&gt;observerGain&lt;/code&gt; at zeros and work Setup
steps 6 and 7. 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;observerGain&lt;/code&gt; against the task period&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 this is the bound you will hit while tuning.&lt;/strong&gt; Too high a gain makes the estimate grow without bound. 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;Task period against the model&amp;rsquo;s poles&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 limit applies to &lt;code&gt;matrixA&lt;/code&gt; alone: the task period must stay below 2 divided by the largest pole magnitude in rad/s&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; A state no measurement reaches cannot be estimated at any gain, and the symptom is a state that simply drifts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model accuracy&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not checked, and cannot be. A wrong model gives a confident, wrong estimate with a small error signal&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix 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 matrix&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model time domain&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Continuous-time only&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix 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;Estimate 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;, so the estimate cannot be re-initialised in service. &lt;code&gt;stateVectorInit&lt;/code&gt; has no effect. A restart is the only way&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;code&gt;outputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded&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: ObserverKalmanFilter</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/observer-kalman-filter/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/observer-kalman-filter/</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;ObserverKalmanFilter&lt;/code&gt; estimates a joint&amp;rsquo;s position, velocity and torque from
noisy sensor readings, using a model of the joint to reject noise the sensor
cannot distinguish from signal. Its purpose is &lt;strong&gt;torque estimation&lt;/strong&gt;: turning a
noisy torque reading into a smooth one you can control against.&lt;/p&gt;
&lt;p&gt;Unlike &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;, you do not tune a gain. You tell the filter
how much to trust the model and how much to trust the sensors, and it works the
gain out for itself, every update. That is the right structure when the model
is approximate and the sensors are noisy.&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;inputVector — commanded torque&amp;quot;]) --&amp;gt; B[&amp;quot;ObserverKalmanFilter&amp;quot;]
    i2([&amp;quot;measurementVector — measured position, velocity, torque&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;stateVectorInit&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;stateVector — estimated position, velocity, torque&amp;quot;])
    B --&amp;gt; o2([&amp;quot;covarianceMatrix — the filter&#39;s own uncertainty&amp;quot;])
    B --&amp;gt; o3([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The model is $I\ddot{\theta} + b\dot{\theta} + k\theta = \tau - F$, where the
friction force $F$ is $c_v\dot{\theta} + c_c\operatorname{sign}(\dot{\theta})$
— a velocity-proportional part plus a constant-magnitude part.
&lt;strong&gt;Tuning is the ratio of &lt;code&gt;processNoiseCovariance&lt;/code&gt; to
&lt;code&gt;measurementNoiseCovariance&lt;/code&gt;&lt;/strong&gt;: raise the first to follow the sensors,
raise the second to trust the model and smooth harder. The third diagonal
element of &lt;code&gt;processNoiseCovariance&lt;/code&gt; is what decides how fast the torque
estimate may move.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;measurementVector&lt;/code&gt; needs all three values&lt;/strong&gt; — position, velocity &lt;strong&gt;and&lt;/strong&gt;
torque. Leaving the velocity element at zero tells the filter the joint is
measured to be stationary, which fights its own estimate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never set &lt;code&gt;measurementNoiseCovariance&lt;/code&gt; to zero.&lt;/strong&gt; It is the natural way to
say &amp;ldquo;trust the sensors completely&amp;rdquo; and it makes the filter&amp;rsquo;s arithmetic
ill-conditioned. &lt;strong&gt;Never set &lt;code&gt;frequencyDivider&lt;/code&gt; to zero&lt;/strong&gt; either — see Limits
and errors.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The torque commanded into the joint. The filter needs it to predict how the joint will respond. A single value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measurementVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed: rad, rad/s, N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;What the sensors read, as &lt;strong&gt;three values in this order&lt;/strong&gt;: position, velocity, torque. All three are used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVectorInit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed: rad, rad/s, N·m&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Intended as a starting estimate. &lt;strong&gt;It has no effect&lt;/strong&gt; — nothing in the block reads it, because there is no reset to apply 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;-&lt;/td&gt;
&lt;td&gt;True &lt;strong&gt;freezes&lt;/strong&gt; the filter: the estimate and the uncertainty both hold their last values and nothing advances. It is not a bypass and not a zeroing.&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;stateVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed: rad, rad/s, N·m&lt;/td&gt;
&lt;td&gt;The estimate, as three values: position, velocity, and &lt;strong&gt;filtered torque — the reason to use this block&lt;/strong&gt;. It starts at zero after a controller start and converges from there. Not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;covarianceMatrix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed&lt;/td&gt;
&lt;td&gt;The filter&amp;rsquo;s own estimate of its uncertainty, 3×3. Useful for watching convergence — the diagonal should settle and stay settled. &lt;strong&gt;Do not read it as a true confidence bound&lt;/strong&gt;; see Limits and errors.&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;Off by default. False freezes the filter entirely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;processNoiseCovariance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed&lt;/td&gt;
&lt;td&gt;identity&lt;/td&gt;
&lt;td&gt;any positive-definite&lt;/td&gt;
&lt;td&gt;How much you distrust the &lt;strong&gt;model&lt;/strong&gt;, 3×3. Raise it to let the estimate follow the sensors more closely. Its third diagonal element governs how fast the torque estimate may change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measurementNoiseCovariance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed&lt;/td&gt;
&lt;td&gt;identity&lt;/td&gt;
&lt;td&gt;positive-definite; &lt;strong&gt;never zero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How much you distrust the &lt;strong&gt;sensors&lt;/strong&gt;, 3×3. Raise it to smooth harder and converge more slowly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frequencyDivider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cycles&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 or more&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Task cycles between corrections. The prediction runs every cycle; the correction — the expensive part — runs every Nth. &lt;strong&gt;0 faults the controller.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/inertia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;kg·m²&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;above 0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Joint inertia. &lt;strong&gt;Zero is not checked and breaks the filter.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/damping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m·s/rad&lt;/td&gt;
&lt;td&gt;10.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Viscous damping in the joint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/stiffness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m/rad&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Joint stiffness. Leave at 0 for a rigid joint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/viscousFrictionCoefficient&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m·s/rad&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Velocity-proportional friction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/coulombFrictionCoefficient_&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N·m&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;0 upward&lt;/td&gt;
&lt;td&gt;Constant-magnitude friction. Note the trailing underscore in the path name — it is not a typo in your configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All nine 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; other than the
five &lt;code&gt;model/&lt;/code&gt; entries listed above.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Measure or estimate the joint&amp;rsquo;s inertia, damping and stiffness. These are
physical properties, not tuning knobs — get them roughly right before
touching the covariances.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;model/inertia&lt;/code&gt;, &lt;code&gt;model/damping&lt;/code&gt; and &lt;code&gt;model/stiffness&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;model/inertia&lt;/code&gt;
must be above zero.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the two friction coefficients from the joint&amp;rsquo;s own friction
measurement, or leave them at their defaults if you have no figure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link &lt;code&gt;inputVector&lt;/code&gt; from the commanded torque, and all three elements of
&lt;code&gt;measurementVector&lt;/code&gt; from the sensors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm all three measurement elements move.&lt;/strong&gt; Trace each one. A stuck
zero on the velocity element is the most damaging misconfiguration of this
block, because the filter treats it as a measurement of standstill.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;frequencyDivider&lt;/code&gt; at 1, and both covariances at identity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and watch element 2 of &lt;code&gt;stateVector&lt;/code&gt; against the raw
torque measurement on one trace. The estimate should be a smoothed version
of the measurement, following it without the noise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 is the only check that the model is right.&lt;/strong&gt; If the estimate
drifts away from the measurement rather than smoothing it, the model is
wrong — go back to step 1. No covariance setting fixes a wrong model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;frequencyDivider&lt;/code&gt; only if the block costs more processing time than
you can afford. Its correction step is the expensive part.&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 model right first, per Setup step 7. The covariances trade noise
against lag; they cannot correct a wrong inertia.&lt;/li&gt;
&lt;li&gt;Tune the &lt;strong&gt;ratio&lt;/strong&gt;, not the absolute values. Only the ratio of
&lt;code&gt;processNoiseCovariance&lt;/code&gt; to &lt;code&gt;measurementNoiseCovariance&lt;/code&gt; affects the result,
so leave one at identity and move the other.&lt;/li&gt;
&lt;li&gt;Start by raising &lt;code&gt;measurementNoiseCovariance&lt;/code&gt; — multiply the whole matrix by
10 — and watch element 2 of &lt;code&gt;stateVector&lt;/code&gt;. It should get smoother and slower.&lt;/li&gt;
&lt;li&gt;Stop when the lag becomes unacceptable for whatever consumes the estimate.
Smoothness and lag are the whole trade here.&lt;/li&gt;
&lt;li&gt;If you need the torque estimate to respond faster without touching the
others, raise &lt;strong&gt;only the third diagonal element&lt;/strong&gt; of
&lt;code&gt;processNoiseCovariance&lt;/code&gt;. That element governs the torque state alone.&lt;/li&gt;
&lt;li&gt;Watch the diagonal of &lt;code&gt;covarianceMatrix&lt;/code&gt; while you tune. It should settle to
steady values. A diagonal that keeps growing means the filter is not getting
useful corrections — check step 5 of Setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tune empirically, from the traces.&lt;/strong&gt; Do not compute the covariances from
sensor datasheets and expect the theoretical result: this filter&amp;rsquo;s internal
gain does not correspond exactly to the model it runs, so the numbers that
work will not be the numbers theory predicts.&lt;/li&gt;
&lt;li&gt;Re-check nothing after a task-rate change to the covariances themselves, but
do re-check &lt;code&gt;frequencyDivider&lt;/code&gt; — it counts cycles, so the correction rate in
hertz moves with the task period.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/observer-kalman-filter-trust-76f086c2.svg&#34; alt=&#34;Torque estimate through a step at three measurement-noise settings: a lowsetting follows the noisy measurement closely, identity gives a balancedresponse, and a high setting is very smooth and reaches the stepslowly.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the trade off any curve: smoother means slower.&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 controller faulted as soon as &lt;code&gt;frequencyDivider&lt;/code&gt; was written&lt;/td&gt;
&lt;td&gt;It was set to 0&lt;/td&gt;
&lt;td&gt;Set it to 1 or more and restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything reads zero and never moves&lt;/td&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt; is false, or &lt;code&gt;disable&lt;/code&gt; is true&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate is frozen at an old value&lt;/td&gt;
&lt;td&gt;Expected: a disabled filter freezes rather than zeroing&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;enable&lt;/code&gt; true and &lt;code&gt;disable&lt;/code&gt; false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate drifts away from the torque measurement&lt;/td&gt;
&lt;td&gt;The model is wrong — most often &lt;code&gt;model/inertia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Re-measure the inertia; no covariance setting fixes this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate fights the measured velocity&lt;/td&gt;
&lt;td&gt;The velocity element of &lt;code&gt;measurementVector&lt;/code&gt; is stuck at zero, so the filter reads standstill&lt;/td&gt;
&lt;td&gt;Link all three measurement elements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate is as noisy as the raw sensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;measurementNoiseCovariance&lt;/code&gt; too low relative to &lt;code&gt;processNoiseCovariance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiply &lt;code&gt;measurementNoiseCovariance&lt;/code&gt; by 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate lags the real torque badly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;measurementNoiseCovariance&lt;/code&gt; too high&lt;/td&gt;
&lt;td&gt;Lower it, or raise the third diagonal element of &lt;code&gt;processNoiseCovariance&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque estimate is slow but position and velocity are fine&lt;/td&gt;
&lt;td&gt;The third diagonal element of &lt;code&gt;processNoiseCovariance&lt;/code&gt; is too small&lt;/td&gt;
&lt;td&gt;Raise that element alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate went to a non-numeric value and stayed there&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model/inertia&lt;/code&gt; is 0, or a non-numeric value reached an input&lt;/td&gt;
&lt;td&gt;Set a non-zero inertia, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;covarianceMatrix&lt;/code&gt; looks healthy but the estimate is nonsense&lt;/td&gt;
&lt;td&gt;The filter protects its uncertainty from bad values but not its estimate&lt;/td&gt;
&lt;td&gt;Restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter behaved oddly after &lt;code&gt;measurementNoiseCovariance&lt;/code&gt; was set to zero&lt;/td&gt;
&lt;td&gt;Expected: zero makes the internal arithmetic ill-conditioned&lt;/td&gt;
&lt;td&gt;Use a small positive value instead of zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;covarianceMatrix&lt;/code&gt;&amp;rsquo;s diagonal keeps growing&lt;/td&gt;
&lt;td&gt;The corrections are not helping — check the measurements and &lt;code&gt;frequencyDivider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Work Setup step 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A stale estimate for a while after re-enabling&lt;/td&gt;
&lt;td&gt;Expected: the filter resumes from where it froze, and still believes its old uncertainty&lt;/td&gt;
&lt;td&gt;Re-enable with the joint near where it was, or restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The estimate is wrong and there is no way to clear it&lt;/td&gt;
&lt;td&gt;Expected: there is no reset in this block, and &lt;code&gt;stateVectorInit&lt;/code&gt; has no effect&lt;/td&gt;
&lt;td&gt;Restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The torque estimate carries a small constant offset at standstill&lt;/td&gt;
&lt;td&gt;Expected: the friction model applies its constant term even at zero velocity&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;model/coulombFrictionCoefficient_&lt;/code&gt;, or accept the offset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The correction rate changed after a task-rate change&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;frequencyDivider&lt;/code&gt; counts cycles, not seconds&lt;/td&gt;
&lt;td&gt;Rescale it by the ratio of the task periods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tuning from the sensor datasheet did not give the expected result&lt;/td&gt;
&lt;td&gt;Expected: tune this filter empirically from traces&lt;/td&gt;
&lt;td&gt;Work Tuning steps 2 to 4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a joint with 0.5 kg·m² inertia on a 1 ms task, correcting
every cycle, trusting the sensors and the model equally:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable                          = true
frequencyDivider                = 1
model/inertia                   = 0.5
model/damping                   = 10.0
model/stiffness                 = 0.0
model/viscousFrictionCoefficient  = 0.10
model/coulombFrictionCoefficient_ = 0.10
processNoiseCovariance          = identity
measurementNoiseCovariance      = identity
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then work Tuning step 3. 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;frequencyDivider&lt;/code&gt; ≥ 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked. A value of 0 faults the controller.&lt;/strong&gt; Always set 1 or more&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model/inertia&lt;/code&gt; &amp;gt; 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; Zero makes the estimate and the uncertainty non-numeric. Only a restart clears the estimate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;measurementNoiseCovariance&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; Zero or near-zero makes the filter&amp;rsquo;s internal arithmetic ill-conditioned, and the bad result reaches the estimate unprotected&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Estimate protection&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;The filter protects its &lt;strong&gt;uncertainty&lt;/strong&gt; from non-numeric values but &lt;strong&gt;not its estimate&lt;/strong&gt;, so &lt;code&gt;covarianceMatrix&lt;/code&gt; can look healthy while &lt;code&gt;stateVector&lt;/code&gt; is ruined&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Covariance meaning&lt;/td&gt;
&lt;td&gt;Inherent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;covarianceMatrix&lt;/code&gt; is useful for watching convergence but is &lt;strong&gt;not a true confidence bound&lt;/strong&gt; — the filter&amp;rsquo;s internal gain does not correspond exactly to the model it runs. Tune empirically&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurement count&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Three measurements are required: position, velocity, torque. Supplying fewer leaves zeros that the filter treats as real readings&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model accuracy&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not checked, and cannot be. A wrong model gives a confident, wrong estimate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Estimate 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;stateVectorInit&lt;/code&gt; has no effect. A restart is the only way to clear a wrong estimate&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Estimate across a stop&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not cleared by a stop or a start. A restart of the task resumes the old estimate&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;code&gt;covarianceMatrix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded&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: StateSpace</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/state-space/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/state-space/</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;StateSpace&lt;/code&gt; runs a linear model of a plant from its four matrices. Use it as a
reference model, a soft sensor, or a simulation you can commission against
before the hardware exists. The whole model is configuration: A, B, C, D and
the starting state are all parameters, so a system identified offline drops in
without a code change.&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;inputVector — the model&#39;s input u&amp;quot;]) --&amp;gt; B[&amp;quot;StateSpace&amp;quot;]
    i2([&amp;quot;disturbanceVector — added to the state rate&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;reset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;stateVector — the model state x&amp;quot;])
    B --&amp;gt; o2([&amp;quot;stateDotVector — rate of change of the state&amp;quot;])
    B --&amp;gt; o3([&amp;quot;outputVector — the model output y&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The model is $\dot{x} = Ax + Bu + g$ and $y = Cx + Du$. &lt;strong&gt;The matrices are
continuous-time&lt;/strong&gt;, and the block integrates them at the task rate — do not
supply a discretised model. Stability needs
task period &amp;lt; $2/\lvert\lambda\rvert$ for every eigenvalue $\lambda$ of A: a model with
a 200 Hz pole needs a task period under 1.6 ms. &lt;strong&gt;Nothing checks this&lt;/strong&gt; — see
Limits and errors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every matrix is written as one flat list, filled column by column.&lt;/strong&gt; For a
4×4 A matrix the first four values are its first &lt;em&gt;column&lt;/em&gt;, not its first row.
Getting this wrong gives you a transposed model that is stable, plausible and
wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This block has no enable and no disable.&lt;/strong&gt; It always runs.&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;inputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model input units&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The model&amp;rsquo;s input $u$, one element per input. Length is fixed when the machine is built.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Added &lt;strong&gt;directly to the state rate&lt;/strong&gt;, one element per state. It bypasses the B matrix, so its units are those of $\dot{x}$ and not of an input. Leave it at zero unless you are injecting a state disturbance deliberately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;A rising value restores &lt;code&gt;stateVector&lt;/code&gt; to &lt;code&gt;stateVectorInit&lt;/code&gt; and zeroes &lt;code&gt;stateDotVector&lt;/code&gt;. It is a one-shot — the block clears it. On the reset cycle &lt;code&gt;outputVector&lt;/code&gt; keeps its previous value, so ignore it for one cycle.&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;stateVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units&lt;/td&gt;
&lt;td&gt;The model state $x$, one element per state. This is the model&amp;rsquo;s entire memory. It starts at zero after a controller start — &lt;strong&gt;not at &lt;code&gt;stateVectorInit&lt;/code&gt;&lt;/strong&gt; — and is not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateDotVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model state units per second&lt;/td&gt;
&lt;td&gt;The rate of change of the state on this cycle. It describes the transition &lt;em&gt;out of&lt;/em&gt; the state published alongside it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;model output units&lt;/td&gt;
&lt;td&gt;The model output $y$, one element per output, computed from the state published in the same cycle.&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;matrixA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1/s&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;System dynamics, states × states, &lt;strong&gt;column-major&lt;/strong&gt;. Its eigenvalues are the model&amp;rsquo;s poles and decide both its behaviour and whether it is stable at your task rate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;state units per input unit per second&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Input matrix, states × inputs, column-major. How the input drives each state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per state unit&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Output matrix, outputs × states, column-major. Which states are visible, and how.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrixD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per input unit&lt;/td&gt;
&lt;td&gt;all zeros&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Feed-through matrix, outputs × inputs, column-major. A non-zero value routes the input to the output &lt;strong&gt;in the same cycle&lt;/strong&gt;, with no delay.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVectorInit&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 &lt;code&gt;reset&lt;/code&gt; restores. It is &lt;strong&gt;not&lt;/strong&gt; applied at startup — the state starts at zero regardless.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All five parameters are persistent and survive a controller restart, which is
how a model ships with a machine. &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;Write down your model&amp;rsquo;s dimensions: how many states, inputs and outputs.
Read the lengths of &lt;code&gt;stateVector&lt;/code&gt;, &lt;code&gt;inputVector&lt;/code&gt; and &lt;code&gt;outputVector&lt;/code&gt; from the
parameter tree and confirm they match. They are fixed when the machine is
built and cannot be changed at runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm your matrices are continuous-time.&lt;/strong&gt; If your identification tool
gave you a discrete model, convert it back, or the dynamics will be wrong by
a factor of the task period.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check your task period against your fastest pole: it must be below
$2/\lvert\lambda\rvert$, and comfortably below for accuracy. Divide 2 by the largest
pole magnitude in rad/s.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3 is the check nothing does for you.&lt;/strong&gt; A model that is perfectly
stable on paper will grow without bound here if the task period is too
slow for it, and the only symptom is the state running away.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write &lt;code&gt;matrixA&lt;/code&gt; as a flat list, &lt;strong&gt;column by column&lt;/strong&gt;. For a 2×2 matrix
$\begin{pmatrix} a &amp;amp; b \ c &amp;amp; d\end{pmatrix}$ the list is &lt;code&gt;a, c, b, d&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write &lt;code&gt;matrixB&lt;/code&gt;, &lt;code&gt;matrixC&lt;/code&gt; and &lt;code&gt;matrixD&lt;/code&gt; the same way. Leave &lt;code&gt;matrixD&lt;/code&gt; at
zeros unless your model genuinely feeds through.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;inputVector&lt;/code&gt; to zero and watch &lt;code&gt;stateVector&lt;/code&gt;. With no input and no
disturbance it should decay toward zero from wherever it is. &lt;strong&gt;If it grows,
go back to step 3 or check for a transposed matrix.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply a small step on &lt;code&gt;inputVector&lt;/code&gt; and compare &lt;code&gt;outputVector&lt;/code&gt; against what
your offline model predicts. A mismatch in shape means a transposed matrix;
a mismatch in timescale means step 2.&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. The work is entering the model correctly and
confirming it behaves as designed.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Verify the model offline first. This block reproduces what you give it; it
cannot tell you the model is wrong.&lt;/li&gt;
&lt;li&gt;Check stability at your task rate before anything else, per Setup step 3.
This is the one property that depends on the controller and not on your
model.&lt;/li&gt;
&lt;li&gt;Verify element ordering with a deliberately &lt;strong&gt;asymmetric&lt;/strong&gt; A matrix. A
symmetric test matrix cannot tell a transposed model from a correct one.&lt;/li&gt;
&lt;li&gt;Compare a step response against your offline tool. Match the shape first,
then the timescale. Shape errors are ordering errors; timescale errors are
discrete-versus-continuous errors.&lt;/li&gt;
&lt;li&gt;Watch for slow drift in the amplitude of an oscillatory model. Integrating
at the task rate distorts lightly damped poles, and the faster the pole
relative to the task rate, the worse it gets. If it matters, raise the task
rate.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;reset&lt;/code&gt; between test runs so each starts from the same state, and set
&lt;code&gt;stateVectorInit&lt;/code&gt; to whatever that should be.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/state-space-step-17818e43.svg&#34; alt=&#34;Response to a unit input for three A matrices with a 2 Hz natural frequency:damping 0.2 overshoots to 1.54 and rings, damping 0.7 overshoots to 1.05, anddamping 1.5 rises to 1.0 with no overshoot.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the model&amp;rsquo;s damping off the overshoot on any curve.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVector&lt;/code&gt; grows without bound&lt;/td&gt;
&lt;td&gt;Task period too slow for the model&amp;rsquo;s fastest pole, or a transposed matrix&lt;/td&gt;
&lt;td&gt;Check step 3 of Setup; then verify element ordering with an asymmetric matrix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything reads zero&lt;/td&gt;
&lt;td&gt;Expected with unconfigured matrices — they all default to zero&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;matrixA&lt;/code&gt; through &lt;code&gt;matrixD&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response has the right timescale but the wrong shape&lt;/td&gt;
&lt;td&gt;A matrix was written row by row instead of column by column&lt;/td&gt;
&lt;td&gt;Rewrite it column-major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The response is far faster or slower than your offline model&lt;/td&gt;
&lt;td&gt;Discretised matrices were supplied, but this block wants continuous-time ones&lt;/td&gt;
&lt;td&gt;Convert back to continuous time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output moves the instant the input does&lt;/td&gt;
&lt;td&gt;Expected: a non-zero &lt;code&gt;matrixD&lt;/code&gt; feeds the input straight through&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;matrixD&lt;/code&gt; to zeros if your model has no feed-through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An oscillatory model slowly gains amplitude&lt;/td&gt;
&lt;td&gt;Expected: integrating at the task rate distorts lightly damped poles&lt;/td&gt;
&lt;td&gt;Raise the task rate, or add damping to the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stateVector&lt;/code&gt; did not start at &lt;code&gt;stateVectorInit&lt;/code&gt; after a restart&lt;/td&gt;
&lt;td&gt;Expected: the state starts at zero, and &lt;code&gt;stateVectorInit&lt;/code&gt; is only what &lt;code&gt;reset&lt;/code&gt; restores&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; after every start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputVector&lt;/code&gt; disagrees with &lt;code&gt;stateVector&lt;/code&gt; for one cycle after a reset&lt;/td&gt;
&lt;td&gt;Expected: the output is not recomputed on a reset cycle&lt;/td&gt;
&lt;td&gt;Ignore the output for one cycle after a reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt; had to be written again for a second reset&lt;/td&gt;
&lt;td&gt;Expected: it is a one-shot and the block clears it&lt;/td&gt;
&lt;td&gt;Write it true again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbanceVector&lt;/code&gt; has a much larger effect than expected&lt;/td&gt;
&lt;td&gt;Expected: it adds to the state rate directly and bypasses &lt;code&gt;matrixB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scale it in state-rate units, not input units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The simulation kept running after the controller was stopped and started&lt;/td&gt;
&lt;td&gt;Expected: the state is not cleared by a stop&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; after each start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A value went non-numeric and stayed there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached a matrix or an input&lt;/td&gt;
&lt;td&gt;Fix the source, then pulse &lt;code&gt;reset&lt;/code&gt; — that clears the state&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 2 Hz second-order model with 0.7 damping, two states,
one input, one output, on a 1 ms task. States are position and velocity:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;matrixA         = 0, -157.91, 1, -17.59
matrixB         = 0, 157.91
matrixC         = 1, 0
matrixD         = 0
stateVectorInit = 0, 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note &lt;code&gt;matrixA&lt;/code&gt; is column-major: the four values are row 1 column 1, row 2
column 1, row 1 column 2, row 2 column 2 — in that order.&lt;/p&gt;
&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task period against the model&amp;rsquo;s poles&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; The task period must stay below 2 divided by the largest pole magnitude in rad/s, and the eigenvalues are never computed. Too slow a task period makes the state grow without bound, with no warning and no flag. Only &lt;code&gt;reset&lt;/code&gt; or a restart clears it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix 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 model&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model time domain&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Continuous-time only. There is no discrete-time mode, whatever else you may have read&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix 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 from the parameter tree to discover them&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;code&gt;outputVector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. Bound them downstream if a consumer needs a limit&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup state&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The state starts at &lt;strong&gt;zero&lt;/strong&gt;, not at &lt;code&gt;stateVectorInit&lt;/code&gt;. Pulse &lt;code&gt;reset&lt;/code&gt; to apply the initial state&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt; restores &lt;code&gt;stateVectorInit&lt;/code&gt;. There is no way to reset to zero from the parameter tree unless &lt;code&gt;stateVectorInit&lt;/code&gt; is zero&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: Integrator</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator/</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;Integrator&lt;/code&gt; accumulates its input over time, with a limiter on each side: the
input can be bounded before it is integrated, and the output after. It is the
building block the library makes motion from — an acceleration integrated into
a velocity, a velocity into a position.&lt;/p&gt;
&lt;p&gt;Two things set it apart from a plain accumulator. It has a &lt;strong&gt;throttle for
approaching a singularity&lt;/strong&gt;: as &lt;code&gt;inputLimiterScaleFactor&lt;/code&gt; falls, motion is
scaled down — but &lt;strong&gt;only while it is falling&lt;/strong&gt;, so an operator can always jog
back out. And its output limiter can be told to &lt;strong&gt;hold the output where it is&lt;/strong&gt;
rather than pulling it back to the bound.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — the signal to integrate&amp;quot;]) --&amp;gt; B[&amp;quot;Integrator&amp;quot;]
    i2([&amp;quot;reference — what a reset restores&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;reset&amp;quot;]) --&amp;gt; B
    i4([&amp;quot;inputLimiterScaleFactor&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the integral&amp;quot;])
    B --&amp;gt; o2([&amp;quot;inputLimiterIsActive — per channel&amp;quot;])
    B --&amp;gt; o3([&amp;quot;outputLimiterIsActive — per channel&amp;quot;])
    B --&amp;gt; o4([&amp;quot;anyInputLimiterIsActive&amp;quot;])
    B --&amp;gt; o5([&amp;quot;anyOutputLimiterIsActive&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;output&lt;/code&gt; grows by &lt;code&gt;input&lt;/code&gt; × task period each cycle. The two &lt;code&gt;…IsActive&lt;/code&gt;
outputs are &lt;strong&gt;+1 at the upper bound, −1 at the lower and 0 when inactive&lt;/strong&gt;,
so they tell you which side is limiting, per channel. The two &lt;code&gt;any…&lt;/code&gt; outputs
are plain true/false across all channels.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The scale factor does two different things&lt;/strong&gt; depending on whether that
channel&amp;rsquo;s input limiter is on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input limiter on&lt;/strong&gt; — it scales the &lt;strong&gt;bounds&lt;/strong&gt;, symmetrically, always.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input limiter off&lt;/strong&gt; — it scales the &lt;strong&gt;input itself&lt;/strong&gt;, but &lt;strong&gt;only while the
factor is falling&lt;/strong&gt;. Once it starts rising the scaling stops, so motion away
from the trouble is never throttled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Both limiters are off by default&lt;/strong&gt;, and so the block is a plain integrator
until you enable them. &lt;strong&gt;The output is not cleared when the controller starts&lt;/strong&gt;
— it resumes from wherever it was.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to integrate. One element per channel. &lt;strong&gt;A reset writes zero here&lt;/strong&gt;, so a trace shows zero on the reset cycle.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;What the output is set to on a reset, per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;A rising value sets every channel&amp;rsquo;s output to its &lt;code&gt;reference&lt;/code&gt; and zeroes &lt;code&gt;input&lt;/code&gt;. It is a one-shot — the block clears it. Hold it true to pin the output at the reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterScaleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0 – 1 in practice&lt;/td&gt;
&lt;td&gt;Scales either the input bounds or the input itself — see above. &lt;strong&gt;A single value for the whole block&lt;/strong&gt;, even though the limiter enables are per channel. Negative values are used as their magnitude.&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;signal unit&lt;/td&gt;
&lt;td&gt;The integral, one element per channel. &lt;strong&gt;Not cleared by a controller stop or start&lt;/strong&gt; — pulse &lt;code&gt;reset&lt;/code&gt; if you need it at a known value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Per channel: &lt;strong&gt;+1&lt;/strong&gt; at the upper bound, &lt;strong&gt;−1&lt;/strong&gt; at the lower, &lt;strong&gt;0&lt;/strong&gt; inactive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Same encoding for the output limiter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anyInputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True while any channel&amp;rsquo;s input limiter is active.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;anyOutputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True while any channel&amp;rsquo;s output limiter is active.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterEnable&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;per channel&lt;/td&gt;
&lt;td&gt;Bounds the input before integration. Off by default — and &lt;strong&gt;switching it off is what enables the singularity throttle&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;signal unit per second&lt;/td&gt;
&lt;td&gt;−1.0&lt;/td&gt;
&lt;td&gt;below the max, per channel&lt;/td&gt;
&lt;td&gt;Lower input bound, scaled by &lt;code&gt;inputLimiterScaleFactor&lt;/code&gt;. Swapped with the max if inverted.&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;signal unit per second&lt;/td&gt;
&lt;td&gt;+1.0&lt;/td&gt;
&lt;td&gt;above the min, per channel&lt;/td&gt;
&lt;td&gt;Upper input bound, scaled the same way.&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;&lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;per channel&lt;/td&gt;
&lt;td&gt;Bounds the integral. Off by default, so the integral is unbounded until you set it.&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;signal unit&lt;/td&gt;
&lt;td&gt;−1.0&lt;/td&gt;
&lt;td&gt;below the max, per channel&lt;/td&gt;
&lt;td&gt;&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;signal unit&lt;/td&gt;
&lt;td&gt;+1.0&lt;/td&gt;
&lt;td&gt;above the min, per channel&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputLimiterClampDisable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;per channel&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Changes what hitting the bound means.&lt;/strong&gt; False clamps the output onto the bound. True leaves it where it is and only forbids moving &lt;strong&gt;further&lt;/strong&gt; past — so an output already outside is not yanked back.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All seven are persistent and survive a restart, and all are &lt;strong&gt;per channel&lt;/strong&gt;.
&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;Link &lt;code&gt;input&lt;/code&gt; from whatever you are integrating, and &lt;code&gt;reference&lt;/code&gt; from the
value a reset should restore — often the machine&amp;rsquo;s measured position.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave both limiter enables false for a first pass. The block is then a plain
integrator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pulse &lt;code&gt;reset&lt;/code&gt; and confirm every channel&amp;rsquo;s &lt;code&gt;output&lt;/code&gt; jumps to its
&lt;code&gt;reference&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed a known constant into &lt;code&gt;input&lt;/code&gt; and confirm &lt;code&gt;output&lt;/code&gt; ramps at that rate.&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 the real travel, then set
&lt;code&gt;outputLimiterEnable&lt;/code&gt; true. Drive into a bound and confirm
&lt;code&gt;outputLimiterIsActive&lt;/code&gt; reads +1 or −1 for that channel.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 stops the integral where you tell it to.&lt;/strong&gt; If the output is
already outside the band when you enable the limiter, it will &lt;strong&gt;jump&lt;/strong&gt; to
the bound — unless you set &lt;code&gt;outputLimiterClampDisable&lt;/code&gt;, which holds it
instead. Enable the limiter with the output inside the band.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only if you need the singularity throttle: leave &lt;code&gt;inputLimiterEnable&lt;/code&gt; false
and link &lt;code&gt;inputLimiterScaleFactor&lt;/code&gt; from whatever measures how close the
machine is to trouble.&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;Decide per channel whether you want the &lt;strong&gt;input limiter&lt;/strong&gt; or the
&lt;strong&gt;throttle&lt;/strong&gt;. They are alternatives, selected by &lt;code&gt;inputLimiterEnable&lt;/code&gt;, and
they behave quite differently.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;input limiter&lt;/strong&gt; when the input has a hard rate bound that always
applies. The scale factor then narrows those bounds symmetrically.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;throttle&lt;/strong&gt; when a supervisor computes a closeness measure — a
manipulability figure near a singularity, say — and motion should fade out
as it falls. Because the throttle releases as soon as the factor rises,
motion &lt;strong&gt;out&lt;/strong&gt; of the region is never slowed.&lt;/li&gt;
&lt;li&gt;Set the output limiter from the real travel, not from what the signal
happens to do.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;outputLimiterClampDisable&lt;/code&gt; deliberately. Clamping is right for a
bound the output must sit exactly on; holding is right where being pulled
back to the bound would itself be a disturbance.&lt;/li&gt;
&lt;li&gt;Pulse &lt;code&gt;reset&lt;/code&gt; after every controller start if the integral must begin from a
known value.&lt;/li&gt;
&lt;li&gt;Nothing here needs re-checking after a task-rate change.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/integrator-throttle-eff414c8.svg&#34; alt=&#34;Integrated output as the scale factor dips to 0.2 and recovers: with thelimiter off the output is throttled only while the factor falls and runs freelyas it rises, and with the limiter on it is clipped in bothdirections.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the asymmetry off the gap between the two output curves during the rise.&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 output resumed from an old value after a restart&lt;/td&gt;
&lt;td&gt;Expected: the integral is not cleared at start&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; after every start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output drifts over a long session&lt;/td&gt;
&lt;td&gt;Expected: it is an integrator with no output bound unless you set one&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;outputLimiterEnable&lt;/code&gt; and its bounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output jumped to the bound when the limiter was enabled&lt;/td&gt;
&lt;td&gt;Expected: it was already outside the band&lt;/td&gt;
&lt;td&gt;Enable with the output inside, or set &lt;code&gt;outputLimiterClampDisable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is stuck outside its bound&lt;/td&gt;
&lt;td&gt;Expected with &lt;code&gt;outputLimiterClampDisable&lt;/code&gt; set: it holds rather than pulling back&lt;/td&gt;
&lt;td&gt;Clear that parameter if you want it clamped onto the bound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output moves differently on the way down than on the way up for the same scale factor&lt;/td&gt;
&lt;td&gt;Expected: the throttle applies only while the factor is falling&lt;/td&gt;
&lt;td&gt;This is what lets you jog back out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The throttle does nothing&lt;/td&gt;
&lt;td&gt;That channel&amp;rsquo;s &lt;code&gt;inputLimiterEnable&lt;/code&gt; is true, so the factor scales the &lt;strong&gt;bounds&lt;/strong&gt; instead&lt;/td&gt;
&lt;td&gt;Switch the input limiter off to get the throttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The scale factor throttles some channels and scales bounds on others&lt;/td&gt;
&lt;td&gt;Expected: the factor is global and the limiter enables are per channel&lt;/td&gt;
&lt;td&gt;Make the enables consistent across channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The throttle stayed on after the factor stopped falling&lt;/td&gt;
&lt;td&gt;Expected: it releases only on a &lt;strong&gt;rising&lt;/strong&gt; factor, not a steady one&lt;/td&gt;
&lt;td&gt;Raise the factor slightly to release it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt; reads zero on a trace&lt;/td&gt;
&lt;td&gt;You are looking at a reset cycle — a reset zeroes the input&lt;/td&gt;
&lt;td&gt;Read it on a non-reset cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A limit reads back swapped&lt;/td&gt;
&lt;td&gt;Expected: an inverted pair is corrected&lt;/td&gt;
&lt;td&gt;Write the lower value to the min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt; had to be written again for a second reset&lt;/td&gt;
&lt;td&gt;Expected: it is a one-shot&lt;/td&gt;
&lt;td&gt;Write it true again, or hold it to pin the output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One channel limits and the others do not&lt;/td&gt;
&lt;td&gt;Expected: every limit and both enables are per channel&lt;/td&gt;
&lt;td&gt;Check each element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The integral went non-numeric and stayed there&lt;/td&gt;
&lt;td&gt;A non-numeric input accumulated into the output&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; with a finite &lt;code&gt;reference&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I cannot tell whether the throttle is engaged&lt;/td&gt;
&lt;td&gt;The block does not publish it&lt;/td&gt;
&lt;td&gt;Compare the scale factor&amp;rsquo;s direction between cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for integrating a velocity into a position bounded to ±0.5,
with the throttle available:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;inputLimiterEnable        = false
outputLimiterEnable       = true
outputLimiterMin          = -0.5
outputLimiterMax          = 0.5
outputLimiterClampDisable = 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;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputLimiterMin&lt;/code&gt;/&lt;code&gt;Max&lt;/code&gt; × &lt;code&gt;inputLimiterScaleFactor&lt;/code&gt;, while &lt;code&gt;inputLimiterEnable&lt;/code&gt; is set&lt;/td&gt;
&lt;td&gt;Clamped before integration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputLimiterIsActive&lt;/code&gt; (+1/−1/0), &lt;code&gt;anyInputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;, with the limiter off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputLimiterScaleFactor&lt;/code&gt;, &lt;strong&gt;only while it is falling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scaled proportionally. Never scaled while the factor rises&lt;/td&gt;
&lt;td&gt;Not reported — the latch is not published&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;outputLimiterMin&lt;/code&gt;/&lt;code&gt;Max&lt;/code&gt;, while &lt;code&gt;outputLimiterEnable&lt;/code&gt; is set&lt;/td&gt;
&lt;td&gt;Clamped onto the bound, or held where it is when &lt;code&gt;outputLimiterClampDisable&lt;/code&gt; is set. &lt;strong&gt;Unbounded when the limiter is off, which is the default&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputLimiterIsActive&lt;/code&gt;, &lt;code&gt;anyOutputLimiterIsActive&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limit ordering&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;An inverted min/max pair is &lt;strong&gt;swapped in place&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silently; read both back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputLimiterScaleFactor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sign forced&lt;/td&gt;
&lt;td&gt;Used as its magnitude&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throttle state&lt;/td&gt;
&lt;td&gt;Not published&lt;/td&gt;
&lt;td&gt;Whether the throttle is engaged cannot be read from the parameter tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integral state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restored from &lt;code&gt;reference&lt;/code&gt;. &lt;strong&gt;Not cleared by a controller stop or start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-numeric input&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Accumulates and latches. &lt;code&gt;reset&lt;/code&gt; clears it if &lt;code&gt;reference&lt;/code&gt; is finite&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed once the controller starts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Docs: IntegratorRot3D</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator-rot-3d/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator-rot-3d/</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;IntegratorRot3D&lt;/code&gt; integrates an angular velocity into an &lt;strong&gt;orientation&lt;/strong&gt;. It is
the rotational counterpart of &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator/&#34;&gt;&lt;code&gt;Integrator&lt;/code&gt;&lt;/a&gt;: where that block
accumulates a number, this one composes rotations, which cannot be done by
adding three angles together.&lt;/p&gt;
&lt;p&gt;The orientation is held as a quaternion and re-normalised every cycle, so it
stays a valid rotation however long the controller runs — no gimbal lock and no
drift out of the valid set.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — angular velocity, three values&amp;quot;]) --&amp;gt; B[&amp;quot;IntegratorRot3D&amp;quot;]
    i2([&amp;quot;reset&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;manipulabilityGain&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — orientation quaternion, four values&amp;quot;])
    B --&amp;gt; o2([&amp;quot;reference — the reset target&amp;quot;])
    B --&amp;gt; o3([&amp;quot;pqrLocal — applied rate, body frame&amp;quot;])
    B --&amp;gt; o4([&amp;quot;pqrGlobal — applied rate, world frame&amp;quot;])
    B --&amp;gt; o5([&amp;quot;absVelOut — rate magnitude&amp;quot;])
    B --&amp;gt; o6([&amp;quot;absVelDotOut — its rate of change&amp;quot;])
    B --&amp;gt; o7([&amp;quot;absVelLimiterActive&amp;quot;])
    B --&amp;gt; o8([&amp;quot;absAccLimiterActive&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The limits act on the &lt;strong&gt;magnitude&lt;/strong&gt; of the angular rate, not on each axis.
When a limit bites, all three components are scaled by the same factor, so
the rotation &lt;strong&gt;slows without changing its axis&lt;/strong&gt;. &lt;code&gt;absVelMax&lt;/code&gt; bounds the
rate&amp;rsquo;s magnitude and &lt;code&gt;absAccMax&lt;/code&gt; how fast that magnitude may change.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;manipulabilityGain&lt;/code&gt; does two different things&lt;/strong&gt; depending on
&lt;code&gt;absVelLimiterOn&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On&lt;/strong&gt; — it scales &lt;code&gt;absVelMax&lt;/code&gt;, always, in both directions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Off&lt;/strong&gt; — it scales the rate itself, but &lt;strong&gt;only while the gain is falling&lt;/strong&gt;.
Motion &lt;strong&gt;out&lt;/strong&gt; of the region that caused the throttle is never slowed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The acceleration limit is deliberately relaxed while slowing toward a
singularity.&lt;/strong&gt; When the throttle demands a lower rate, the block allows
whatever deceleration that needs — the limit only constrains speeding up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;reference&lt;/code&gt; is an output, not an input.&lt;/strong&gt; The reset target can only be set
from an application, not from the parameter tree — see Limits and errors.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The angular velocity, three values. &lt;strong&gt;&lt;code&gt;inputAsLocal&lt;/code&gt; decides whether these are in the body frame or the world frame&lt;/strong&gt; — getting that wrong produces plausible motion about the wrong axis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Sets &lt;code&gt;output&lt;/code&gt; back to &lt;code&gt;reference&lt;/code&gt; and zeroes the input.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;manipulabilityGain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0 – 1 in practice&lt;/td&gt;
&lt;td&gt;How much rotation is allowed, typically from a measure of how close the machine is to a singularity. Negative values are used as their magnitude.&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;-&lt;/td&gt;
&lt;td&gt;The orientation, as a four-value quaternion. Always normalised. &lt;strong&gt;Not cleared when the controller starts&lt;/strong&gt; — it resumes from the previous run&amp;rsquo;s attitude.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The quaternion a reset restores. &lt;strong&gt;Readable only&lt;/strong&gt; — it cannot be written from the parameter tree.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqrLocal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;The rate actually applied, expressed in the &lt;strong&gt;body&lt;/strong&gt; frame.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqrGlobal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;The same rate in the &lt;strong&gt;world&lt;/strong&gt; frame. Compare the two to confirm the frame switch is doing what you expect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelOut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;The magnitude of the applied rate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelDotOut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s²&lt;/td&gt;
&lt;td&gt;How fast that magnitude is changing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelLimiterActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True while the rate magnitude is being limited.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absAccLimiterActive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True while its rate of change is being limited.&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;inputAsLocal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True means &lt;code&gt;input&lt;/code&gt; is in the body frame and the block rotates it into the world frame. False means it is already in the world frame.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelLimiterOn&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 the rate magnitude at &lt;code&gt;absVelMax&lt;/code&gt; × &lt;code&gt;manipulabilityGain&lt;/code&gt;. &lt;strong&gt;Switching it off is what enables the throttle instead.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;Maximum rate magnitude. &lt;strong&gt;Not checked.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absAccLimiterOn&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 how fast the rate magnitude may change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absAccMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s²&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;above 0&lt;/td&gt;
&lt;td&gt;That bound. &lt;strong&gt;Not checked, and a negative value is unsafe&lt;/strong&gt; — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All five are persistent and survive a restart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One sub-tree exists below this block&lt;/strong&gt;, &lt;code&gt;rateLimiter3D&lt;/code&gt;, which performs the
acceleration limit. &lt;strong&gt;Do not configure it&lt;/strong&gt; — this block overwrites its
settings every cycle. Use &lt;code&gt;absAccMax&lt;/code&gt; and &lt;code&gt;absAccLimiterOn&lt;/code&gt; instead.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Decide which frame your angular velocity is in and set &lt;code&gt;inputAsLocal&lt;/code&gt; to
match. A body-frame rate fed as a world-frame one rotates about the wrong
axis, and the result looks like plausible motion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;reference&lt;/code&gt; from your application to the orientation a reset should
restore. It cannot be set from the parameter tree.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pulse &lt;code&gt;reset&lt;/code&gt; and confirm &lt;code&gt;output&lt;/code&gt; matches &lt;code&gt;reference&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Command a steady rate about one axis with both limiters off, and confirm the
orientation rotates about that axis at the expected rate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compare &lt;code&gt;pqrLocal&lt;/code&gt; against &lt;code&gt;pqrGlobal&lt;/code&gt;. With the machine at its reference
orientation they will agree; as it rotates they diverge, and that divergence
confirms the frame handling.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 is the check that the frames are right.&lt;/strong&gt; If the two never
diverge as the machine rotates, the frame rotation is not happening and
&lt;code&gt;inputAsLocal&lt;/code&gt; is probably wrong.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;absVelMax&lt;/code&gt; and switch &lt;code&gt;absVelLimiterOn&lt;/code&gt; on. Command a rate above the
limit and confirm &lt;code&gt;absVelLimiterActive&lt;/code&gt; goes true and &lt;code&gt;absVelOut&lt;/code&gt; settles at
the limit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only if you need the singularity throttle: switch &lt;code&gt;absVelLimiterOn&lt;/code&gt; &lt;strong&gt;off&lt;/strong&gt;
and link &lt;code&gt;manipulabilityGain&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;Set &lt;code&gt;absVelMax&lt;/code&gt; from the machine&amp;rsquo;s rated rotational speed — it is a
magnitude, so it applies equally to a rotation about one axis and a
combined one.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;absAccMax&lt;/code&gt; from what the structure can take. It bounds how quickly the
rotation speeds up; slowing down toward a singularity is deliberately
exempt.&lt;/li&gt;
&lt;li&gt;Choose per installation whether you want the &lt;strong&gt;limiter&lt;/strong&gt; or the
&lt;strong&gt;throttle&lt;/strong&gt;. They are alternatives selected by &lt;code&gt;absVelLimiterOn&lt;/code&gt;, exactly
as in &lt;a href=&#34;/docs/developing-control-applications/control-blocks/estimation-and-maths/integrator/&#34;&gt;&lt;code&gt;Integrator&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use the throttle when a supervisor computes a closeness measure and rotation
should fade out as it falls. Rotation away from the trouble is never slowed,
which is the point.&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;absVelOut&lt;/code&gt; and &lt;code&gt;absVelDotOut&lt;/code&gt; while tuning. They are the two
quantities the limits act on.&lt;/li&gt;
&lt;li&gt;Nothing here needs re-checking after a task-rate change.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/integrator-rot-3d-throttle-cd9eea44.svg&#34; alt=&#34;Integrated rotation angle as manipulabilityGain dips to 0.2 and recovers:with the velocity limiter off the rotation is throttled only while the gainfalls, and with it on the rate is limited in bothdirections.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the asymmetry off the gap between the two curves during the rise.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The machine rotates about the wrong axis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputAsLocal&lt;/code&gt; does not match the frame your rate is in&lt;/td&gt;
&lt;td&gt;Switch it and re-check step 5 of Setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pqrLocal&lt;/code&gt; and &lt;code&gt;pqrGlobal&lt;/code&gt; never diverge&lt;/td&gt;
&lt;td&gt;The frame rotation is not being applied&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;inputAsLocal&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The orientation resumed from an old attitude after a restart&lt;/td&gt;
&lt;td&gt;Expected: it is not cleared at start&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; after every start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I cannot set the reset target&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;reference&lt;/code&gt; is an output and is settable only from an application&lt;/td&gt;
&lt;td&gt;Ask the application to set it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotation is slower on the way in than on the way out for the same gain&lt;/td&gt;
&lt;td&gt;Expected: the throttle applies only while the gain is falling&lt;/td&gt;
&lt;td&gt;This is what lets the machine rotate back out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The throttle does nothing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;absVelLimiterOn&lt;/code&gt; is true, so the gain scales &lt;code&gt;absVelMax&lt;/code&gt; instead&lt;/td&gt;
&lt;td&gt;Switch the limiter off to get the throttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The acceleration limit seems to be ignored while slowing down&lt;/td&gt;
&lt;td&gt;Expected: it is relaxed while braking toward a singularity, so the machine may always reach the throttled rate&lt;/td&gt;
&lt;td&gt;This is deliberate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One axis is limited and the others are not&lt;/td&gt;
&lt;td&gt;Not possible — the limits act on the magnitude, so all three scale together&lt;/td&gt;
&lt;td&gt;Check what consumes the output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The rotation axis changed when the limit bit&lt;/td&gt;
&lt;td&gt;Not possible in this block&lt;/td&gt;
&lt;td&gt;Check what follows it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuring the &lt;code&gt;rateLimiter3D&lt;/code&gt; sub-tree had no effect&lt;/td&gt;
&lt;td&gt;Expected: this block overwrites its settings every cycle&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;absAccMax&lt;/code&gt; and &lt;code&gt;absAccLimiterOn&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The rotation ran away or reversed&lt;/td&gt;
&lt;td&gt;A negative &lt;code&gt;absAccMax&lt;/code&gt; reaches the internal rate limiter, which then drives away from its target&lt;/td&gt;
&lt;td&gt;Set it positive, then pulse &lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The orientation went non-numeric and stayed there&lt;/td&gt;
&lt;td&gt;A non-numeric rate poisoned the quaternion&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; with a finite &lt;code&gt;reference&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The quaternion drifted out of normalisation&lt;/td&gt;
&lt;td&gt;Not possible — it is re-normalised every cycle&lt;/td&gt;
&lt;td&gt;Check what consumes it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a wrist limited to 1 rad/s, with the throttle available:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;inputAsLocal    = true
absVelLimiterOn = false
absVelMax       = 1.0
absAccLimiterOn = true
absAccMax       = 10.0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rate magnitude&lt;/td&gt;
&lt;td&gt;&lt;code&gt;absVelMax&lt;/code&gt; × &lt;code&gt;manipulabilityGain&lt;/code&gt;, while &lt;code&gt;absVelLimiterOn&lt;/code&gt; is set&lt;/td&gt;
&lt;td&gt;Scaled down uniformly, &lt;strong&gt;preserving the axis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;absVelLimiterActive&lt;/code&gt;, &lt;code&gt;absVelOut&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate magnitude, limiter off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;manipulabilityGain&lt;/code&gt;, &lt;strong&gt;only while it is falling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scaled proportionally. Never scaled while the gain rises&lt;/td&gt;
&lt;td&gt;Not reported — the latch is not published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate of change of the magnitude&lt;/td&gt;
&lt;td&gt;&lt;code&gt;absAccMax&lt;/code&gt;, while &lt;code&gt;absAccLimiterOn&lt;/code&gt; is set&lt;/td&gt;
&lt;td&gt;Bounded — &lt;strong&gt;except while slowing toward a singularity, where whatever deceleration the throttle demands is allowed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;absAccLimiterActive&lt;/code&gt;, &lt;code&gt;absVelDotOut&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;absVelMax&lt;/code&gt;, &lt;code&gt;absAccMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; A negative &lt;code&gt;absAccMax&lt;/code&gt; reaches the internal rate limiter, which then drives the rate &lt;strong&gt;away&lt;/strong&gt; from its target&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Individual axes&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not bounded. Only the magnitude is limited&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orientation validity&lt;/td&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;The quaternion is re-normalised every cycle and stays a valid rotation indefinitely&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not writable&lt;/td&gt;
&lt;td&gt;It is registered as an output, so the reset target is settable only from an application&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orientation state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restored from &lt;code&gt;reference&lt;/code&gt;. &lt;strong&gt;Not cleared by a controller stop or start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rateLimiter3D&lt;/code&gt; sub-tree&lt;/td&gt;
&lt;td&gt;Overwritten&lt;/td&gt;
&lt;td&gt;Its &lt;code&gt;enable&lt;/code&gt; and &lt;code&gt;rateLimit&lt;/code&gt; are written every cycle by this block&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-numeric input&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Poisons the quaternion and latches. &lt;code&gt;reset&lt;/code&gt; clears it if &lt;code&gt;reference&lt;/code&gt; is finite&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;Fixed&lt;/td&gt;
&lt;td&gt;Always three rate inputs and one quaternion output&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: Polynomial</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/polynomial/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/polynomial/</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;Polynomial&lt;/code&gt; evaluates a polynomial in its input:
$y = a_0 + a_1u + a_2u^2 + \dots$. Its usual job is &lt;strong&gt;linearising a sensor&lt;/strong&gt; —
fit a curve to the sensor&amp;rsquo;s error offline, put the coefficients here, and the
reading comes out proportional to the quantity it measures.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — the raw value&amp;quot;]) --&amp;gt; B[&amp;quot;Polynomial&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — the corrected value&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;coefficients[0]&lt;/code&gt; is the constant term and is always added.
&lt;code&gt;coefficients[1]&lt;/code&gt; multiplies the input, &lt;code&gt;coefficients[2]&lt;/code&gt; the input squared,
and so on up to &lt;code&gt;order&lt;/code&gt;. So an identity correction is &lt;code&gt;order&lt;/code&gt; = 1 with
coefficients &lt;code&gt;0, 1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never write &lt;code&gt;order&lt;/code&gt; larger than the coefficient array.&lt;/strong&gt; The block does not
check it, and a value past the end makes it read memory that is not part of the
array. Find the ceiling with Setup step 2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;order&lt;/code&gt; = 0 gives zero, not the constant term.&lt;/strong&gt; For a fixed offset use
&lt;code&gt;order&lt;/code&gt; = 1 with coefficients &lt;code&gt;offset, 0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disabling this block outputs zero, not the input&lt;/strong&gt; — and it writes zero into
&lt;code&gt;input&lt;/code&gt; too. In a sensor chain that takes everything downstream to zero with
it.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw sensor unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value to correct. A single value, not an array. &lt;strong&gt;The block writes zero here on every disabled cycle&lt;/strong&gt;, so a trace reads zero while it is off.&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;corrected unit&lt;/td&gt;
&lt;td&gt;The polynomial evaluated at &lt;code&gt;input&lt;/code&gt;. &lt;strong&gt;Zero — not the input — while disabled&lt;/strong&gt;, and zero whenever &lt;code&gt;order&lt;/code&gt; is 0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. It does &lt;strong&gt;not&lt;/strong&gt; mean the block is producing output: &lt;code&gt;order&lt;/code&gt; = 0 gives zero while this still reads true.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;parameters&#34;&gt;Parameters&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False makes the output zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 up to the array length minus 1&lt;/td&gt;
&lt;td&gt;How many powers of the input to use. &lt;strong&gt;Not checked — a value past the array length reads out of bounds.&lt;/strong&gt; 0 gives zero output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coefficients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mixed&lt;/td&gt;
&lt;td&gt;0 in every element&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The coefficients, constant term first. Element 1 multiplies the input, element 2 the input squared, and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both persistent parameters survive a controller restart, which is how a
calibration ships with a machine. &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;Fit the polynomial offline against known reference values. Two or three
terms is usually enough; a high order fits noise rather than the sensor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Find the coefficient array&amp;rsquo;s length&lt;/strong&gt; by reading it back from the
parameter tree. &lt;code&gt;order&lt;/code&gt; must stay at least one below it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write &lt;code&gt;order&lt;/code&gt; &lt;strong&gt;first&lt;/strong&gt;, then &lt;code&gt;coefficients&lt;/code&gt;. Writing the order afterwards
from an application clears the coefficients you just set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write the coefficients with the &lt;strong&gt;constant term in element 0&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply a known reference value to the sensor and check &lt;code&gt;output&lt;/code&gt; reads the
right corrected value. Repeat at three or four points across the range —
one point cannot tell a good fit from a bad one.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 is the only check that the fit is right.&lt;/strong&gt; A polynomial that is
correct at the point you tested and wrong everywhere else looks exactly
like a working correction until the machine moves.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm &lt;code&gt;output&lt;/code&gt; matches &lt;code&gt;input&lt;/code&gt; at the point where your correction should
be neutral.&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 at runtime — the coefficients are a calibration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take measurements across the &lt;strong&gt;whole&lt;/strong&gt; working range, not just the middle.
A polynomial is worst at the ends.&lt;/li&gt;
&lt;li&gt;Use the lowest order that fits. Each extra term makes the curve wilder
outside the range you measured.&lt;/li&gt;
&lt;li&gt;Check the behaviour beyond your measured range. A cubic that fits well from
0 to 10 can be badly wrong at 15, and there is no limiter here.&lt;/li&gt;
&lt;li&gt;Re-check the fit after any change to the sensor or its mounting.&lt;/li&gt;
&lt;li&gt;Nothing here depends on the task rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/polynomial-curve-ae95ce50.svg&#34; alt=&#34;Output against input for three coefficient sets: an order-1 set is a straightline, an order-2 set curves upward, and an order-3 set bends back down at theends.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the correction as the vertical distance from the straight line.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The output is zero and &lt;code&gt;isEnabled&lt;/code&gt; reads true&lt;/td&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;order&lt;/code&gt; to 1 or more; for a constant, use &lt;code&gt;order&lt;/code&gt; 1 with coefficients &lt;code&gt;offset, 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is zero and the block is disabled&lt;/td&gt;
&lt;td&gt;Expected: it outputs zero rather than passing through&lt;/td&gt;
&lt;td&gt;Enable it; if a sensor chain went dead, this is why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt; reads zero on a trace&lt;/td&gt;
&lt;td&gt;You are looking at a disabled cycle — the block zeroes its own input&lt;/td&gt;
&lt;td&gt;Enable it before judging the link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The coefficients went to zero after setting the order from an application&lt;/td&gt;
&lt;td&gt;Setting the order clears the coefficient array&lt;/td&gt;
&lt;td&gt;Write &lt;code&gt;order&lt;/code&gt; first, then the coefficients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The correction is right at one point and wrong elsewhere&lt;/td&gt;
&lt;td&gt;The fit was made from too few points&lt;/td&gt;
&lt;td&gt;Re-fit across the whole range, per Tuning step 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output goes wildly wrong beyond the measured range&lt;/td&gt;
&lt;td&gt;Expected: a polynomial diverges outside where it was fitted&lt;/td&gt;
&lt;td&gt;Lower the order, or bound the output downstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is very large or non-numeric for a large input&lt;/td&gt;
&lt;td&gt;A high power of a large input overflows&lt;/td&gt;
&lt;td&gt;Lower the order, or scale the input first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing seems to change when I raise the order&lt;/td&gt;
&lt;td&gt;The higher coefficients are zero&lt;/td&gt;
&lt;td&gt;Write them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The controller behaves strangely after writing a large &lt;code&gt;order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;It was written past the coefficient array&amp;rsquo;s length, which reads out of bounds&lt;/td&gt;
&lt;td&gt;Keep &lt;code&gt;order&lt;/code&gt; below the array length; restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The correction is inverted&lt;/td&gt;
&lt;td&gt;The coefficient signs are wrong, or the constant term is in the wrong element&lt;/td&gt;
&lt;td&gt;Element 0 is the constant term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A non-numeric input produced a non-numeric output that then cleared&lt;/td&gt;
&lt;td&gt;Expected: the block holds no state and recovers at once&lt;/td&gt;
&lt;td&gt;Fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need this on several channels&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per channel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for an identity correction, which is what the block ships
with:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enable       = true
order        = 1
coefficients = 0.0, 1.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A second-order correction with a small offset:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;order        = 2
coefficients = -0.05, 1.02, 0.003
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt; below the coefficient array&amp;rsquo;s length&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked. A larger value reads past the end of the array&lt;/strong&gt;, which is undefined and can crash or produce nonsense. Discover the length by reading &lt;code&gt;coefficients&lt;/code&gt; back&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt; = 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;The output is &lt;strong&gt;zero&lt;/strong&gt;, not the constant term&lt;/td&gt;
&lt;td&gt;Not reported; &lt;code&gt;isEnabled&lt;/code&gt; still reads true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. A high order and a large input grow very quickly. Limit it downstream if the consumer needs a bound&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fit quality&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Not checked, and cannot be. A polynomial that is wrong outside the range you fitted looks correct inside it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disabled behaviour&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Output &lt;strong&gt;zero&lt;/strong&gt;, not a pass-through, and &lt;code&gt;input&lt;/code&gt; is zeroed too&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-numeric input&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Produces a non-numeric output. The block is stateless and recovers immediately&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 channel 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: Lookup</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/lookup/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/lookup/</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;Lookup&lt;/code&gt; maps an input to an output through a table of points, interpolating
straight lines between them. Outside the table the output is held at the
nearest end value — it never extrapolates.&lt;/p&gt;
&lt;p&gt;Use it to linearise a sensor, to schedule a gain against speed, or to describe
any relationship that is easier to measure than to write down.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input&amp;quot;]) --&amp;gt; B[&amp;quot;Lookup&amp;quot;]
    p1([&amp;quot;x&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;y&amp;quot;]) --&amp;gt; B
    p3([&amp;quot;numPoints&amp;quot;]) --&amp;gt; B
    p4([&amp;quot;gain&amp;quot;]) --&amp;gt; B
    p5([&amp;quot;useSortedData&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output&amp;quot;])
    B --&amp;gt; o2([&amp;quot;index&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;x&lt;/code&gt; must increase strictly, left to right.&lt;/strong&gt; If any value is equal to or
less than the one before it, &lt;strong&gt;the output is 0&lt;/strong&gt; — silently, with nothing to
tell you the table was rejected. Since 0 is also a perfectly good output
value, check your table&amp;rsquo;s order first whenever the output reads 0.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;numPoints&lt;/code&gt; defaults to 0, and an unconfigured block outputs 0.&lt;/strong&gt; Set it to
how many of your &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; entries are real.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The table has a fixed maximum size&lt;/strong&gt;, set when the controller is built —
usually 7 points. A larger &lt;code&gt;numPoints&lt;/code&gt; is silently reduced to it, and the
ceiling is not published, so count your points against what you were given.&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;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value to look up. Outside the table the output clamps to the nearest end. A value that is not a valid number &lt;strong&gt;freezes&lt;/strong&gt; the output at its last value.&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 interpolated result, scaled by &lt;code&gt;gain&lt;/code&gt;. Reads &lt;strong&gt;0&lt;/strong&gt; when the table is empty or rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Which table segment was used last. Diagnostic — it is the block&amp;rsquo;s internal search position, not a measurement.&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;numPoints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 to the built-in maximum&lt;/td&gt;
&lt;td&gt;How many entries of &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are real. &lt;strong&gt;At 0 the output is 0.&lt;/strong&gt; At 1 the output is &lt;code&gt;gain × y[0]&lt;/code&gt; whatever the input. &lt;strong&gt;Values above the maximum are silently reduced.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input unit&lt;/td&gt;
&lt;td&gt;zeros&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;strictly increasing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The input values of the table. &lt;strong&gt;Any entry not greater than the one before it makes the output 0.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;zeros&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The output value at each corresponding &lt;code&gt;x&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Multiplies every &lt;code&gt;y&lt;/code&gt; value. Use it to rescale the whole table without editing it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;useSortedData&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;Lets the block remember where it looked last, so a slowly moving input searches less. Makes no difference to the answer. At the usual table sizes it makes no measurable difference to the cost either.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart. &lt;strong&gt;No parameters exist
below this block.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Measure or calculate the relationship you want, at as many points as your
table allows.&lt;/li&gt;
&lt;li&gt;Write the input values into &lt;code&gt;x&lt;/code&gt;, &lt;strong&gt;in increasing order&lt;/strong&gt;, and the matching
output values into &lt;code&gt;y&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;numPoints&lt;/code&gt; to how many entries you filled.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;gain&lt;/code&gt; at 1.&lt;/li&gt;
&lt;li&gt;Sweep &lt;code&gt;input&lt;/code&gt; across the range and confirm &lt;code&gt;output&lt;/code&gt; traces the shape you
expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If &lt;code&gt;output&lt;/code&gt; reads 0 everywhere&lt;/strong&gt;, either &lt;code&gt;numPoints&lt;/code&gt; is 0 or your &lt;code&gt;x&lt;/code&gt;
values are not strictly increasing. Check both.&lt;/li&gt;
&lt;li&gt;Check the ends: below the first &lt;code&gt;x&lt;/code&gt; the output should hold at the first &lt;code&gt;y&lt;/code&gt;,
above the last it should hold at the last.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Put your points where the curve bends. Even spacing wastes points on the
straight sections.&lt;/li&gt;
&lt;li&gt;Always include a point at each end of the range you care about. Outside the
table the output goes flat, and a client tracing an unexpected plateau is
usually looking at a table that stops too early.&lt;/li&gt;
&lt;li&gt;Check the interpolation error at the midpoint of your widest segment — that
is where a piecewise-linear fit is worst.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;gain&lt;/code&gt; to rescale the whole table, for a unit change or a calibration
factor, rather than re-entering every &lt;code&gt;y&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the table is a sensor linearisation, take the measurements at the same
temperature and load you will run at.&lt;/li&gt;
&lt;li&gt;Nothing here depends on the task rate, and both the interpolation and the
clamping are exact.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/lookup-interpolation-c5458303.svg&#34; alt=&#34;Output against input for a five-point table. Between points the output is astraight line; outside the table it holds flat. A table whose x values are notincreasing gives zero everywhere.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The flat sections at each end are the clamping, not a fault.&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 output is 0 everywhere&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; is 0 — the default — or &lt;code&gt;x&lt;/code&gt; is not strictly increasing&lt;/td&gt;
&lt;td&gt;Check both. Nothing reports which&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is 0 and my table looks fine&lt;/td&gt;
&lt;td&gt;Two &lt;code&gt;x&lt;/code&gt; entries are equal, which counts as not increasing&lt;/td&gt;
&lt;td&gt;They must be strictly increasing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is a constant&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; is 1, so the output is always &lt;code&gt;gain × y[0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set it to your real point count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output goes flat at each end&lt;/td&gt;
&lt;td&gt;Expected: the block clamps rather than extrapolating&lt;/td&gt;
&lt;td&gt;Add points further out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only part of my table is used&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; is smaller than the entries you filled&lt;/td&gt;
&lt;td&gt;Raise it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; reads back lower than I set&lt;/td&gt;
&lt;td&gt;It exceeded the built-in maximum and was reduced&lt;/td&gt;
&lt;td&gt;Use fewer points, or a larger instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is scaled wrongly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; multiplies every &lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check it is 1 unless you meant otherwise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output has visible corners&lt;/td&gt;
&lt;td&gt;Expected: the table is straight lines between points&lt;/td&gt;
&lt;td&gt;Add points where the curve bends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output froze&lt;/td&gt;
&lt;td&gt;The input is not a valid number, which holds the last output&lt;/td&gt;
&lt;td&gt;Fix the upstream signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output jumped when I edited the table&lt;/td&gt;
&lt;td&gt;Expected: edits take effect on the next cycle, with no fade&lt;/td&gt;
&lt;td&gt;Edit while the machine is stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index&lt;/code&gt; moves around unexpectedly&lt;/td&gt;
&lt;td&gt;It is the internal search position, not a measurement&lt;/td&gt;
&lt;td&gt;Ignore it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turning on &lt;code&gt;useSortedData&lt;/code&gt; changed the answer&lt;/td&gt;
&lt;td&gt;It should not — it only changes how the block searches&lt;/td&gt;
&lt;td&gt;Report it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need extrapolation beyond the table&lt;/td&gt;
&lt;td&gt;Not possible — the block clamps&lt;/td&gt;
&lt;td&gt;Extend the table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more points&lt;/td&gt;
&lt;td&gt;The maximum is fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild with a larger instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need this on several signals&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a five-point sensor linearisation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;numPoints     = 5
x             = [0.0, 1.0, 2.0, 3.0, 4.0]
y             = &amp;lt;your measured outputs at those inputs&amp;gt;
gain          = 1.0
useSortedData = 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;Interpolation&lt;/td&gt;
&lt;td&gt;The table&lt;/td&gt;
&lt;td&gt;Straight lines between adjacent points, exact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outside the table&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Clamped&lt;/strong&gt; to the first or last &lt;code&gt;y&lt;/code&gt;. There is no extrapolation&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt; ordering&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must be &lt;strong&gt;strictly increasing&lt;/strong&gt; over the first &lt;code&gt;numPoints&lt;/code&gt;. Otherwise the output is &lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not reported&lt;/strong&gt; — 0 is also a valid result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; above the maximum&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silently reduced to the maximum every cycle, so the block never reads past its table&lt;/td&gt;
&lt;td&gt;Read the value back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum point count&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not published.&lt;/strong&gt; Usually 7&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; of 0&lt;/td&gt;
&lt;td&gt;Checked&lt;/td&gt;
&lt;td&gt;Output is 0&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;numPoints&lt;/code&gt; of 1&lt;/td&gt;
&lt;td&gt;Checked&lt;/td&gt;
&lt;td&gt;Output is &lt;code&gt;gain × y[0]&lt;/code&gt; for any input&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. Multiplies every &lt;code&gt;y&lt;/code&gt;, including on the one-point path — &lt;strong&gt;but not on either zero path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. Such an input &lt;strong&gt;holds the previous output&lt;/strong&gt; rather than propagating&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table edits&lt;/td&gt;
&lt;td&gt;The tree&lt;/td&gt;
&lt;td&gt;Take effect on the very next cycle, with no fade or filtering&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 signal 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, ever&lt;/strong&gt;. Every 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: Gain</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/gain/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/gain/</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;Gain&lt;/code&gt; multiplies each channel of its input by a gain and adds an offset. That
is all it does.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The gain defaults to 0, so an unconfigured block outputs nothing.&lt;/strong&gt; It does
&lt;strong&gt;not&lt;/strong&gt; pass its input through. Set &lt;code&gt;gain&lt;/code&gt; to 1 on every channel before you
expect a signal out of it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input&amp;quot;]) --&amp;gt; B[&amp;quot;Gain&amp;quot;]
    p1([&amp;quot;gain&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;offset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The gain is applied &lt;strong&gt;first&lt;/strong&gt;, then the offset: &lt;code&gt;output = gain × input + offset&lt;/code&gt;. To undo it, subtract the offset before dividing.&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;input&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 signals to scale. One value per channel.&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;scaled unit&lt;/td&gt;
&lt;td&gt;The scaled and offset signals, one per channel. Reads &lt;strong&gt;0&lt;/strong&gt; on every channel until you set the gain.&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;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit per input unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Multiplies the input, &lt;strong&gt;per channel&lt;/strong&gt;. &lt;strong&gt;At the default of 0 the output is just the offset.&lt;/strong&gt; A negative gain inverts the signal.&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;output unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Added after the multiplication, per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both are persistent and survive a controller restart. Both are &lt;strong&gt;per channel&lt;/strong&gt;,
so a four-channel block has four gains and four offsets. &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;Connect your signals to &lt;code&gt;input&lt;/code&gt;. The channel count is fixed when the
controller is built.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set &lt;code&gt;gain&lt;/code&gt; to 1 on every channel&lt;/strong&gt;, unless you want scaling. This is not
the default and the block will output zeros until you do.&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;output&lt;/code&gt; matches &lt;code&gt;input&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the gain you actually want, per channel.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;offset&lt;/code&gt; if you need one, remembering it is added after the
multiplication.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This block only scales a signal. Whatever consumes &lt;code&gt;output&lt;/code&gt; will see the change
on the next cycle.&lt;/p&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There is nothing dynamic to tune. Both parameters take effect on the next
cycle, with no fade and no filtering.&lt;/li&gt;
&lt;li&gt;Work out the gain from the units: it is the output unit divided by the input
unit. A block converting millimetres to metres has a gain of 0.001.&lt;/li&gt;
&lt;li&gt;Set the offset from where the two scales differ at zero. A sensor reading
0.5 at true zero needs an offset of -0.5 after the gain, or a correction
upstream.&lt;/li&gt;
&lt;li&gt;Check both directions if anything downstream inverts this conversion — the
inverse is &lt;code&gt;(output − offset) ÷ gain&lt;/code&gt;, not &lt;code&gt;output ÷ gain − offset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use a negative gain to reverse a signal&amp;rsquo;s sense. Nothing else in this block
changes.&lt;/li&gt;
&lt;li&gt;Nothing here depends on the task rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/gain-default-c655f518.svg&#34; alt=&#34;Output against input for three settings. At the default gain of 0 the outputis flat at the offset, whatever the input does.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The flat line is what an unconfigured block gives you.&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 output is 0 whatever the input does&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; is 0 — the default&lt;/td&gt;
&lt;td&gt;Set it to 1, or to the scaling you want&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is a constant, not 0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; is 0 and &lt;code&gt;offset&lt;/code&gt; is not&lt;/td&gt;
&lt;td&gt;Set the gain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only some channels work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; is per channel and you set only some&lt;/td&gt;
&lt;td&gt;Set every channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is inverted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; is negative&lt;/td&gt;
&lt;td&gt;Check its sign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset is scaled differently than expected&lt;/td&gt;
&lt;td&gt;The offset is added &lt;strong&gt;after&lt;/strong&gt; the multiplication, so it is in output units&lt;/td&gt;
&lt;td&gt;Convert it to output units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undoing the conversion gives the wrong answer&lt;/td&gt;
&lt;td&gt;The inverse is &lt;code&gt;(output − offset) ÷ gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subtract before dividing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is invalid&lt;/td&gt;
&lt;td&gt;An input that is not a valid number passes straight through&lt;/td&gt;
&lt;td&gt;Fix the upstream signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The output is enormous&lt;/td&gt;
&lt;td&gt;A large gain and a large input&lt;/td&gt;
&lt;td&gt;Check both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need to switch it off&lt;/td&gt;
&lt;td&gt;Not possible — there is no enable&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gain&lt;/code&gt; to 1 and &lt;code&gt;offset&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need two-way conversion&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;SignalConverter&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need a non-linear conversion&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;Lookup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more channels&lt;/td&gt;
&lt;td&gt;The count is fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild, or use a second instance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a pass-through on four channels:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gain   = [1.0, 1.0, 1.0, 1.0]
offset = [0.0, 0.0, 0.0, 0.0]
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;limits-and-errors&#34;&gt;Limits and errors&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Set by&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output = gain × input + offset&lt;/code&gt;, per channel, every cycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; default&lt;/td&gt;
&lt;td&gt;Fixed at &lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The output is the offset alone&lt;/strong&gt; until you set it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; range&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. 0 is a legitimate setting meaning &amp;ldquo;output the offset&amp;rdquo;; negative inverts&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. They pass straight through to the output&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overflow&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A large gain and a large input overflow silently&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The block has no memory. Nothing survives a cycle, and nothing needs resetting&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;There is no enable, disable or isEnabled&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Cannot be changed from the tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This block &lt;strong&gt;logs nothing, ever&lt;/strong&gt;. Every 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: SignalConverter</title>
      <link>/docs/developing-control-applications/control-blocks/estimation-and-maths/signal-converter/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/estimation-and-maths/signal-converter/</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;SignalConverter&lt;/code&gt; converts a quantity in both directions at once. One port
multiplies by a gain and adds an offset; the other subtracts the offset and
divides. The two are exact inverses, so a value sent out and read back comes
home unchanged.&lt;/p&gt;
&lt;p&gt;Use it wherever a quantity crosses a unit boundary both ways — a setpoint going
out to hardware and a measurement coming back.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input&amp;quot;]) --&amp;gt; B[&amp;quot;SignalConverter&amp;quot;]
    i2([&amp;quot;inverseInput&amp;quot;]) --&amp;gt; B
    p1([&amp;quot;gain&amp;quot;]) --&amp;gt; B
    p2([&amp;quot;offset&amp;quot;]) --&amp;gt; B
    p3([&amp;quot;inverseOutputReference&amp;quot;]) --&amp;gt; B
    p4([&amp;quot;doReferencing&amp;quot;]) --&amp;gt; B
    p5([&amp;quot;doReferencingAll&amp;quot;]) --&amp;gt; B
    p6([&amp;quot;invert&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output&amp;quot;])
    B --&amp;gt; o2([&amp;quot;inverseOutput&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;invert&lt;/code&gt; decides which port multiplies.&lt;/strong&gt; With it false — the default —
&lt;code&gt;inverseOutput&lt;/code&gt; multiplies and &lt;code&gt;output&lt;/code&gt; divides. With it true they swap.
Everything else about the block is the same.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Automatic referencing does not work correctly at the default &lt;code&gt;invert&lt;/code&gt;
setting.&lt;/strong&gt; It computes an offset that suits the other direction, so unless
your gain is 1 the result will be wrong. &lt;strong&gt;Set the offset by hand, or set
&lt;code&gt;invert&lt;/code&gt; to true&lt;/strong&gt;, until this is fixed.&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;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;With &lt;code&gt;invert&lt;/code&gt; false this port &lt;strong&gt;divides&lt;/strong&gt;: &lt;code&gt;output = (input − offset) ÷ gain&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inverseInput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;With &lt;code&gt;invert&lt;/code&gt; false this port &lt;strong&gt;multiplies&lt;/strong&gt;: &lt;code&gt;inverseOutput = gain × inverseInput + offset&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output unit&lt;/td&gt;
&lt;td&gt;The result on the &lt;code&gt;input&lt;/code&gt; side. Reads &lt;strong&gt;0&lt;/strong&gt; on any channel whose gain is 0, deliberately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inverseOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input unit&lt;/td&gt;
&lt;td&gt;The result on the &lt;code&gt;inverseInput&lt;/code&gt; side. This is the port that referencing aims at.&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;gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The conversion factor, &lt;strong&gt;per channel&lt;/strong&gt;. Shared by both directions, which is what makes them exact inverses. &lt;strong&gt;A gain of 0 makes the dividing side output 0&lt;/strong&gt; rather than failing.&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;output unit&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;Added on the multiplying side, subtracted on the dividing side, per channel. &lt;strong&gt;Referencing overwrites this.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inverseOutputReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input unit&lt;/td&gt;
&lt;td&gt;zeros&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The value referencing aims &lt;code&gt;inverseOutput&lt;/code&gt; at, per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doReferencing&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;Per channel: solve for the offset that puts &lt;code&gt;inverseOutput&lt;/code&gt; at its reference. &lt;strong&gt;Cleared automatically.&lt;/strong&gt; See the caveat above.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doReferencingAll&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;The same for every channel at once. Also cleared automatically.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invert&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;Swaps which port multiplies and which divides.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All are persistent and survive a controller restart. &lt;strong&gt;&lt;code&gt;doReferencing&lt;/code&gt; and
&lt;code&gt;doReferencingAll&lt;/code&gt; are persistent too&lt;/strong&gt;, so a configuration saved while one was
set will re-reference on the next startup, against whatever the inputs happen
to read then. Save your configuration after referencing, not during.&lt;/p&gt;
&lt;p&gt;&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;Connect the two directions. With &lt;code&gt;invert&lt;/code&gt; false, the multiplying direction
is &lt;code&gt;inverseInput&lt;/code&gt; to &lt;code&gt;inverseOutput&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;gain&lt;/code&gt; per channel, from the units: it is the multiplying side&amp;rsquo;s output
unit divided by its input unit.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;offset&lt;/code&gt; at 0 for now.&lt;/li&gt;
&lt;li&gt;Feed a known value into &lt;code&gt;inverseInput&lt;/code&gt; and confirm &lt;code&gt;inverseOutput&lt;/code&gt; is that
value times the gain.&lt;/li&gt;
&lt;li&gt;Feed &lt;code&gt;inverseOutput&lt;/code&gt; back into &lt;code&gt;input&lt;/code&gt; and confirm &lt;code&gt;output&lt;/code&gt; returns the
original. &lt;strong&gt;The round trip should be exact.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Set the offset by hand from a known reference point — see the caveat about
automatic referencing above.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;There is nothing dynamic here. Both parameters take effect on the next
cycle, with no fade and no filtering.&lt;/li&gt;
&lt;li&gt;Set the gain from the units first, and check the round trip before worrying
about the offset.&lt;/li&gt;
&lt;li&gt;Set the offset from one known point: put the machine at a position you can
measure, read &lt;code&gt;inverseInput&lt;/code&gt;, and compute the offset that makes
&lt;code&gt;inverseOutput&lt;/code&gt; read the true value.&lt;/li&gt;
&lt;li&gt;If you use &lt;code&gt;doReferencing&lt;/code&gt;, verify the result rather than trusting it —
read &lt;code&gt;offset&lt;/code&gt; back and check &lt;code&gt;inverseOutput&lt;/code&gt; really lands on
&lt;code&gt;inverseOutputReference&lt;/code&gt;. At a gain of 1 it will; away from 1 it will not.&lt;/li&gt;
&lt;li&gt;Check both directions after every change. The two share one gain and one
offset, so a change intended for one affects the other.&lt;/li&gt;
&lt;li&gt;Use a gain of 0 deliberately if you want a channel&amp;rsquo;s dividing side muted —
it outputs 0 rather than failing.&lt;/li&gt;
&lt;li&gt;Nothing here depends on the task rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/signal-converter-paths-d81dc1cf.svg&#34; alt=&#34;The two paths with a gain of 2 and an offset of 0.5. One multiplies, theother divides, and sending a value through both returns itunchanged.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The dashed line is the round trip. It should always be the identity.&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;Referencing put the offset in the wrong place&lt;/td&gt;
&lt;td&gt;The referencing formula suits &lt;code&gt;invert&lt;/code&gt; true, and the default is false&lt;/td&gt;
&lt;td&gt;Set the offset by hand, or set &lt;code&gt;invert&lt;/code&gt; to true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing looked right at a gain of 1&lt;/td&gt;
&lt;td&gt;Expected: the two formulas agree only there&lt;/td&gt;
&lt;td&gt;Verify at your real gain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The round trip does not return the input&lt;/td&gt;
&lt;td&gt;The gain or offset changed between the two directions&lt;/td&gt;
&lt;td&gt;They are shared, so check nothing else wrote them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One direction is scaled and the other is not&lt;/td&gt;
&lt;td&gt;That is what the block does — one multiplies, one divides&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;invert&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The multiplying and dividing ports are the wrong way round&lt;/td&gt;
&lt;td&gt;&lt;code&gt;invert&lt;/code&gt; is set the other way&lt;/td&gt;
&lt;td&gt;Flip it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A channel&amp;rsquo;s output is 0&lt;/td&gt;
&lt;td&gt;Its &lt;code&gt;gain&lt;/code&gt; is 0, so the dividing side outputs 0 deliberately&lt;/td&gt;
&lt;td&gt;Set a non-zero gain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset changed by itself&lt;/td&gt;
&lt;td&gt;A referencing trigger fired&lt;/td&gt;
&lt;td&gt;Check whether &lt;code&gt;doReferencing&lt;/code&gt; was saved as true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing happened at startup&lt;/td&gt;
&lt;td&gt;The triggers are persistent, and one was saved as true&lt;/td&gt;
&lt;td&gt;Save your configuration after referencing completes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only some channels converted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt; are per channel&lt;/td&gt;
&lt;td&gt;Set every channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The outputs are invalid&lt;/td&gt;
&lt;td&gt;An input that is not a valid number passes straight through&lt;/td&gt;
&lt;td&gt;Fix the upstream signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need the two directions scaled differently&lt;/td&gt;
&lt;td&gt;Not possible — they share one gain, which is what makes them exact inverses&lt;/td&gt;
&lt;td&gt;Use two &lt;code&gt;Gain&lt;/code&gt; blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need a non-linear conversion&lt;/td&gt;
&lt;td&gt;Wrong block&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;Lookup&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need to switch it off&lt;/td&gt;
&lt;td&gt;Not possible — there is no enable&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;gain&lt;/code&gt; to 1 and &lt;code&gt;offset&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I need more channels&lt;/td&gt;
&lt;td&gt;The count is fixed when the controller is built&lt;/td&gt;
&lt;td&gt;Rebuild, or use a second instance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A starting point for a two-channel pass-through:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gain    = [1.0, 1.0]
offset  = [0.0, 0.0]
invert  = 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;Conversion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gain&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One port multiplies and adds, the other subtracts and divides. &lt;strong&gt;Exact inverses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;, &lt;code&gt;inverseOutput&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;invert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chooses which port does which&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A gain of 0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Checked&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The dividing side outputs &lt;strong&gt;0&lt;/strong&gt; rather than failing&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic referencing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doReferencing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Computes an offset for the wrong direction when &lt;code&gt;invert&lt;/code&gt; is false.&lt;/strong&gt; Correct only at a gain of 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not reported&lt;/strong&gt; — read &lt;code&gt;offset&lt;/code&gt; and &lt;code&gt;inverseOutput&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referencing triggers&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;Self-clearing, and &lt;strong&gt;persistent&lt;/strong&gt; — a saved &lt;code&gt;true&lt;/code&gt; fires once at startup&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offset&lt;/code&gt; after referencing&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Overwritten.&lt;/strong&gt; A later configuration reload restores the saved value, not the referenced one&lt;/td&gt;
&lt;td&gt;Read it back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values that are not numbers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked. They pass through both paths&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The block has no memory, deliberately — some parent blocks run it twice in one cycle&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;There is no enable, disable or isEnabled&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Fixed at build time&lt;/td&gt;
&lt;td&gt;Cannot be changed from the tree&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This block &lt;strong&gt;logs nothing, ever&lt;/strong&gt;. Every 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>
    
  </channel>
</rss>
