<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title> – Filters</title>
    <link>/docs/developing-control-applications/control-blocks/filters/</link>
    <description>Recent content in Filters on </description>
    <generator>Hugo -- gohugo.io</generator>
    
	  <atom:link href="/docs/developing-control-applications/control-blocks/filters/index.xml" rel="self" type="application/rss+xml" />
    
    
      
        
      
    
    
    <item>
      <title>Docs: LowPass1</title>
      <link>/docs/developing-control-applications/control-blocks/filters/low-pass-1/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/low-pass-1/</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;LowPass1&lt;/code&gt; smooths a noisy signal before a controller consumes it. One cut-off
frequency governs every channel, and the channel count is fixed by the machine
configuration. It also publishes the rate of change of its own smoothed
output, so you never have to differentiate &lt;code&gt;output&lt;/code&gt; yourself.&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 — signal to smooth&amp;quot;]) --&amp;gt; B[&amp;quot;LowPass1&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — smoothed signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputDot — rate of the smoothed signal&amp;quot;])
    B --&amp;gt; o3([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$f_c = \omega/2\pi$ — &lt;code&gt;omega&lt;/code&gt; 6.283 rad/s is a 1 Hz cut-off. Time constant
$\tau = 1/\omega$ [s]: 63% of a step in $\tau$, 90% in $2.3,\tau$, 99% in
$4.6,\tau$. At the cut-off: gain 0.71, lag 45°, which is a delay of
$0.125/f_c$ [s]. Below the cut-off the delay is $\tau$. That delay is the
whole trade of this block.&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 signal to smooth. One element per channel; the channel count is fixed by the machine configuration.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;The smoothed signal, one element per channel. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. Starts from zero after every controller start, and is not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;The rate of change of &lt;code&gt;output&lt;/code&gt;, one element per channel. While bypassed this becomes the raw sample-to-sample difference of &lt;code&gt;input&lt;/code&gt; divided by the task period — see the symptom table.&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. A single value for the whole block, not one per channel.&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;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;6.283 (1 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;Cut-off, shared by all channels. Lower removes more noise and adds more delay. Out-of-range values are corrected silently — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;omega&lt;/code&gt; and &lt;code&gt;enable&lt;/code&gt; are persistent — both survive a controller restart. The
inputs and outputs do not; &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;disable&lt;/code&gt; return to their linked sources
and &lt;code&gt;output&lt;/code&gt; starts from zero. &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 the source signal: &lt;code&gt;…/actuator/actualVelocity&lt;/code&gt; →
&lt;code&gt;…/velocityFilter/input&lt;/code&gt;. &lt;code&gt;input&lt;/code&gt; follows the source on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;omega&lt;/code&gt; to 300. Read it back; a lower value means your task rate capped
it, and tells you the highest cut-off this task rate allows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. &lt;code&gt;isEnabled&lt;/code&gt; reads true and &lt;code&gt;output&lt;/code&gt; still tracks &lt;code&gt;input&lt;/code&gt;
closely, with the noise still visible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Halve &lt;code&gt;omega&lt;/code&gt; in steps, watching the noise and the loop consuming it. Stop
one step above where that loop feels soft or hunts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 changes what the controller sees.&lt;/strong&gt; Lower the cut-off with the
axis at rest before trying it in motion — the added delay can destabilise
a tightly tuned loop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;. An element that reads zero
while the machine moves is an unwired channel, not a filtered one.&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;Read the frequency of the ripple you want gone, in Hz, off a trace of
&lt;code&gt;input&lt;/code&gt;. Set &lt;code&gt;omega&lt;/code&gt; to $2\pi$ times that frequency as a starting point,
then lower it until the ripple is gone.&lt;/li&gt;
&lt;li&gt;Measure the delay you have bought. Step the source and read the time
&lt;code&gt;output&lt;/code&gt; takes to cross 63% of the step; that time is $\tau$, and the delay
below the cut-off equals it.&lt;/li&gt;
&lt;li&gt;Keep that delay below a tenth of the response time of the loop consuming
&lt;code&gt;output&lt;/code&gt;. Above that, the loop pays for the smoothing in stability.&lt;/li&gt;
&lt;li&gt;Check the loop in motion, not at rest. Delay costs phase margin only while
the axis is moving fast enough to need it.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;omega&lt;/code&gt; after any task-rate change. The upper bound scales with the
task period, so a slower task can clamp a cut-off that used to be accepted.&lt;/li&gt;
&lt;li&gt;If you need the noise gone &lt;em&gt;and&lt;/em&gt; the delay, fix the noise at its source —
shielding, grounding, or a higher-resolution sensor. No cut-off gives you
both.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/low-pass-1-step-24e744cc.svg&#34; alt=&#34;Step response at three cut-offs: omega 30 reaches the step almost at once,omega 6.283 crosses 63% at 0.16 s, and omega 2 is still climbing after asecond.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the time constant off any curve as the moment it crosses 0.63.&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;Noise still present on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cut-off too high&lt;/td&gt;
&lt;td&gt;Halve &lt;code&gt;omega&lt;/code&gt;, then re-check the loop for softness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop went soft, hunts or oscillates after enabling&lt;/td&gt;
&lt;td&gt;Delay too high for the loop&lt;/td&gt;
&lt;td&gt;Double &lt;code&gt;omega&lt;/code&gt;; if the noise returns, fix it at the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint tracking lags behind the command&lt;/td&gt;
&lt;td&gt;The block is in the command path, not the feedback path&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;omega&lt;/code&gt;, or move the filter onto the measurement only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; reads back lower than written&lt;/td&gt;
&lt;td&gt;Outside the accepted band for this task rate&lt;/td&gt;
&lt;td&gt;Raise the task rate or accept the cap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; reads back as 0.1&lt;/td&gt;
&lt;td&gt;Written below the fixed lower bound&lt;/td&gt;
&lt;td&gt;Write 0.1 or more; a slower filter is not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; used to be accepted and now clamps&lt;/td&gt;
&lt;td&gt;The task period grew, so the upper bound fell&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;omega&lt;/code&gt;&amp;rsquo;s task rate back, or lower &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; ramps from zero for a second after every start&lt;/td&gt;
&lt;td&gt;Expected: the filter starts from zero&lt;/td&gt;
&lt;td&gt;Gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus a short delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; stays at zero while the signal moves&lt;/td&gt;
&lt;td&gt;Channel unwired — an unwired element reads zero&lt;/td&gt;
&lt;td&gt;Trace &lt;code&gt;input&lt;/code&gt; element by element and link every one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt; shows one large spike the moment the filter is bypassed&lt;/td&gt;
&lt;td&gt;Expected: while bypassed, &lt;code&gt;outputDot&lt;/code&gt; is a raw difference over one task period&lt;/td&gt;
&lt;td&gt;Gate any consumer of &lt;code&gt;outputDot&lt;/code&gt; on &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; follows &lt;code&gt;input&lt;/code&gt; exactly, with no smoothing at all&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;; it tells you which of the two is holding the bypass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels smooth more than others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt; or &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source; the block cannot recover its own state, so restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a velocity signal on a 1 ms task, feeding a
position loop with a 100 ms response time:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omega  = 60.0
enable = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work step 1 with a trace of your
own signal.&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;omega&lt;/code&gt; ≥ 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A slower value is replaced by 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; ≤ 0.628 / task period [s]&lt;/td&gt;
&lt;td&gt;Task rate — 628 rad/s on a 1 ms task, 62.8 rad/s on a 10 ms task&lt;/td&gt;
&lt;td&gt;A faster value is replaced by the edge. This is what keeps the filter stable at any setting&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the input carries reaches the output. 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;&lt;code&gt;outputDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded, and largest on the cycle the bypass engages&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; it cannot be changed at runtime&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: LowPass2</title>
      <link>/docs/developing-control-applications/control-blocks/filters/low-pass-2/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/low-pass-2/</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;LowPass2&lt;/code&gt; smooths a noisy signal with a second-order filter: twice the
roll-off of a first-order filter, and a damping setting that decides whether
the output overshoots. One cut-off and one damping govern every channel. It
publishes the whole derivative stack — value, velocity, acceleration and jerk —
so a setpoint path can take all four from one block.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    i1([&amp;quot;input — signal to smooth&amp;quot;]) --&amp;gt; B[&amp;quot;LowPass2&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;vaLimiterDisable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — smoothed signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputDot — velocity of the output&amp;quot;])
    B --&amp;gt; o3([&amp;quot;outputDDot — acceleration of the output&amp;quot;])
    B --&amp;gt; o4([&amp;quot;outputDDDot — jerk of the output&amp;quot;])
    B --&amp;gt; o5([&amp;quot;isEnabled&amp;quot;])
    B --&amp;gt; o6([&amp;quot;vaLimiterIsEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$f_c = \omega/2\pi$ — &lt;code&gt;omega&lt;/code&gt; 6.283 rad/s is a 1 Hz cut-off. &lt;code&gt;beta&lt;/code&gt; is the
damping ratio: overshoot is $e^{-\pi\beta/\sqrt{1-\beta^2}}$, so 37% at
&lt;code&gt;beta&lt;/code&gt; 0.3, 16% at 0.5, and none at 1.0 or above. Ringing frequency is
$\omega\sqrt{1-\beta^2}$ [rad/s]. At the cut-off the gain is $1/(2\beta)$ —
&lt;strong&gt;above 1 for any &lt;code&gt;beta&lt;/code&gt; below 0.5&lt;/strong&gt;, so a low damping amplifies rather than
removes. Phase lag at the cut-off is 90°, twice a first-order filter&amp;rsquo;s, and
that lag is the trade.&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 signal to smooth. One element per channel; the channel count is fixed by the machine configuration.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterDisable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True switches the velocity and acceleration limiter off, leaving the filter itself running. Use it for a runtime override; use &lt;code&gt;vaLimiterEnable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;The smoothed signal, one element per channel. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. Starts from zero after every controller start, and is not cleared by a stop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;Velocity of &lt;code&gt;output&lt;/code&gt;. While the filter runs this is integrated, not differenced, so it is as smooth as &lt;code&gt;output&lt;/code&gt;. While bypassed it becomes a raw difference over one task period.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second²&lt;/td&gt;
&lt;td&gt;Acceleration of &lt;code&gt;output&lt;/code&gt;, and the quantity the limiter acts on. While bypassed it becomes a raw difference of &lt;code&gt;outputDot&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDDDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second³&lt;/td&gt;
&lt;td&gt;Jerk of &lt;code&gt;output&lt;/code&gt;. Always a raw difference, so it is by far the noisiest output and lags the others by one task period. Treat it as diagnostic, not as a control signal.&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. A single value for the whole block, not one per channel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterIsEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;vaLimiterEnable&lt;/code&gt; is true and &lt;code&gt;vaLimiterDisable&lt;/code&gt; is false. It does &lt;strong&gt;not&lt;/strong&gt; tell you the limiter is currently cutting, only that it is switched on.&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;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;6.283 (1 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;Cut-off, shared by all channels. Lower removes more noise and adds more delay. Out-of-range values are corrected silently — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0.1 – 10&lt;/td&gt;
&lt;td&gt;Damping ratio, shared by all channels. Below 1 the output overshoots and rings; below 0.5 it amplifies at the cut-off. Above 1 it is slower and never overshoots. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterMaxAcc&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;must be positive&lt;/td&gt;
&lt;td&gt;Largest acceleration the limiter allows. &lt;strong&gt;Not validated&lt;/strong&gt; — see Limits and errors. Only has effect while &lt;code&gt;vaLimiterIsEnabled&lt;/code&gt; is true.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterMaxVel&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;must be positive&lt;/td&gt;
&lt;td&gt;Largest velocity the limiter allows. Where this and &lt;code&gt;vaLimiterMaxAcc&lt;/code&gt; conflict, the velocity bound wins. &lt;strong&gt;Not validated.&lt;/strong&gt; Only has effect while &lt;code&gt;vaLimiterIsEnabled&lt;/code&gt; is true.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterEnable&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 switches on the velocity and acceleration limiter. Set both bounds to real machine values &lt;strong&gt;before&lt;/strong&gt; you turn this on.&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;code&gt;output&lt;/code&gt; starts from zero. &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 the source signal: &lt;code&gt;…/actuator/actualPosition&lt;/code&gt; →
&lt;code&gt;…/positionFilter/input&lt;/code&gt;. &lt;code&gt;input&lt;/code&gt; follows the source on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;vaLimiterEnable&lt;/code&gt; false for the whole of setup. The limiter&amp;rsquo;s default
bounds of 1.0 will throttle any real machine signal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;omega&lt;/code&gt; to 300 and &lt;code&gt;beta&lt;/code&gt; to 1.0. Read &lt;code&gt;omega&lt;/code&gt; back; a lower value means
your task rate capped it, and tells you the highest cut-off this task rate
allows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. &lt;code&gt;isEnabled&lt;/code&gt; reads true and &lt;code&gt;output&lt;/code&gt; still tracks &lt;code&gt;input&lt;/code&gt;
closely, with the noise still visible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Halve &lt;code&gt;omega&lt;/code&gt; in steps, watching the noise and the loop consuming it. Stop
one step above where that loop feels soft or hunts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 6 changes what the controller sees.&lt;/strong&gt; A second-order filter costs
twice the phase lag of a first-order one at the same cut-off. Lower the
cut-off with the axis at rest before trying it in motion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;. An element that reads zero
while the machine moves is an unwired channel, not a filtered one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only if you want the block to shape motion as well as smooth it: set
&lt;code&gt;vaLimiterMaxAcc&lt;/code&gt; and &lt;code&gt;vaLimiterMaxVel&lt;/code&gt; to your machine&amp;rsquo;s real bounds, then
set &lt;code&gt;vaLimiterEnable&lt;/code&gt; true and confirm &lt;code&gt;vaLimiterIsEnabled&lt;/code&gt; reads true.&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;Read the frequency of the ripple you want gone, in Hz, off a trace of
&lt;code&gt;input&lt;/code&gt;. Set &lt;code&gt;omega&lt;/code&gt; to $2\pi$ times that frequency, then lower it until the
ripple is gone.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;beta&lt;/code&gt; at 1.0 while you find &lt;code&gt;omega&lt;/code&gt;. Damping and cut-off interact,
and 1.0 is the setting with no overshoot.&lt;/li&gt;
&lt;li&gt;Step the source and measure the overshoot on &lt;code&gt;output&lt;/code&gt;. If you want a faster
response and can accept overshoot, lower &lt;code&gt;beta&lt;/code&gt; toward 0.5. Never go below
0.5 unless you intend a resonant peak — below it the filter amplifies at the
cut-off instead of cutting.&lt;/li&gt;
&lt;li&gt;Measure the delay you have bought. Step the source and read the time
&lt;code&gt;output&lt;/code&gt; takes to reach 90% of the step. Keep it below a tenth of the
response time of the loop consuming &lt;code&gt;output&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the loop in motion, not at rest. Delay costs phase margin only while
the axis moves fast enough to need it.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;omega&lt;/code&gt; after any task-rate change. The upper bound scales with the
task period, so a slower task can clamp a cut-off that used to be accepted.&lt;/li&gt;
&lt;li&gt;If you are using &lt;code&gt;outputDDot&lt;/code&gt; or &lt;code&gt;outputDDDot&lt;/code&gt;, look at them on a trace
before you rely on them. Each derivative multiplies the remaining noise, and
jerk is differenced rather than filtered.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/low-pass-2-step-93fee86a.svg&#34; alt=&#34;Step response at three damping settings with the cut-off held at 6.283 rad/s:beta 0.3 overshoots to 1.37 and rings, beta 1.0 rises to 1.0 with no overshoot,and beta 3.0 has reached only 0.8 after 1.5 s.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the overshoot off any curve as its highest point above 1.0.&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;Noise still present on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cut-off too high&lt;/td&gt;
&lt;td&gt;Halve &lt;code&gt;omega&lt;/code&gt;, then re-check the loop for softness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; overshoots the step and rings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; below 1&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;beta&lt;/code&gt; toward 1.0; ringing stops entirely at 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noise at the cut-off got &lt;em&gt;worse&lt;/em&gt; after enabling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; below 0.5, so the filter has a resonant peak&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;beta&lt;/code&gt; to 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; reaches its target far too slowly&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; well above 1, or &lt;code&gt;omega&lt;/code&gt; too low&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;beta&lt;/code&gt; toward 1.0 first, then raise &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop went soft, hunts or oscillates after enabling&lt;/td&gt;
&lt;td&gt;Delay too high — a second-order filter costs twice the lag&lt;/td&gt;
&lt;td&gt;Double &lt;code&gt;omega&lt;/code&gt;, or use a first-order filter instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; or &lt;code&gt;beta&lt;/code&gt; reads back different from what you wrote&lt;/td&gt;
&lt;td&gt;Outside the accepted band&lt;/td&gt;
&lt;td&gt;Read the value back and work within it; the task rate sets &lt;code&gt;omega&lt;/code&gt;&amp;rsquo;s upper bound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; used to be accepted and now clamps&lt;/td&gt;
&lt;td&gt;The task period grew, so the upper bound fell&lt;/td&gt;
&lt;td&gt;Raise the task rate back, or lower &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis crawls the moment &lt;code&gt;vaLimiterEnable&lt;/code&gt; goes true&lt;/td&gt;
&lt;td&gt;The limiter&amp;rsquo;s default bounds are 1.0, not machine values&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;vaLimiterMaxAcc&lt;/code&gt; and &lt;code&gt;vaLimiterMaxVel&lt;/code&gt; first, then re-enable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion is clipped and you cannot tell whether the limiter did it&lt;/td&gt;
&lt;td&gt;Expected: no output reports the limiter cutting&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;vaLimiterEnable&lt;/code&gt; false and compare; that is the only way to tell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The axis runs away in one direction with the limiter on&lt;/td&gt;
&lt;td&gt;A negative or zero value in &lt;code&gt;vaLimiterMaxAcc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write a positive value; negatives are accepted and are unsafe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; ramps from zero for a second after every start&lt;/td&gt;
&lt;td&gt;Expected: the filter starts from zero&lt;/td&gt;
&lt;td&gt;Gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus a short delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; stays at zero while the signal moves&lt;/td&gt;
&lt;td&gt;Channel unwired — an unwired element reads zero&lt;/td&gt;
&lt;td&gt;Trace &lt;code&gt;input&lt;/code&gt; element by element and link every one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One large spike on &lt;code&gt;outputDot&lt;/code&gt;, &lt;code&gt;outputDDot&lt;/code&gt; and &lt;code&gt;outputDDDot&lt;/code&gt; the moment the filter is bypassed&lt;/td&gt;
&lt;td&gt;Expected: while bypassed the derivatives are raw differences over one task period&lt;/td&gt;
&lt;td&gt;Gate every consumer of the derivative stack on &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A brief ring on &lt;code&gt;output&lt;/code&gt; after re-enabling the filter&lt;/td&gt;
&lt;td&gt;Expected: the filter resumes with the velocity it measured while bypassed&lt;/td&gt;
&lt;td&gt;Re-enable while the axis is at rest, or accept a settle of about $1/(\beta\omega)$ seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDDDot&lt;/code&gt; is unusably noisy&lt;/td&gt;
&lt;td&gt;Expected: jerk is differenced, not filtered&lt;/td&gt;
&lt;td&gt;Use it for diagnosis only; filter it downstream if a consumer needs it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels smooth more than others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one &lt;code&gt;omega&lt;/code&gt; and one &lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;omega&lt;/code&gt; or &lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source; the block cannot recover its own state, so restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a position signal on a 1 ms task, feeding a
loop with a 100 ms response time, with the limiter off:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omega            = 60.0
beta             = 1.0
vaLimiterEnable  = false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work step 1 with a trace of your
own signal.&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;omega&lt;/code&gt; ≥ 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A slower value is replaced by 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; ≤ 0.628 / task period [s]&lt;/td&gt;
&lt;td&gt;Task rate — 628 rad/s on a 1 ms task, 62.8 rad/s on a 10 ms task&lt;/td&gt;
&lt;td&gt;A faster value is replaced by the edge. This is what keeps the filter stable at any setting&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; within 0.1 – 10&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the band is replaced by the nearest edge&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;beta&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaLimiterMaxAcc&lt;/code&gt;, &lt;code&gt;vaLimiterMaxVel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not checked.&lt;/strong&gt; Zero and negative values are accepted, and a negative acceleration bound drives the output away in one direction until something else stops it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing while the limiter is off; &lt;code&gt;vaLimiterMaxVel&lt;/code&gt; and &lt;code&gt;vaLimiterMaxAcc&lt;/code&gt; shape it while the limiter is on&lt;/td&gt;
&lt;td&gt;Unbounded in position either way — the limiter bounds rate, not travel. Limit position 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;&lt;code&gt;outputDot&lt;/code&gt;, &lt;code&gt;outputDDot&lt;/code&gt;, &lt;code&gt;outputDDDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded, and largest on the cycle the bypass engages&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; it cannot be changed at runtime&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: HighPass1</title>
      <link>/docs/developing-control-applications/control-blocks/filters/high-pass-1/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/high-pass-1/</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;HighPass1&lt;/code&gt; removes the slow content of a signal and keeps the fast — sensor
drift, a thermal offset, a load cell&amp;rsquo;s zero error. One cut-off frequency
governs every channel, and the channel count is fixed by the machine
configuration. Its steady output for a constant input is &lt;strong&gt;zero, not the
input&lt;/strong&gt;: this block deletes the part of the signal that does not 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;input — signal to wash out&amp;quot;]) --&amp;gt; B[&amp;quot;HighPass1&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — fast content of the input&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$f_c = \omega/2\pi$ — &lt;code&gt;omega&lt;/code&gt; 6.283 rad/s is a 1 Hz cut-off. Time constant
$\tau = 1/\omega$ [s]: a step appears in full, then decays to 37% in $\tau$
and to 1% in $4.6,\tau$. At the cut-off: gain 0.71 and 45° of phase
&lt;strong&gt;lead&lt;/strong&gt;. Signals slower than the cut-off are removed; signals faster pass at
gain 1. Choosing &lt;code&gt;omega&lt;/code&gt; is choosing how slow &amp;ldquo;slow&amp;rdquo; means.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;This block has no derivative output and no reset input.&lt;/strong&gt; It also cannot be
bypassed without restoring the offset it was removing — see the symptom table.&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&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to wash out. One element per channel; the channel count is fixed by the machine configuration.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;, offset included. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;The fast content of &lt;code&gt;input&lt;/code&gt;, one element per channel. Settles to zero for any constant input. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. Starts from the full value of &lt;code&gt;input&lt;/code&gt; after every controller start, then washes out over $\tau$.&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. A single value for the whole block, not one per channel.&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;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;6.283 (1 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;Cut-off, shared by all channels. Lower keeps more of the slow content and takes longer to wash a step out. Higher removes more, and starts eating the signal you wanted to keep. Out-of-range values are corrected silently — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged, offset included.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both 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;Link the source signal: &lt;code&gt;…/sensor/rawForce&lt;/code&gt; → &lt;code&gt;…/forceWashout/input&lt;/code&gt;.
&lt;code&gt;input&lt;/code&gt; follows the source on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;omega&lt;/code&gt; to 300. Read it back; a lower value means your task rate capped
it, and tells you the highest cut-off this task rate allows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hold the machine still and note the constant value &lt;code&gt;input&lt;/code&gt; sits at. That
offset is what the block is going to remove.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. &lt;code&gt;output&lt;/code&gt; starts at the full value of &lt;code&gt;input&lt;/code&gt;, then decays
toward zero. Time that decay — it reaches 37% of the way at $\tau$.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 injects the whole offset into whatever consumes &lt;code&gt;output&lt;/code&gt;.&lt;/strong&gt; The
filter starts from a clean state, so its first sample is the input in
full. Enable it with the machine at rest and the consumer gated off.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hold the machine still again. &lt;code&gt;output&lt;/code&gt; should sit at zero, not at the offset
from step 4. If it does not, the cut-off is too low for the drift you have.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;. An element that reads zero
while the machine moves is an unwired channel, not a washed-out one.&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 the slowest signal you want to &lt;strong&gt;keep&lt;/strong&gt;, in Hz. Set &lt;code&gt;omega&lt;/code&gt; to
$2\pi$ times that frequency; everything slower than it will be removed.&lt;/li&gt;
&lt;li&gt;Measure the drift you want gone. Read how long the offset takes to build on
a trace, in seconds; &lt;code&gt;omega&lt;/code&gt; must be at least a few times $1/$that.&lt;/li&gt;
&lt;li&gt;Check the two against each other. If the drift builds as fast as the signal
you want to keep, no cut-off separates them and this block cannot help.&lt;/li&gt;
&lt;li&gt;Step the machine and read the decay off &lt;code&gt;output&lt;/code&gt;. The time to 37% is $\tau$,
and $\tau$ is how long a genuine step survives before the filter eats it.&lt;/li&gt;
&lt;li&gt;Confirm the steady state with the machine at rest: &lt;code&gt;output&lt;/code&gt; at zero, not at
an offset.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;omega&lt;/code&gt; after any task-rate change. The upper bound scales with the
task period, so a slower task can clamp a cut-off that used to be accepted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/high-pass-1-washout-187731db.svg&#34; alt=&#34;Response to a step at three cut-offs: omega 20 washes the step out within0.2 s, omega 6.283 decays to 0.37 at 0.16 s, and omega 2 still holds 0.14 ofthe step after a second.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the time constant off any curve as the moment 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;output&lt;/code&gt; sits at zero while &lt;code&gt;input&lt;/code&gt; clearly has a value&lt;/td&gt;
&lt;td&gt;Expected: a constant input has no fast content, so there is nothing to pass&lt;/td&gt;
&lt;td&gt;Confirm with a moving signal; if that also reads zero, the channel is unwired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The offset is still on &lt;code&gt;output&lt;/code&gt; at rest&lt;/td&gt;
&lt;td&gt;Cut-off too low for the drift&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;omega&lt;/code&gt;; if the drift is as fast as your signal, this block cannot separate them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A genuine slow movement got deleted&lt;/td&gt;
&lt;td&gt;Cut-off too high — the block cannot tell drift from slow signal&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;omega&lt;/code&gt;, and accept that some drift stays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; jumps to the full input value the moment the filter is enabled&lt;/td&gt;
&lt;td&gt;Expected: the block starts from a clean state, so the offset appears once and washes out over $\tau$&lt;/td&gt;
&lt;td&gt;Enable at rest, or gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus a few $\tau$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The same jump every time &lt;code&gt;disable&lt;/code&gt; is toggled off&lt;/td&gt;
&lt;td&gt;Expected: the same reason — the state is cleared while bypassed&lt;/td&gt;
&lt;td&gt;Do not toggle the bypass during operation; leave &lt;code&gt;enable&lt;/code&gt; set and use it as configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bypassing the filter put a large offset back on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expected: bypass restores the input in full, including the DC the filter was removing&lt;/td&gt;
&lt;td&gt;Bypass only when the consumer can accept the raw signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; overshoots the other way after a step&lt;/td&gt;
&lt;td&gt;Expected: the block removes the step&amp;rsquo;s steady part, so the output must return through zero&lt;/td&gt;
&lt;td&gt;None; this is what a washout does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; reads back lower than written&lt;/td&gt;
&lt;td&gt;Outside the accepted band for this task rate&lt;/td&gt;
&lt;td&gt;Raise the task rate or accept the cap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; reads back as 0.1&lt;/td&gt;
&lt;td&gt;Written below the fixed lower bound&lt;/td&gt;
&lt;td&gt;Write 0.1 or more; a slower washout is not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; used to be accepted and now clamps&lt;/td&gt;
&lt;td&gt;The task period grew, so the upper bound fell&lt;/td&gt;
&lt;td&gt;Raise the task rate back, or lower &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need the rate of the washed-out signal&lt;/td&gt;
&lt;td&gt;The block publishes no derivative&lt;/td&gt;
&lt;td&gt;Differentiate downstream, or filter first and difference after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels wash out faster than others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt; or &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source, then bypass the filter for one cycle — that clears its state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for washing thermal drift off a force signal on a
1 ms task, keeping everything above about 0.5 Hz:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omega  = 3.14
enable = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work steps 1 to 3 with a trace of
your own signal.&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;omega&lt;/code&gt; ≥ 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A slower value is replaced by 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; ≤ 0.628 / task period [s]&lt;/td&gt;
&lt;td&gt;Task rate — 628 rad/s on a 1 ms task, 62.8 rad/s on a 10 ms task&lt;/td&gt;
&lt;td&gt;A faster value is replaced by the edge. This is what keeps the filter stable at any setting&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — a fast input reaches the output at full size. 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;Channel count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed once the controller starts; it cannot be changed at runtime&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: HighPass2</title>
      <link>/docs/developing-control-applications/control-blocks/filters/high-pass-2/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/high-pass-2/</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;HighPass2&lt;/code&gt; is the second-order washout filter. Feed it an acceleration and it
removes the sustained part — the part a platform cannot reproduce without
running out of travel — and returns the transient part, plus the velocity and
position that follow from it. The three outputs are &lt;strong&gt;three different
filters&lt;/strong&gt;, not three views of one: &lt;code&gt;output&lt;/code&gt; keeps the fast content, &lt;code&gt;outputInt&lt;/code&gt;
keeps only content near the cut-off, and &lt;code&gt;outputIInt&lt;/code&gt; keeps the slow content.&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 — acceleration to wash out&amp;quot;]) --&amp;gt; B[&amp;quot;HighPass2&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;outputIInt — position, slow content only&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputInt — velocity, cut-off content only&amp;quot;])
    B --&amp;gt; o3([&amp;quot;output — acceleration, fast content only&amp;quot;])
    B --&amp;gt; o4([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$f_c = \omega/2\pi$ — &lt;code&gt;omega&lt;/code&gt; 6.283 rad/s is a 1 Hz cut-off. &lt;code&gt;beta&lt;/code&gt; is the
damping ratio: 1.0 gives one small undershoot, below 1.0 the outputs ring at
$\omega\sqrt{1-\beta^2}$ [rad/s]. &lt;code&gt;output&lt;/code&gt; settles to zero for any constant
input. &lt;code&gt;outputInt&lt;/code&gt; peaks at the cut-off with gain $1/(2\beta\omega)$.
&lt;code&gt;outputIInt&lt;/code&gt; settles to $1/\omega^2$ times a constant input — a factor of
0.025 at the default, so &lt;strong&gt;do not expect it to match the input&amp;rsquo;s
magnitude&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Leave this block enabled.&lt;/strong&gt; Disabling it does not idle it: &lt;code&gt;output&lt;/code&gt; drops to
zero rather than passing the input through, and &lt;code&gt;outputIInt&lt;/code&gt; drifts. It also
has no reset input — see Limits and errors.&lt;/p&gt;
&lt;h2 id=&#34;signals&#34;&gt;Signals&lt;/h2&gt;
&lt;h3 id=&#34;inputs&#34;&gt;Inputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s² for a washout, or any signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to wash out, normally an acceleration. One element per channel; the channel count is fixed by the machine configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True forces &lt;code&gt;output&lt;/code&gt; to zero and leaves the other two outputs coasting. It is not a bypass and does not pass &lt;code&gt;input&lt;/code&gt; through. Use &lt;code&gt;enable&lt;/code&gt; for the configured intent, and read the note above before using either.&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;outputIInt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m for a washout, or input unit × s²&lt;/td&gt;
&lt;td&gt;Position. Keeps the &lt;strong&gt;slow&lt;/strong&gt; content of &lt;code&gt;input&lt;/code&gt; and settles to $1/\omega^2$ times a constant input. The smoothest of the three. Starts from zero after every controller start, and is never cleared once running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputInt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;m/s for a washout, or input unit × s&lt;/td&gt;
&lt;td&gt;Velocity. Keeps only content near the cut-off, and settles to zero for both a constant input and a very fast one. Starts from zero after every controller start.&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;m/s² for a washout, or the input unit&lt;/td&gt;
&lt;td&gt;Acceleration. Keeps the &lt;strong&gt;fast&lt;/strong&gt; content and settles to zero for a constant input. It is &lt;strong&gt;not smoothed&lt;/strong&gt; — noise on &lt;code&gt;input&lt;/code&gt; reaches it at full size and undelayed. Zero while &lt;code&gt;disable&lt;/code&gt; is true or &lt;code&gt;enable&lt;/code&gt; is false.&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. A single value for the whole block, not one per channel.&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;omega&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;6.283 (1 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;Cut-off, shared by all channels. Lower keeps more of the sustained content and washes it out more slowly, so the platform travels further. Higher washes out sooner and the motion feels thinner. Out-of-range values are corrected silently — see Limits and errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0.1 – 2&lt;/td&gt;
&lt;td&gt;Damping ratio, shared by all channels. 1.0 gives one small undershoot and no ringing. Below 1.0 the outputs ring, badly below 0.5. Above 1.0 the washout is slower and gentler. Out-of-range values are corrected silently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False forces &lt;code&gt;output&lt;/code&gt; to zero. It does &lt;strong&gt;not&lt;/strong&gt; bypass the block. See the note under the diagram.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All three parameters are persistent and survive a controller restart. The
inputs and outputs do not; all three outputs start from zero. &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 the source signal: &lt;code&gt;…/model/commandedAcceleration&lt;/code&gt; →
&lt;code&gt;…/washout/input&lt;/code&gt;. &lt;code&gt;input&lt;/code&gt; follows the source on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leave &lt;code&gt;enable&lt;/code&gt; true for the whole of setup. There is no useful idle state,
and the outputs are already zero before any input arrives.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;omega&lt;/code&gt; to 6.283 and &lt;code&gt;beta&lt;/code&gt; to 1.0. Read &lt;code&gt;omega&lt;/code&gt; back; a lower value
means your task rate capped it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decide which output your consumer needs, and wire only that one. A platform
position command takes &lt;code&gt;outputIInt&lt;/code&gt;; a velocity feedforward takes
&lt;code&gt;outputInt&lt;/code&gt;; an accelerometer-matching path takes &lt;code&gt;output&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply a sustained acceleration on &lt;code&gt;input&lt;/code&gt; and hold it. &lt;code&gt;output&lt;/code&gt; must return
to zero, and &lt;code&gt;outputIInt&lt;/code&gt; must settle to a constant, not keep climbing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 is the check that the washout works.&lt;/strong&gt; If &lt;code&gt;outputIInt&lt;/code&gt; keeps
climbing while &lt;code&gt;input&lt;/code&gt; is held constant, stop and fix that before you
connect a platform — it will run to its travel limit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read the settled value of &lt;code&gt;outputIInt&lt;/code&gt; and divide it by the input you
applied. It should be close to $1/\omega^2$. This confirms the scaling you
have to compensate downstream.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and each output you use. An element that
reads zero while the machine moves is an unwired channel.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Measure your consumer&amp;rsquo;s travel budget first, in metres. That budget, not the
feel of the motion, sets the lowest &lt;code&gt;omega&lt;/code&gt; you can use.&lt;/li&gt;
&lt;li&gt;Apply the largest sustained acceleration your application produces and read
the peak of &lt;code&gt;outputIInt&lt;/code&gt;. Raise &lt;code&gt;omega&lt;/code&gt; until that peak fits the budget with
margin.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;beta&lt;/code&gt; at 1.0 while you find &lt;code&gt;omega&lt;/code&gt;. Damping and cut-off interact,
and 1.0 is the setting with almost no undershoot.&lt;/li&gt;
&lt;li&gt;Step the input and watch &lt;code&gt;output&lt;/code&gt; return through zero. The undershoot is the
platform being pulled back to centre; it is unavoidable in a washout.&lt;/li&gt;
&lt;li&gt;If that return is too abrupt, raise &lt;code&gt;beta&lt;/code&gt; toward 2.0. If the motion feels
dead, lower &lt;code&gt;beta&lt;/code&gt; toward 0.7 — but never below 0.5 unless you intend
ringing.&lt;/li&gt;
&lt;li&gt;Re-check the travel budget after every &lt;code&gt;beta&lt;/code&gt; change. Damping changes the
peak excursion as well as its shape.&lt;/li&gt;
&lt;li&gt;Re-check &lt;code&gt;omega&lt;/code&gt; after any task-rate change. The upper bound scales with the
task period, so a slower task can clamp a cut-off that used to be accepted.&lt;/li&gt;
&lt;li&gt;Look at &lt;code&gt;output&lt;/code&gt; on a trace before relying on it. It is unsmoothed, so it
carries whatever noise &lt;code&gt;input&lt;/code&gt; carries.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/high-pass-2-washout-743639af.svg&#34; alt=&#34;Response of the acceleration output to a step at three damping settings withthe cut-off held at 6.283 rad/s: beta 0.1 rings for seconds, beta 1.0undershoots once to -0.14 and returns, and beta 2.0 undershoots only to-0.05.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the pull-back off any curve as its lowest point below zero.&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;outputIInt&lt;/code&gt; is far smaller than the input&lt;/td&gt;
&lt;td&gt;Expected: its steady gain is $1/\omega^2$, which is 0.025 at the default cut-off&lt;/td&gt;
&lt;td&gt;Scale it downstream; there is no gain parameter in this block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputIInt&lt;/code&gt; keeps climbing while &lt;code&gt;input&lt;/code&gt; is held constant&lt;/td&gt;
&lt;td&gt;The block is not running the washout — check &lt;code&gt;isEnabled&lt;/code&gt; reads true&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;enable&lt;/code&gt; true and &lt;code&gt;disable&lt;/code&gt; false; a disabled block coasts instead of washing out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputIInt&lt;/code&gt; reached the platform&amp;rsquo;s travel limit&lt;/td&gt;
&lt;td&gt;Cut-off too low for the accelerations you are feeding it&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;omega&lt;/code&gt; and re-check step 2 of Tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; is noisy&lt;/td&gt;
&lt;td&gt;Expected: &lt;code&gt;output&lt;/code&gt; is not smoothed, unlike the two integrals&lt;/td&gt;
&lt;td&gt;Filter &lt;code&gt;input&lt;/code&gt; upstream, or take &lt;code&gt;outputInt&lt;/code&gt; instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; sits at zero while &lt;code&gt;input&lt;/code&gt; clearly has a value&lt;/td&gt;
&lt;td&gt;Expected for a constant input, since a constant has no fast content. Otherwise &lt;code&gt;enable&lt;/code&gt; is false&lt;/td&gt;
&lt;td&gt;Read &lt;code&gt;isEnabled&lt;/code&gt;; if it is true, this is normal washout behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All three outputs ring after a step&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; below 1&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;beta&lt;/code&gt; toward 1.0; ringing stops almost entirely at 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The motion pulls back too hard after a step&lt;/td&gt;
&lt;td&gt;Damping too low for the feel you want&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;beta&lt;/code&gt; toward 2.0, then re-check the travel budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The motion feels dead and thin&lt;/td&gt;
&lt;td&gt;Cut-off too high — the sustained content is being removed too soon&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;omega&lt;/code&gt;, within the travel budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; or &lt;code&gt;beta&lt;/code&gt; reads back different from what you wrote&lt;/td&gt;
&lt;td&gt;Outside the accepted band&lt;/td&gt;
&lt;td&gt;Read the value back and work within it; the task rate sets &lt;code&gt;omega&lt;/code&gt;&amp;rsquo;s upper bound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; used to be accepted and now clamps&lt;/td&gt;
&lt;td&gt;The task period grew, so the upper bound fell&lt;/td&gt;
&lt;td&gt;Raise the task rate back, or lower &lt;code&gt;omega&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputIInt&lt;/code&gt; sits at a large offset and will not return, even with &lt;code&gt;input&lt;/code&gt; at zero&lt;/td&gt;
&lt;td&gt;The block&amp;rsquo;s state is off-centre and it has no reset&lt;/td&gt;
&lt;td&gt;Restart the controller. Nothing in the parameter tree can clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputIInt&lt;/code&gt; drifted while the block was disabled&lt;/td&gt;
&lt;td&gt;Expected: a disabled block holds its velocity and keeps integrating it&lt;/td&gt;
&lt;td&gt;Leave the block enabled; restart the controller to clear the drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels wash out faster than others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one &lt;code&gt;omega&lt;/code&gt; and one &lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a separate washout per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An output went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;omega&lt;/code&gt; or &lt;code&gt;beta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source, then restart the controller. Disabling the block does not clear it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a motion-platform washout on a 1 ms task,
with a generous travel budget:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omega  = 6.283
beta   = 1.0
enable = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work steps 1 and 2 with your own
travel budget.&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;omega&lt;/code&gt; ≥ 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A slower value is replaced by 0.1 rad/s&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omega&lt;/code&gt; ≤ 0.628 / task period [s]&lt;/td&gt;
&lt;td&gt;Task rate — 628 rad/s on a 1 ms task, 62.8 rad/s on a 10 ms task&lt;/td&gt;
&lt;td&gt;A faster value is replaced by the edge. This is what keeps the filter stable at any setting&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;omega&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beta&lt;/code&gt; within 0.1 – 2&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A value outside the band is replaced by the nearest edge. Note the upper bound is 2, lower than the second-order low-pass filter&amp;rsquo;s&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;beta&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputIInt&lt;/code&gt;, &lt;code&gt;outputInt&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. &lt;code&gt;outputIInt&lt;/code&gt; in particular has no travel limit of its own — bound it downstream before it reaches a platform&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block state&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;There is &lt;strong&gt;no reset input and no way to clear the outputs in service.&lt;/strong&gt; Once &lt;code&gt;outputIInt&lt;/code&gt; is off-centre, only a controller restart returns it to zero&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; it cannot be changed at runtime&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: BandPass1</title>
      <link>/docs/developing-control-applications/control-blocks/filters/band-pass-1/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/band-pass-1/</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;BandPass1&lt;/code&gt; passes a band of frequencies and rejects everything slower and
everything faster. The band is set by two independent cut-offs rather than by a
centre frequency, so you set its low edge and its high edge separately. Unlike
the other filters here, its passband gain is &lt;strong&gt;not 1&lt;/strong&gt; — the output is scaled
by &lt;code&gt;omegaHighPass&lt;/code&gt;, so moving the low edge of the band also rescales the
output.&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 — signal to filter&amp;quot;]) --&amp;gt; B[&amp;quot;BandPass1&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;reset&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — content inside the band&amp;quot;])
    B --&amp;gt; o2([&amp;quot;outputDot — rate inside the band&amp;quot;])
    B --&amp;gt; o3([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;$f = \omega/2\pi$ — &lt;code&gt;omegaHighPass&lt;/code&gt; 1 rad/s is a 0.16 Hz low edge,
&lt;code&gt;omegaLowPass&lt;/code&gt; 10 rad/s a 1.6 Hz high edge. Passband gain is &lt;code&gt;omegaHighPass&lt;/code&gt;
itself, so the defaults happen to give gain 1. The peak is $-0.8$ dB at the
defaults, not 0 dB, because a decade of separation is not wide enough for the
two edges to stop interacting. Keep both cut-offs below 0.628/task period
[s]; nothing in the block enforces that for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;A channel whose &lt;code&gt;input&lt;/code&gt; is exactly zero is reset to zero and stays there
until the input moves off zero.&lt;/strong&gt; This block also has no automatic stability
limit — see Limits and errors before you write either cut-off.&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&lt;/td&gt;
&lt;td&gt;unbounded, but exactly 0.0 triggers a reset&lt;/td&gt;
&lt;td&gt;The signal to filter. One element per channel; the channel count is fixed by the machine configuration. This path is treated as configuration rather than as a link target, so it survives a restart with whatever value it last held.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;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 clears &lt;code&gt;output&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; to zero for every channel, once. It clears only part of the filter&amp;rsquo;s memory, so the output can move again immediately. Ignored while the block is bypassed, and consumed even then.&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;code&gt;omegaHighPass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The content of &lt;code&gt;input&lt;/code&gt; inside the band, one element per channel. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. Starts from zero after every controller start. This path is writable and survives a restart, but the block overwrites it on the next cycle it runs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit per second&lt;/td&gt;
&lt;td&gt;The rate of the filtered signal. It matches the rate of &lt;code&gt;output&lt;/code&gt; only while &lt;code&gt;damping&lt;/code&gt; is 0. &lt;strong&gt;It is not updated while the block is bypassed&lt;/strong&gt; and holds its last value indefinitely.&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. A single value for the whole block, not one per channel.&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;damping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0 – 0.9 in practice&lt;/td&gt;
&lt;td&gt;Slows the high edge of the band. 0 leaves it at &lt;code&gt;omegaLowPass&lt;/code&gt;; 0.5 halves it. &lt;strong&gt;At 1.0 the output freezes and above 1.0 it runs away&lt;/strong&gt; — see Limits and errors. Prefer lowering &lt;code&gt;omegaLowPass&lt;/code&gt; instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omegaHighPass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;1.0 (0.16 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;Low edge of the band, &lt;strong&gt;and the filter&amp;rsquo;s gain.&lt;/strong&gt; Raising it rejects more slow content and multiplies the output by the same factor. Not checked — a value above the range diverges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;omegaLowPass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rad/s&lt;/td&gt;
&lt;td&gt;10.0 (1.6 Hz)&lt;/td&gt;
&lt;td&gt;0.1 – 0.628/task period [s]&lt;/td&gt;
&lt;td&gt;High edge of the band. Lowering it rejects more fast content. Keep it at least a decade above &lt;code&gt;omegaHighPass&lt;/code&gt;, or the band&amp;rsquo;s peak drops well below its nominal gain. Not checked — a value above the range diverges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All four parameters are persistent and survive a controller restart, and so do
&lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt; and &lt;code&gt;outputDot&lt;/code&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 the source signal into &lt;code&gt;input&lt;/code&gt; and confirm on a trace that &lt;code&gt;input&lt;/code&gt;
follows the source. If your source can sit at exactly zero, read the note
under the diagram first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compute your task rate&amp;rsquo;s ceiling: 0.628 divided by the task period in
seconds. That is 628 on a 1 ms task. &lt;strong&gt;Neither cut-off may exceed it.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;omegaHighPass&lt;/code&gt; to the low edge you want and &lt;code&gt;omegaLowPass&lt;/code&gt; to the high
edge, both below the ceiling from step 3 and at least a decade apart. Leave
&lt;code&gt;damping&lt;/code&gt; at 0.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 4 has no safety net.&lt;/strong&gt; A cut-off above the ceiling makes the output
grow without bound instead of being rejected. Check both values against
step 3 before you set &lt;code&gt;enable&lt;/code&gt; true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. &lt;code&gt;isEnabled&lt;/code&gt; reads true and &lt;code&gt;output&lt;/code&gt; responds to movement
in the band.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed a slow movement and confirm &lt;code&gt;output&lt;/code&gt; stays near zero. Feed a fast
movement and confirm the same. Feed one in the band and confirm &lt;code&gt;output&lt;/code&gt;
responds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Divide &lt;code&gt;output&lt;/code&gt; by &lt;code&gt;input&lt;/code&gt; for an in-band signal. The ratio should be close
to &lt;code&gt;omegaHighPass&lt;/code&gt;. That factor is what you compensate for downstream.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;. An element that reads zero
while the machine moves is either unwired or being reset by an exact-zero
input.&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;Read the frequency you want to keep off a trace of &lt;code&gt;input&lt;/code&gt;, in Hz. Set
&lt;code&gt;omegaHighPass&lt;/code&gt; to $2\pi$ times about a third of it, and &lt;code&gt;omegaLowPass&lt;/code&gt; to
$2\pi$ times about three times it.&lt;/li&gt;
&lt;li&gt;Check both against the ceiling from Setup step 3 every time you change
either.&lt;/li&gt;
&lt;li&gt;Widen the band until the signal you want passes at full size. Narrow it
until the content you want rejected is gone. Those two pull against each
other, and the gap between them is the whole tuning.&lt;/li&gt;
&lt;li&gt;Measure the passband gain after every &lt;code&gt;omegaHighPass&lt;/code&gt; change. The gain moves
with it, so a wider band is also a quieter one.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;damping&lt;/code&gt; at 0. It duplicates &lt;code&gt;omegaLowPass&lt;/code&gt; — only the product of the
two matters — and it has no safe upper limit. Change &lt;code&gt;omegaLowPass&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;Re-check both cut-offs after any task-rate change. The ceiling scales with
the task period, and a value that was safe on a fast task can diverge on a
slow one.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/band-pass-1-magnitude-a14b5f95.svg&#34; alt=&#34;Magnitude response at three cut-off pairs on a log frequency axis: a narrowband peaks at +3.1 dB, the default decade-wide band at -0.8 dB, and a wide bandat -6.1 dB, each rolling off below and above its edges.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the passband gain off any curve as its highest point, and the band edges
as where it falls 3 dB below that.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; is a constant multiple of what you expected&lt;/td&gt;
&lt;td&gt;Expected: the passband gain is &lt;code&gt;omegaHighPass&lt;/code&gt;, not 1&lt;/td&gt;
&lt;td&gt;Scale downstream, or set &lt;code&gt;omegaHighPass&lt;/code&gt; to 1 and set the low edge with &lt;code&gt;omegaLowPass&lt;/code&gt; alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The passband gain changed after you retuned the low edge&lt;/td&gt;
&lt;td&gt;Expected: gain and low edge are the same parameter&lt;/td&gt;
&lt;td&gt;Recompensate downstream after every &lt;code&gt;omegaHighPass&lt;/code&gt; change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; grew without bound until something tripped&lt;/td&gt;
&lt;td&gt;A cut-off above 0.628/task period, or &lt;code&gt;damping&lt;/code&gt; above 1&lt;/td&gt;
&lt;td&gt;Bring both cut-offs under the ceiling and &lt;code&gt;damping&lt;/code&gt; to 0, then restart the controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; froze at a value and &lt;code&gt;outputDot&lt;/code&gt; still reads non-zero&lt;/td&gt;
&lt;td&gt;&lt;code&gt;damping&lt;/code&gt; is exactly 1&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;damping&lt;/code&gt; to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A channel sits at zero and will not move&lt;/td&gt;
&lt;td&gt;Its &lt;code&gt;input&lt;/code&gt; is exactly 0.0, which resets that channel every cycle&lt;/td&gt;
&lt;td&gt;Confirm on a trace that &lt;code&gt;input&lt;/code&gt; is non-zero; an unwired channel reads exactly zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A channel dropped to zero mid-motion and recovered&lt;/td&gt;
&lt;td&gt;Its &lt;code&gt;input&lt;/code&gt; landed on exactly 0.0 for a cycle&lt;/td&gt;
&lt;td&gt;Expected with quantised or integer-derived signals; add a tiny offset upstream if it recurs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The peak is several dB below the gain you calculated&lt;/td&gt;
&lt;td&gt;The two cut-offs are too close together&lt;/td&gt;
&lt;td&gt;Separate them by at least a decade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; does not respond to the movement you care about&lt;/td&gt;
&lt;td&gt;The band excludes it&lt;/td&gt;
&lt;td&gt;Widen the band and re-check with step 3 of Tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow drift is still on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low edge too low&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;omegaHighPass&lt;/code&gt;, and recompensate the gain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noise is still on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High edge too high&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;omegaLowPass&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt; is frozen while the filter is bypassed&lt;/td&gt;
&lt;td&gt;Expected: it is not written on the bypass path&lt;/td&gt;
&lt;td&gt;Gate any consumer of &lt;code&gt;outputDot&lt;/code&gt; on &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputDot&lt;/code&gt; does not match the slope of &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;damping&lt;/code&gt; is not 0&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;damping&lt;/code&gt; to 0, or differentiate &lt;code&gt;output&lt;/code&gt; downstream instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt; did nothing&lt;/td&gt;
&lt;td&gt;The block was bypassed at the time, and the reset was consumed&lt;/td&gt;
&lt;td&gt;Enable the block first, then reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; moved again immediately after a reset&lt;/td&gt;
&lt;td&gt;Expected: a reset clears only part of the filter&amp;rsquo;s memory&lt;/td&gt;
&lt;td&gt;Bypass the block for one cycle to clear it fully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A large step on &lt;code&gt;output&lt;/code&gt; after re-enabling&lt;/td&gt;
&lt;td&gt;Expected: the slow-content memory is cleared while bypassed, so the input&amp;rsquo;s offset appears once and washes out&lt;/td&gt;
&lt;td&gt;Re-enable at rest, or gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; and &lt;code&gt;outputDot&lt;/code&gt; came back after a restart with old values&lt;/td&gt;
&lt;td&gt;Expected: both are stored as configuration&lt;/td&gt;
&lt;td&gt;Ignore them until the block has run one cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels filter differently from others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one band and one &lt;code&gt;damping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for isolating a 0.5 Hz oscillation on a 1 ms
task, with the gain left at 1:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omegaHighPass = 1.0
omegaLowPass  = 10.0
damping       = 0.0
enable        = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work step 1 with a trace of your
own signal.&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;omegaHighPass&lt;/code&gt;, &lt;code&gt;omegaLowPass&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; Both must stay under 0.628/task period [s] — 628 rad/s on a 1 ms task. Above that the output grows without bound instead of filtering, and 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;&lt;code&gt;damping&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; At 1.0 the output freezes; above 1.0 it grows without bound. Keep it at 0&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt; exactly 0.0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;That channel&amp;rsquo;s &lt;code&gt;output&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; are set to zero for that cycle, every cycle the condition holds&lt;/td&gt;
&lt;td&gt;Not reported; visible as a channel stuck at zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;, &lt;code&gt;outputDot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. Limit 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;Block state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;, or a bypassed cycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt; clears part of the memory; a bypassed cycle clears the rest. There is no single action that clears all of it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed once the controller starts; it cannot be changed at runtime&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: FIRFilter</title>
      <link>/docs/developing-control-applications/control-blocks/filters/fir-filter/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/fir-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;FIRFilter&lt;/code&gt; filters a signal with a weighted sum of its last few samples. It
has no feedback, which makes it &lt;strong&gt;unconditionally stable&lt;/strong&gt;: no coefficient set
you can write will make it run away. That is the reason to choose it over a
coefficient-driven filter with feedback, and the trade is cost — matching the
same roll-off takes many more taps. It is &lt;strong&gt;single channel&lt;/strong&gt;: one instance
filters one signal.&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 — signal to filter&amp;quot;]) --&amp;gt; B[&amp;quot;FIRFilter&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — filtered signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The filter computes $y[k] = h_0,u[k] + h_1,u[k-1] + \dots + h_N,u[k-N]$,
with $h$ = &lt;code&gt;coefficients&lt;/code&gt;. DC gain is the sum of the coefficients, so an
averaging filter needs $1/M$ in each of its $M$ taps. A symmetric coefficient
set delays the signal by exactly half the tap count, with perfectly linear
phase. A step settles completely after one tap count and never overshoots for
non-negative coefficients. &lt;strong&gt;The coefficients are defined in samples, not
seconds&lt;/strong&gt;, so the same set on a different task rate is a different filter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The tap count is fixed when the machine is built&lt;/strong&gt; and cannot be changed at
runtime. This block also has no reset — 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;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to filter. A single value, not an array.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit × the DC gain&lt;/td&gt;
&lt;td&gt;The filtered signal, a single value. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. Starts from zero after every controller start and reaches its true value after one tap count of samples.&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. A single value for the whole block.&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;coefficients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1 in the first tap, 0 in the rest&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;The taps, most recent sample first. All of them are always used — writing zeros into the tail is the only way to shorten the filter, and it does not make it cheaper. Check the DC gain after every change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both parameters are persistent and survive a controller restart, which is how a
filter design 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;Design your filter against &lt;strong&gt;your task period&lt;/strong&gt;. A 1 ms task is 1000
samples per second, so a six-tap filter spans 6 ms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read the length of the &lt;code&gt;coefficients&lt;/code&gt; array from the parameter tree. That is
your tap count, and it is fixed when the machine is built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link the source signal into &lt;code&gt;input&lt;/code&gt; and confirm on a trace that &lt;code&gt;input&lt;/code&gt;
follows the source.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write the whole &lt;code&gt;coefficients&lt;/code&gt; array, including any trailing zeros. A short
write leaves the old values in the tail, and they keep filtering.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 must cover every tap.&lt;/strong&gt; A leftover coefficient in the tail is the
most common misconfiguration of this block, and it shows up as a filter
that behaves almost right.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed a constant into &lt;code&gt;input&lt;/code&gt; and read &lt;code&gt;output&lt;/code&gt;. Divide the two: the ratio
must equal the sum of your coefficients.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Step the input and count the samples until &lt;code&gt;output&lt;/code&gt; stops moving. It must be
exactly the tap count; anything longer means a coefficient is in a tap you
did not intend.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;p&gt;There is nothing to tune here in the usual sense. The work is choosing the
coefficients and verifying them on the machine.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fix the task period before you design anything. Every coefficient depends
on it. If the task rate changes later, &lt;strong&gt;redesign the whole set&lt;/strong&gt; — no
parameter in this block rescales for you.&lt;/li&gt;
&lt;li&gt;For plain noise reduction, start with an equal-weight average: $1/M$ in each
of $M$ taps. It is the simplest set that works and its DC gain is exactly 1.&lt;/li&gt;
&lt;li&gt;For less ripple at the cost of a slightly wider response, taper the weights
toward the ends — triangular weights are the usual next step.&lt;/li&gt;
&lt;li&gt;Keep the coefficients symmetric if two filtered signals have to stay in step
with each other. Symmetry is what buys exactly linear phase.&lt;/li&gt;
&lt;li&gt;Measure the delay you have bought: half the tap count, in samples, for a
symmetric set. Keep it below a tenth of the response time of the loop
consuming &lt;code&gt;output&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify the DC gain by summing the coefficients. It should be 1 for a filter
that is not meant to change the signal&amp;rsquo;s size.&lt;/li&gt;
&lt;li&gt;Re-verify after any task-rate change, starting from step 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/fir-filter-step-43ec0ed6.svg&#34; alt=&#34;Step response for three coefficient sets: pass-through reaches 1 on the firstsample, a five-tap equal-weight average climbs in five equal steps, andtriangular weights climb in unequal steps to the samevalue.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the settling time off any curve as the sample it first reaches 1.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; is a constant multiple of what you expected&lt;/td&gt;
&lt;td&gt;The coefficients do not sum to 1&lt;/td&gt;
&lt;td&gt;Divide every coefficient by their sum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter behaves almost right but not quite&lt;/td&gt;
&lt;td&gt;A leftover coefficient in the tail of the array&lt;/td&gt;
&lt;td&gt;Write the whole array including trailing zeros, then re-check step 8 of Setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; settles more slowly than the tap count&lt;/td&gt;
&lt;td&gt;A non-zero coefficient sits in a tap you did not intend&lt;/td&gt;
&lt;td&gt;Write zeros over the whole array, then write your set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing zeros into the tail did not make the block cheaper&lt;/td&gt;
&lt;td&gt;Expected: every tap is computed regardless of its value&lt;/td&gt;
&lt;td&gt;Size the instance to the requirement when the machine is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter behaves nothing like the design&lt;/td&gt;
&lt;td&gt;The design assumed a different sample rate&lt;/td&gt;
&lt;td&gt;Redesign at the controller&amp;rsquo;s actual task period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter changed behaviour after a task-rate change&lt;/td&gt;
&lt;td&gt;Expected: the coefficients are defined in samples, not seconds&lt;/td&gt;
&lt;td&gt;Redesign the whole set for the new rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two filtered signals drifted out of step&lt;/td&gt;
&lt;td&gt;The coefficient sets are not both symmetric, or not the same length&lt;/td&gt;
&lt;td&gt;Use symmetric sets of equal length on both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A short glitch on &lt;code&gt;output&lt;/code&gt; for a few samples after re-enabling&lt;/td&gt;
&lt;td&gt;Expected: the filter still holds the samples from before the bypass&lt;/td&gt;
&lt;td&gt;Wait one tap count, or gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus that long&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; ramps up from zero for a few samples after every start&lt;/td&gt;
&lt;td&gt;Expected: the filter memory starts empty&lt;/td&gt;
&lt;td&gt;Gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus one tap count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You cannot flush the filter&amp;rsquo;s memory&lt;/td&gt;
&lt;td&gt;There is no reset in this block&lt;/td&gt;
&lt;td&gt;Bypass and re-enable, then wait one tap count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt; or a coefficient&lt;/td&gt;
&lt;td&gt;Fix the source; the value clears itself after one tap count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need more taps than the instance has&lt;/td&gt;
&lt;td&gt;The tap count is fixed when the machine is built&lt;/td&gt;
&lt;td&gt;It cannot be changed at runtime; it needs a configuration change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to filter several axes&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A five-tap equal-weight average in a six-tap instance, which smooths without
changing the signal&amp;rsquo;s size:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;coefficients = 0.2, 0.2, 0.2, 0.2, 0.2, 0.0
enable       = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A pass-through, which is also what the block ships with:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;coefficients = 1.0, 0.0, 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;Filter stability&lt;/td&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;The block has no feedback, so no coefficient set can make it diverge. This is why you would choose it&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC gain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not normalised and not checked. Whatever the coefficients sum to is the gain you get&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tap count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed when the machine is built. A longer coefficient write has its tail dropped; a shorter one leaves the old tail in place and keeps using it&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter memory&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;There is &lt;strong&gt;no reset input.&lt;/strong&gt; Bypassing the block and re-enabling it is the only way to flush it, and it takes one tap count to clear&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. 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;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: IIRFilter</title>
      <link>/docs/developing-control-applications/control-blocks/filters/iir-filter-3.32/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/iir-filter-3.32/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version c3-version--archived&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter/&#34;&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter-3.32/&#34; selected&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;
    Superseded. The current release is
    &lt;a href=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter/&#34;&gt;3.34&lt;/a&gt;.
  &lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;IIRFilter&lt;/code&gt; runs any discrete filter you can express as a numerator and a
denominator coefficient set — notch, Butterworth, Chebyshev, lead-lag. Where
the named filters give you a cut-off, this one gives you the coefficients
themselves, so a filter design drops straight into configuration with no code
change. It is &lt;strong&gt;single channel&lt;/strong&gt;: one instance filters one signal.&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 — signal to filter&amp;quot;]) --&amp;gt; B[&amp;quot;IIRFilter&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — filtered signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The filter computes $a_0,y[k] = b_0,u[k] + b_1,u[k-1] + \dots - a_1,y[k-1]- a_2,y[k-2] - \dots$,
with $b$ = &lt;code&gt;num&lt;/code&gt; and $a$ = &lt;code&gt;den&lt;/code&gt;. DC gain is the sum
of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt; — check it after every coefficient
change, because nothing normalises it for you. &lt;strong&gt;The coefficients are defined
in samples, not seconds&lt;/strong&gt;, so the same set on a different task rate is a
different filter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;This block is bypassed to zero, not to the input.&lt;/strong&gt; While it is disabled,
&lt;code&gt;output&lt;/code&gt; reads zero and &lt;code&gt;input&lt;/code&gt; reads zero too. Stability is yours to
guarantee — 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;signal unit&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to filter. A single value, not an array. The block writes zero here on every cycle it is disabled, so a trace of &lt;code&gt;input&lt;/code&gt; reads zero while the filter is off.&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 stops the filter and forces &lt;code&gt;output&lt;/code&gt; to zero. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit × the DC gain&lt;/td&gt;
&lt;td&gt;The filtered signal, a single value. &lt;strong&gt;Zero — not the input&lt;/strong&gt; while disabled, while &lt;code&gt;order&lt;/code&gt; is 0, and while &lt;code&gt;den[0]&lt;/code&gt; is 0. Starts from zero after every controller start and plays out the filter&amp;rsquo;s full transient.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. It does not tell you the filter is actually running: a zero &lt;code&gt;order&lt;/code&gt; or a zero &lt;code&gt;den[0]&lt;/code&gt; stops it 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;order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0 – the array length minus 1&lt;/td&gt;
&lt;td&gt;How many coefficients are used. A value above the ceiling is corrected silently. &lt;strong&gt;0 stops the filter and outputs zero&lt;/strong&gt; — it is not a constant gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1, 0, 0, 0, 0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Numerator coefficients $b_0 … b_N$, most recent sample first. Set these together with &lt;code&gt;den&lt;/code&gt;, then check the DC gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;den&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1, 0, 0, 0, 0&lt;/td&gt;
&lt;td&gt;any, but &lt;code&gt;den[0]&lt;/code&gt; must not be 0&lt;/td&gt;
&lt;td&gt;Denominator coefficients $a_0 … a_N$. &lt;code&gt;den[0]&lt;/code&gt; divides every output sample, so &lt;strong&gt;&lt;code&gt;den[0]&lt;/code&gt; = 0 stops the filter&lt;/strong&gt; and outputs zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False stops the filter and forces &lt;code&gt;output&lt;/code&gt; to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All four parameters are persistent and survive a controller restart, which is
how a filter design 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;Design your filter against &lt;strong&gt;your task period&lt;/strong&gt;, in whatever tool you use,
and export the coefficients. A 1 ms task is 1000 samples per second.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link the source signal into &lt;code&gt;input&lt;/code&gt; and confirm on a trace that &lt;code&gt;input&lt;/code&gt;
follows the source while the block is enabled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; reads zero and so does &lt;code&gt;input&lt;/code&gt;. This is normal
for this block and is not a broken link.&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;num&lt;/code&gt;, then &lt;code&gt;den&lt;/code&gt;. Read &lt;code&gt;order&lt;/code&gt; back — a lower
value means you exceeded the array length.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the array length by writing &lt;code&gt;order&lt;/code&gt; to a large number and reading it
back. The value you get is the highest order this instance supports; it is
fixed when the machine is built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed a constant into &lt;code&gt;input&lt;/code&gt; and read &lt;code&gt;output&lt;/code&gt;. Divide the two: the ratio
must match the sum of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 is the check that catches an unnormalised coefficient set.&lt;/strong&gt; A
set that is right up to a scale factor gives a correctly-shaped filter
with the wrong gain, and every loop downstream of it is then mistuned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Step the input and watch &lt;code&gt;output&lt;/code&gt;. If it grows instead of settling, your
denominator has an unstable root — set &lt;code&gt;enable&lt;/code&gt; false immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;p&gt;There is nothing to tune here in the usual sense. The work is designing the
coefficients and verifying them on the machine.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fix the task period before you design anything. Every coefficient depends
on it. If the task rate changes later, &lt;strong&gt;redesign the whole set&lt;/strong&gt; — no
parameter in this block rescales for you.&lt;/li&gt;
&lt;li&gt;Design in your own tool, then verify the DC gain by hand:
the sum of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt; should be 1 for a filter that
is not meant to change the signal&amp;rsquo;s size.&lt;/li&gt;
&lt;li&gt;Confirm stability before you enable it. Every root of the denominator
polynomial must lie inside the unit circle. The block does not check this
and will happily run a filter that diverges.&lt;/li&gt;
&lt;li&gt;Step the input and read the settling time and any overshoot off &lt;code&gt;output&lt;/code&gt;.
Compare them against what your design predicted; a mismatch usually means
the sample rate assumed in the design tool was wrong.&lt;/li&gt;
&lt;li&gt;Keep the order as low as the requirement allows. A high-order set with
coefficients spanning many orders of magnitude loses precision, and a
narrow notch is the usual culprit.&lt;/li&gt;
&lt;li&gt;Re-verify after any task-rate change, starting from step 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/iir-filter-step-c5eea095.svg&#34; alt=&#34;Step response for three coefficient sets: pass-through reaches 1 immediately,a 2nd-order Butterworth at 10 Hz rises with a small overshoot, and a 1st-orderlow-pass at omega 10 crosses 0.63 at 0.1 s.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the settling time off any curve as the point it stops moving toward 1.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; grew without bound until something tripped&lt;/td&gt;
&lt;td&gt;A denominator root outside the unit circle&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;enable&lt;/code&gt; false, redesign the coefficients, and re-check step 3 of Tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; is a constant multiple of what you expected&lt;/td&gt;
&lt;td&gt;The coefficient set is not normalised&lt;/td&gt;
&lt;td&gt;Rescale &lt;code&gt;num&lt;/code&gt; so the sums divide to 1, or rescale in your design tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; reads 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, or &lt;code&gt;den[0]&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Write a non-zero &lt;code&gt;den[0]&lt;/code&gt; and an &lt;code&gt;order&lt;/code&gt; of at least 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt; reads zero on a trace while the filter is off&lt;/td&gt;
&lt;td&gt;Expected: the block zeroes its own input while disabled&lt;/td&gt;
&lt;td&gt;Enable the block before you judge the link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; reads zero as soon as you disabled the filter&lt;/td&gt;
&lt;td&gt;Expected: this block bypasses to zero, not to the input&lt;/td&gt;
&lt;td&gt;Use a switch block downstream if you need the raw signal while bypassed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt; reads back lower than written&lt;/td&gt;
&lt;td&gt;Above the array length for this instance&lt;/td&gt;
&lt;td&gt;Work within the value you read back; the length is fixed when the machine is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The coefficients went to zero after you set the order&lt;/td&gt;
&lt;td&gt;Setting the order clears the coefficient arrays&lt;/td&gt;
&lt;td&gt;Always write &lt;code&gt;order&lt;/code&gt; first, then &lt;code&gt;num&lt;/code&gt; and &lt;code&gt;den&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter behaves nothing like the design&lt;/td&gt;
&lt;td&gt;The design assumed a different sample rate&lt;/td&gt;
&lt;td&gt;Redesign at the controller&amp;rsquo;s actual task period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter changed behaviour after a task-rate change&lt;/td&gt;
&lt;td&gt;Expected: the coefficients are defined in samples, not seconds&lt;/td&gt;
&lt;td&gt;Redesign the whole set for the new rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A high-order coefficient still has an effect after you shortened the set&lt;/td&gt;
&lt;td&gt;The unused tail of &lt;code&gt;num&lt;/code&gt; or &lt;code&gt;den&lt;/code&gt; was left in place&lt;/td&gt;
&lt;td&gt;Write zeros over the whole array, then write the new set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; plays out a large transient every time the filter is enabled&lt;/td&gt;
&lt;td&gt;Expected: the filter memory starts from zero, so the full transient runs&lt;/td&gt;
&lt;td&gt;Enable at rest, or gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus the settling time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt; or a coefficient&lt;/td&gt;
&lt;td&gt;Disable the block for one cycle — that clears the filter memory — then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to filter several axes&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A pass-through starting point, which is also what the block ships with:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;order = 1
num   = 1, 0
den   = 1, 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A 2nd-order Butterworth low-pass at 10 Hz &lt;strong&gt;for a 1 ms task only&lt;/strong&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;order = 2
num   = 0.0009447, 0.0018894, 0.0009447
den   = 1, -1.9111971, 0.9149758
&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;Filter stability&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 denominator with a root outside the unit circle diverges, and the block runs it. Only a controller restart or disabling the block clears the state&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;den[0]&lt;/code&gt; ≠ 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A zero &lt;code&gt;den[0]&lt;/code&gt; stops the filter and &lt;code&gt;output&lt;/code&gt; reads zero&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;order&lt;/code&gt; ≤ array length − 1&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;A larger value is replaced by the ceiling&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;order&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC gain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not normalised and not checked. Whatever the coefficients imply is what you get&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. 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;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;tr&gt;
&lt;td&gt;Array length&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed when the machine is built and not readable directly — discover it with Setup step 5&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: IIRFilter</title>
      <link>/docs/developing-control-applications/control-blocks/filters/iir-filter/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/iir-filter/</guid>
      <description>
        
        
        &lt;div class=&#34;c3-version&#34;&gt;
  &lt;span class=&#34;c3-version__control&#34;&gt;
    &lt;span class=&#34;c3-version__label&#34; aria-hidden=&#34;true&#34;&gt;Control3&lt;/span&gt;
    &lt;select class=&#34;c3-version__select&#34;
            aria-label=&#34;Show this page as it was in another Control3 version&#34;
            onchange=&#34;if(this.value)window.location.href=this.value;&#34;&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter/&#34; selected&gt;3.34 (latest)&lt;/option&gt;
      &lt;option value=&#34;/docs/developing-control-applications/control-blocks/filters/iir-filter-3.32/&#34;&gt;3.30–3.32&lt;/option&gt;
    &lt;/select&gt;
  &lt;/span&gt;
  &lt;span class=&#34;c3-version__note&#34;&gt;Current release&lt;/span&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;IIRFilter&lt;/code&gt; runs any discrete filter you can express as a numerator and a
denominator coefficient set — notch, Butterworth, Chebyshev, lead-lag. Where
the named filters give you a cut-off, this one gives you the coefficients
themselves, so a filter design drops straight into configuration with no code
change. It is &lt;strong&gt;single channel&lt;/strong&gt;: one instance filters one signal.&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 — signal to filter&amp;quot;]) --&amp;gt; B[&amp;quot;IIRFilter&amp;quot;]
    i2([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — filtered signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;The filter computes $a_0,y[k] = b_0,u[k] + b_1,u[k-1] + \dots - a_1,y[k-1]- a_2,y[k-2] - \dots$,
with $b$ = &lt;code&gt;num&lt;/code&gt; and $a$ = &lt;code&gt;den&lt;/code&gt;. DC gain is the sum
of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt; — check it after every coefficient
change, because nothing normalises it for you. &lt;strong&gt;The coefficients are defined
in samples, not seconds&lt;/strong&gt;, so the same set on a different task rate is a
different filter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;This block is bypassed to zero, not to the input.&lt;/strong&gt; While it is disabled,
&lt;code&gt;output&lt;/code&gt; reads zero and &lt;code&gt;input&lt;/code&gt; reads zero too. Stability is yours to
guarantee — see Limits and errors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not put this block in a setpoint path unguarded.&lt;/strong&gt; Because a stopped
filter outputs zero rather than its input, &lt;code&gt;disable&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt; = 0 or
&lt;code&gt;den[0]&lt;/code&gt; = 0 on a position, velocity or torque &lt;em&gt;target&lt;/em&gt; is a command to zero.
An application in C++ can guard the path with &lt;code&gt;IIRFilter::getIsFiltering()&lt;/code&gt;,
which is false in exactly those three cases, and substitute the unfiltered
signal — this is what &lt;code&gt;ActuatorControlLoop&lt;/code&gt; does for its four target filters,
so those four are safe to disable at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The coefficient arrays hold &lt;strong&gt;7 entries by default&lt;/strong&gt;, so orders up to 6 — two
notches plus a lowpass. An instance can be built with a different ceiling; read
it back rather than assuming (see Setup, step 5).&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&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to filter. A single value, not an array. The block writes zero here on every cycle it is disabled, so a trace of &lt;code&gt;input&lt;/code&gt; reads zero while the filter is off.&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 stops the filter and forces &lt;code&gt;output&lt;/code&gt; to zero. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit × the DC gain&lt;/td&gt;
&lt;td&gt;The filtered signal, a single value. &lt;strong&gt;Zero — not the input&lt;/strong&gt; while disabled, while &lt;code&gt;order&lt;/code&gt; is 0, and while &lt;code&gt;den[0]&lt;/code&gt; is 0. Starts from zero after every controller start and plays out the filter&amp;rsquo;s full transient.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;True when &lt;code&gt;enable&lt;/code&gt; is true and &lt;code&gt;disable&lt;/code&gt; is false. It does not tell you the filter is actually running: a zero &lt;code&gt;order&lt;/code&gt; or a zero &lt;code&gt;den[0]&lt;/code&gt; stops it 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;order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0 – the array length minus 1&lt;/td&gt;
&lt;td&gt;How many coefficients are used. A value above the ceiling is corrected silently. &lt;strong&gt;0 stops the filter and outputs zero&lt;/strong&gt; — it is not a constant gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1, 0, 0, 0, 0, 0, 0&lt;/td&gt;
&lt;td&gt;any&lt;/td&gt;
&lt;td&gt;Numerator coefficients $b_0 … b_N$, most recent sample first. Set these together with &lt;code&gt;den&lt;/code&gt;, then check the DC gain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;den&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1, 0, 0, 0, 0, 0, 0&lt;/td&gt;
&lt;td&gt;any, but &lt;code&gt;den[0]&lt;/code&gt; must not be 0&lt;/td&gt;
&lt;td&gt;Denominator coefficients $a_0 … a_N$. &lt;code&gt;den[0]&lt;/code&gt; divides every output sample, so &lt;strong&gt;&lt;code&gt;den[0]&lt;/code&gt; = 0 stops the filter&lt;/strong&gt; and outputs zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False stops the filter and forces &lt;code&gt;output&lt;/code&gt; to zero.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All four parameters are persistent and survive a controller restart, which is
how a filter design 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;Design your filter against &lt;strong&gt;your task period&lt;/strong&gt;, in whatever tool you use,
and export the coefficients. A 1 ms task is 1000 samples per second.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Link the source signal into &lt;code&gt;input&lt;/code&gt; and confirm on a trace that &lt;code&gt;input&lt;/code&gt;
follows the source while the block is enabled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; reads zero and so does &lt;code&gt;input&lt;/code&gt;. This is normal
for this block and is not a broken link.&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;num&lt;/code&gt;, then &lt;code&gt;den&lt;/code&gt;. Read &lt;code&gt;order&lt;/code&gt; back — a lower
value means you exceeded the array length.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the array length by writing &lt;code&gt;order&lt;/code&gt; to a large number and reading it
back. The value you get is the highest order this instance supports; it is
fixed when the machine is built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true and confirm &lt;code&gt;isEnabled&lt;/code&gt; reads true.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed a constant into &lt;code&gt;input&lt;/code&gt; and read &lt;code&gt;output&lt;/code&gt;. Divide the two: the ratio
must match the sum of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 7 is the check that catches an unnormalised coefficient set.&lt;/strong&gt; A
set that is right up to a scale factor gives a correctly-shaped filter
with the wrong gain, and every loop downstream of it is then mistuned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Step the input and watch &lt;code&gt;output&lt;/code&gt;. If it grows instead of settling, your
denominator has an unstable root — set &lt;code&gt;enable&lt;/code&gt; false immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tuning&#34;&gt;Tuning&lt;/h2&gt;
&lt;p&gt;There is nothing to tune here in the usual sense. The work is designing the
coefficients and verifying them on the machine.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fix the task period before you design anything. Every coefficient depends
on it. If the task rate changes later, &lt;strong&gt;redesign the whole set&lt;/strong&gt; — no
parameter in this block rescales for you.&lt;/li&gt;
&lt;li&gt;Design in your own tool, then verify the DC gain by hand:
the sum of &lt;code&gt;num&lt;/code&gt; divided by the sum of &lt;code&gt;den&lt;/code&gt; should be 1 for a filter that
is not meant to change the signal&amp;rsquo;s size.&lt;/li&gt;
&lt;li&gt;Confirm stability before you enable it. Every root of the denominator
polynomial must lie inside the unit circle. The block does not check this
and will happily run a filter that diverges.&lt;/li&gt;
&lt;li&gt;Step the input and read the settling time and any overshoot off &lt;code&gt;output&lt;/code&gt;.
Compare them against what your design predicted; a mismatch usually means
the sample rate assumed in the design tool was wrong.&lt;/li&gt;
&lt;li&gt;Keep the order as low as the requirement allows. A high-order set with
coefficients spanning many orders of magnitude loses precision, and a
narrow notch is the usual culprit.&lt;/li&gt;
&lt;li&gt;Re-verify after any task-rate change, starting from step 1.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/iir-filter-step-c5eea095.svg&#34; alt=&#34;Step response for three coefficient sets: pass-through reaches 1 immediately,a 2nd-order Butterworth at 10 Hz rises with a small overshoot, and a 1st-orderlow-pass at omega 10 crosses 0.63 at 0.1 s.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the settling time off any curve as the point it stops moving toward 1.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; grew without bound until something tripped&lt;/td&gt;
&lt;td&gt;A denominator root outside the unit circle&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;enable&lt;/code&gt; false, redesign the coefficients, and re-check step 3 of Tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; is a constant multiple of what you expected&lt;/td&gt;
&lt;td&gt;The coefficient set is not normalised&lt;/td&gt;
&lt;td&gt;Rescale &lt;code&gt;num&lt;/code&gt; so the sums divide to 1, or rescale in your design tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; reads 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, or &lt;code&gt;den[0]&lt;/code&gt; is 0&lt;/td&gt;
&lt;td&gt;Write a non-zero &lt;code&gt;den[0]&lt;/code&gt; and an &lt;code&gt;order&lt;/code&gt; of at least 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt; reads zero on a trace while the filter is off&lt;/td&gt;
&lt;td&gt;Expected: the block zeroes its own input while disabled&lt;/td&gt;
&lt;td&gt;Enable the block before you judge the link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; reads zero as soon as you disabled the filter&lt;/td&gt;
&lt;td&gt;Expected: this block bypasses to zero, not to the input&lt;/td&gt;
&lt;td&gt;Use a switch block downstream if you need the raw signal while bypassed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;order&lt;/code&gt; reads back lower than written&lt;/td&gt;
&lt;td&gt;Above the array length for this instance&lt;/td&gt;
&lt;td&gt;Work within the value you read back; the length is fixed when the machine is built&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The coefficients went to zero after you set the order&lt;/td&gt;
&lt;td&gt;Setting the order clears the coefficient arrays&lt;/td&gt;
&lt;td&gt;Always write &lt;code&gt;order&lt;/code&gt; first, then &lt;code&gt;num&lt;/code&gt; and &lt;code&gt;den&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter behaves nothing like the design&lt;/td&gt;
&lt;td&gt;The design assumed a different sample rate&lt;/td&gt;
&lt;td&gt;Redesign at the controller&amp;rsquo;s actual task period&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter changed behaviour after a task-rate change&lt;/td&gt;
&lt;td&gt;Expected: the coefficients are defined in samples, not seconds&lt;/td&gt;
&lt;td&gt;Redesign the whole set for the new rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A high-order coefficient still has an effect after you shortened the set&lt;/td&gt;
&lt;td&gt;The unused tail of &lt;code&gt;num&lt;/code&gt; or &lt;code&gt;den&lt;/code&gt; was left in place&lt;/td&gt;
&lt;td&gt;Write zeros over the whole array, then write the new set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; plays out a large transient every time the filter is enabled&lt;/td&gt;
&lt;td&gt;Expected: the filter memory starts from zero, so the full transient runs&lt;/td&gt;
&lt;td&gt;Enable at rest, or gate the consumer off &lt;code&gt;isEnabled&lt;/code&gt; plus the settling time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value and stays there&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt; or a coefficient&lt;/td&gt;
&lt;td&gt;Disable the block for one cycle — that clears the filter memory — then fix the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need to filter several axes&lt;/td&gt;
&lt;td&gt;Not possible — this block is single channel&lt;/td&gt;
&lt;td&gt;Use one instance per axis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A pass-through starting point, which is also what the block ships with:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;order = 1
num   = 1, 0
den   = 1, 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A 2nd-order Butterworth low-pass at 10 Hz &lt;strong&gt;for a 1 ms task only&lt;/strong&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;order = 2
num   = 0.0009447, 0.0018894, 0.0009447
den   = 1, -1.9111971, 0.9149758
&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;Filter stability&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 denominator with a root outside the unit circle diverges, and the block runs it. Only a controller restart or disabling the block clears the state&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;den[0]&lt;/code&gt; ≠ 0&lt;/td&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;A zero &lt;code&gt;den[0]&lt;/code&gt; stops the filter and &lt;code&gt;output&lt;/code&gt; reads zero&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;order&lt;/code&gt; ≤ array length − 1&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;A larger value is replaced by the ceiling&lt;/td&gt;
&lt;td&gt;Silently; read &lt;code&gt;order&lt;/code&gt; back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC gain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not normalised and not checked. Whatever the coefficients imply is what you get&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded. 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;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;tr&gt;
&lt;td&gt;Array length&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed when the machine is built and not readable directly — discover it with Setup step 5&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: MovingAverageFilter</title>
      <link>/docs/developing-control-applications/control-blocks/filters/moving-average-filter/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>/docs/developing-control-applications/control-blocks/filters/moving-average-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;MovingAverageFilter&lt;/code&gt; smooths a noisy signal by averaging it over time, tuned
by a &lt;strong&gt;sample count&lt;/strong&gt; rather than by a frequency. It is the filter to use when
you think in samples: &amp;ldquo;average this over a hundred readings&amp;rdquo;. Its memory cost
is the same at any sample count, so a very long average is as cheap as a short
one.&lt;/p&gt;
&lt;p&gt;It averages by weighted decay, not over a fixed window: older samples fade out
rather than dropping out. So &lt;code&gt;output&lt;/code&gt; approaches a step gradually and does not
finish after &lt;code&gt;numberOfSamples&lt;/code&gt; cycles — it reaches 63% at that point and 90% at
a little over twice it.&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 — signal to smooth&amp;quot;]) --&amp;gt; B[&amp;quot;MovingAverageFilter&amp;quot;]
    i2([&amp;quot;reset&amp;quot;]) --&amp;gt; B
    i3([&amp;quot;disable&amp;quot;]) --&amp;gt; B
    B --&amp;gt; o1([&amp;quot;output — smoothed signal&amp;quot;])
    B --&amp;gt; o2([&amp;quot;isEnabled&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Time constant $\tau = (N+1) \times$ task period [s], with $N$ =
&lt;code&gt;numberOfSamples&lt;/code&gt;. At the default 100 on a 1 ms task that is 0.101 s, a
cut-off of 1.58 Hz: 63% of a step in 0.1 s, 90% in 0.23 s. Phase lag at the
cut-off is 45°, and that lag is the whole trade. Note the time constant
scales with the task period, so the same sample count is a different filter
in seconds on a different task rate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;For the first &lt;code&gt;numberOfSamples&lt;/code&gt; cycles after a start or a reset the filter
converges faster&lt;/strong&gt; than the numbers above, because it is averaging everything
it has seen rather than decaying. That is deliberate and useful.&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&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;The signal to smooth. One element per channel; the channel count is fixed by the machine configuration.&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 &lt;code&gt;output&lt;/code&gt; to &lt;code&gt;input&lt;/code&gt; immediately and restarts the fast-converging phase. It affects &lt;strong&gt;every channel at once&lt;/strong&gt;, and it works whether the block is enabled or not. Use it after a large step you do not want the filter to average across.&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 bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes straight to &lt;code&gt;output&lt;/code&gt;. Use it for a runtime override from a supervisor; use &lt;code&gt;enable&lt;/code&gt; for the configured intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;outputs&#34;&gt;Outputs&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;signal unit&lt;/td&gt;
&lt;td&gt;The smoothed signal, one element per channel. Equals &lt;code&gt;input&lt;/code&gt; exactly while bypassed. After a controller start its &lt;strong&gt;first value is half of &lt;code&gt;input&lt;/code&gt;&lt;/strong&gt;, then it converges — gate the consumer if that matters.&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. A single value for the whole block, not one per channel.&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;numberOfSamples&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;samples&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;0 upward; no upper limit&lt;/td&gt;
&lt;td&gt;How heavily to average, shared by all channels. Higher removes more noise and adds more delay, at no extra cost in memory or computation. 0 is a pass-through. Any value is safe — this filter cannot be made unstable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;False bypasses the filter: &lt;code&gt;input&lt;/code&gt; passes through unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both 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;Link the source signal: &lt;code&gt;…/sensor/rawPressure&lt;/code&gt; → &lt;code&gt;…/pressureFilter/input&lt;/code&gt;.
&lt;code&gt;input&lt;/code&gt; follows the source on a trace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; false. &lt;code&gt;output&lt;/code&gt; equals &lt;code&gt;input&lt;/code&gt; sample for sample and
&lt;code&gt;isEnabled&lt;/code&gt; reads false.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;numberOfSamples&lt;/code&gt; to 10. Any value is accepted, so there is nothing to
read back and check.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;enable&lt;/code&gt; true. &lt;code&gt;isEnabled&lt;/code&gt; reads true and &lt;code&gt;output&lt;/code&gt; tracks &lt;code&gt;input&lt;/code&gt;
closely, with the noise still visible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Raise &lt;code&gt;numberOfSamples&lt;/code&gt; in steps of roughly double, watching the noise and
the loop consuming &lt;code&gt;output&lt;/code&gt;. Stop one step below where that loop feels soft
or hunts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 changes what the controller sees.&lt;/strong&gt; Each doubling of the sample
count doubles the delay. Raise it with the axis at rest before trying it
in motion — the added delay can destabilise a tightly tuned loop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Step the input and time how long &lt;code&gt;output&lt;/code&gt; takes to reach 63% of the step.
It should be about &lt;code&gt;numberOfSamples&lt;/code&gt; plus one task periods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trace every element of &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;. An element that reads zero
while the machine moves is an unwired channel, not a filtered one.&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;Read the ripple you want gone off a trace of &lt;code&gt;input&lt;/code&gt;, and count its period
in task cycles. Set &lt;code&gt;numberOfSamples&lt;/code&gt; to at least a few times that count.&lt;/li&gt;
&lt;li&gt;Measure the delay you have bought: &lt;code&gt;numberOfSamples&lt;/code&gt; plus one task periods
is the time constant, and the delay below the cut-off equals it.&lt;/li&gt;
&lt;li&gt;Keep that delay below a tenth of the response time of the loop consuming
&lt;code&gt;output&lt;/code&gt;. Above that, the loop pays for the smoothing in stability.&lt;/li&gt;
&lt;li&gt;Double &lt;code&gt;numberOfSamples&lt;/code&gt; to halve the noise. It is a cheap knob — there is
no memory or computation penalty for a large value — so the only cost is
delay.&lt;/li&gt;
&lt;li&gt;Check the loop in motion, not at rest. Delay costs phase margin only while
the axis is moving fast enough to need it.&lt;/li&gt;
&lt;li&gt;Re-check the sample count after any task-rate change. The count stays the
same in samples but the delay in seconds moves with the task period.&lt;/li&gt;
&lt;li&gt;If you need a filter with a hard cut at a known frequency — to kill a mains
harmonic, say — this block cannot do it. A finite-impulse-response filter
with equal taps can.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/control3/img/moving-average-filter-step-cec28e30.svg&#34; alt=&#34;Step response at three sample counts: 10 samples settles within 0.03 s, thedefault 100 crosses 0.63 at about 0.1 s, and 500 is still climbing after asecond.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Read the time constant off any curve as the moment it crosses 0.63.&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;Noise still present on &lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sample count too low&lt;/td&gt;
&lt;td&gt;Double &lt;code&gt;numberOfSamples&lt;/code&gt;, then re-check the loop for softness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop went soft, hunts or oscillates after enabling&lt;/td&gt;
&lt;td&gt;Delay too high for the loop&lt;/td&gt;
&lt;td&gt;Halve &lt;code&gt;numberOfSamples&lt;/code&gt;; if the noise returns, fix it at the source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; had not finished settling after &lt;code&gt;numberOfSamples&lt;/code&gt; cycles&lt;/td&gt;
&lt;td&gt;Expected: the filter decays rather than using a fixed window, so it reaches 63% at that point&lt;/td&gt;
&lt;td&gt;Wait about 2.3 times the sample count for 90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setpoint tracking lags behind the command&lt;/td&gt;
&lt;td&gt;The block is in the command path, not the feedback path&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;numberOfSamples&lt;/code&gt;, or move the filter onto the measurement only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The filter converged quickly just after start, then got sluggish&lt;/td&gt;
&lt;td&gt;Expected: it averages everything it has seen for the first &lt;code&gt;numberOfSamples&lt;/code&gt; cycles, then switches to steady decay&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;reset&lt;/code&gt; to get the fast phase back deliberately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; was half the input on the first cycle after a start&lt;/td&gt;
&lt;td&gt;Expected behaviour of the first sample&lt;/td&gt;
&lt;td&gt;Gate the consumer off a short delay after start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; averaged across a step you wanted it to follow&lt;/td&gt;
&lt;td&gt;The filter has no way to know the step was intentional&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt; at the moment of the step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resetting one channel restarted the averaging on all of them&lt;/td&gt;
&lt;td&gt;Expected: the sample count is shared across channels&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel if they need independent resets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changing &lt;code&gt;numberOfSamples&lt;/code&gt; had no immediate effect&lt;/td&gt;
&lt;td&gt;Expected: the filter is still in its fast-converging phase, where the count does not yet apply&lt;/td&gt;
&lt;td&gt;Pulse &lt;code&gt;reset&lt;/code&gt;, or wait for the count to be reached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; follows &lt;code&gt;input&lt;/code&gt; exactly, with no smoothing at all&lt;/td&gt;
&lt;td&gt;&lt;code&gt;numberOfSamples&lt;/code&gt; is 0, or &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; and &lt;code&gt;numberOfSamples&lt;/code&gt;; 0 is a pass-through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The delay changed after a task-rate change&lt;/td&gt;
&lt;td&gt;Expected: the time constant is the sample count times the task period&lt;/td&gt;
&lt;td&gt;Rescale &lt;code&gt;numberOfSamples&lt;/code&gt; by the ratio of the task periods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need a hard cut at a specific frequency&lt;/td&gt;
&lt;td&gt;This filter has no such thing — it is a gentle first-order roll-off&lt;/td&gt;
&lt;td&gt;Use a finite-impulse-response filter with equal taps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some channels smooth more than others&lt;/td&gt;
&lt;td&gt;Not possible — all channels share one sample count&lt;/td&gt;
&lt;td&gt;Use a separate filter per channel group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt; went to a non-numeric value&lt;/td&gt;
&lt;td&gt;A non-numeric value reached &lt;code&gt;input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix the source, then pulse &lt;code&gt;reset&lt;/code&gt; — that clears the filter completely&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A conservative starting point for a pressure signal on a 1 ms task, feeding a
loop with a 100 ms response time:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;numberOfSamples = 50
enable          = true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a starting point, not a final tuning. Work step 1 with a trace of your
own signal.&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;numberOfSamples&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Nothing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not checked, and it does not need to be — no value can make this filter unstable. 0 is a pass-through&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter stability&lt;/td&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;Stable at every sample count and every task rate. Unlike the frequency-tuned filters, there is no task-rate ceiling to respect&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Unbounded — whatever the input carries reaches the output. 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;Filter memory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;, or a bypassed cycle&lt;/td&gt;
&lt;td&gt;Both set &lt;code&gt;output&lt;/code&gt; to &lt;code&gt;input&lt;/code&gt; and clear the filter completely. There is always a way out of a bad state&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel count&lt;/td&gt;
&lt;td&gt;Machine configuration&lt;/td&gt;
&lt;td&gt;Fixed once the controller starts; it cannot be changed at runtime&lt;/td&gt;
&lt;td&gt;Not reported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The block raises no errors or warnings and logs nothing. Every failure above
shows as a value on a trace, not as a message.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Verified against motorcortex-control3 3.30.0 (bc348fd).&lt;/p&gt;

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