PID_VariableGainControl

PID_VariableGainControl closes a control loop with a proportional, integral and derivative term.
Current release

PID_VariableGainControl closes a control loop with a proportional, integral and derivative term. Its integral only accumulates while the error is large enough and the machine is slow enough, and its derivative is filtered so sensor noise is not amplified without bound. It is single channel: one instance closes one loop.

The block is named for a second, variable-gain integrator intended to reject low-frequency disturbance without the usual phase-lag penalty. That branch is not exposed — no parameter enables it and it contributes nothing. For new work use PID, which is the same controller with output limits, stronger anti-windup and more conventional parameter naming.

Deprecated — scheduled for removal. PID is the PID to use for new work. With the variable-gain branch dead, this block is PID without the anti-windup rework — no output saturation, no back-calculation, no directional conditional integration, no integrator deadband — and with the derivative filter spelled Nd rather than nd. Its only consumer, FeedbackController, is deprecated with it. Nothing else in the library instantiates either. This block is kept only until the last machine is off it, and will then be deleted.

Swapping to PID is close to a rename. kp, ki, kd, iMin, iMax, iPositionErrorThreshold* and iActualVelocityWindow* all carry over unchanged, and the proportional, integral and derivative arithmetic is the same. Two things to watch: the derivative filter leaf is nd there, not Nd; and PID integrates the deadband residual of the error rather than the raw error, so a non-zero controlErrorDeadBand changes the integral — it defaults to 0, which reproduces this block exactly.

Do not try to revive the variable-gain branch as it stands. iTermVGC has no clamp of any kind — iMax/iMin bound the ordinary integral term only — so a one-sided error outside the deadband charges it without bound, and iReset does not discharge it. Its only discharge path is a sign change of the error, and that test is asymmetric about zero. The technique itself is sound and published (Heertjes and van de Wouw 2007; Heertjes et al. 2019, 2020); the implementation is not finished.

flowchart LR
    i1(["error — reference minus actual"]) --> B["PID_VariableGainControl"]
    i2(["actual — feeds the velocity gate only"]) --> B
    i3(["iReset"]) --> B
    B --> o1(["output — the control signal"])
    B --> o2(["iTerm — integral contribution"])
    B --> o3(["dTerm — derivative contribution"])

$u = k_p e + k_i !\int! e,dt + k_d \frac{de}{dt}$. Nd is the derivative filter frequency in rad/s: the D action rolls off above it and its high-frequency gain is capped at $k_d \times$ Nd. Keep Nd below $1/$ task period [s] for a clean response — 1000 on a 1 ms task — and never above $2/$ task period, where it grows without bound.

The derivative filter is Nd here, with a capital N. PID registers the same quantity as nd. Configuration files are not interchangeable between the two blocks on this point.

This block has no enable, no disable and no output limit. iMax and iMin default to ±0.1 and are the only protection against integral wind-up — set them before you set ki.

Signals

Inputs

Path Unit Range Description
error signal unit unbounded The control error: reference minus actual. A single value, not an array. This is the only input the control law acts on.
actual signal unit unbounded The measured value. It feeds the velocity gate only and plays no part in computing the output. Leave it at 0 if you are not using the velocity window.
iReset - - While true, the integral term is held at zero and stops accumulating. The block becomes a proportional-derivative controller for as long as you hold it. It is a level, not a pulse. It does not clear dTerm.

Outputs

Path Unit Description
output output unit The control signal — the sum of all terms, with no limit applied. Starts from zero after every controller start, but the integral state is not cleared by a stop.
iTerm output unit The integral term’s accumulated value, after this cycle’s update, so it and output reconcile sample for sample. Watch it to see wind-up as it happens.
dTerm output unit The filtered derivative term. Not cleared by iReset, and not cleared by a stop.

Parameters

Path Unit Default Range Effect
kp output unit per signal unit 0.0 any Proportional gain. Raising it makes the loop stiffer and faster, and eventually makes it oscillate.
ki output unit per signal unit per second 0.0 any Integral gain. It removes steady-state error. It does nothing useful until iMax and iMin are set wide enough.
kd output unit per signal unit per second 0.0 any Derivative gain. It damps overshoot and amplifies noise. Set Nd before you use it.
Nd rad/s 1.0 above 0, below 1/task period [s] in practice Derivative filter frequency, capital N. Higher gives a sharper, noisier D action. Not checked — see Limits and errors. At the default of 1.0 the D action is filtered so heavily that kd barely acts.
iPositionErrorThresholdPos signal unit 0.0 any The integral accumulates only while the error is above this or below the negative threshold. At the default of 0 it accumulates whenever the error is not exactly zero.
iPositionErrorThresholdNeg signal unit 0.0 any The lower half of the same gate.
iActualVelocityWindowPos signal unit per second +infinity any The integral accumulates only while the rate of change of actual is inside this window. The default admits everything.
iActualVelocityWindowNeg signal unit per second −infinity any The lower half of the same window. Writing 0 to both closes the gate permanently and the integral never accumulates.
iMax output unit 0.1 above iMin Upper bound on the integral term, and the block’s only wind-up protection. Raise it before tuning ki.
iMin output unit −0.1 below iMax Lower bound. Setting it above iMax pins the integral at iMin — see Limits and errors.

All ten parameters are persistent and survive a controller restart. The inputs and outputs do not. No parameters exist below this block.

Setup

  1. Set iMax and iMin to the largest integral contribution you will accept. Do this first. There is no output limit in this block, so these two are all that stands between a wound integrator and the actuator.

    Step 1 is the only wind-up protection you get. Unlike PID, this block has no output clamp and no back-calculation. If a wound integrator reaching the actuator would be unsafe, use PID instead.

  2. Set kp, ki and kd to 0, and Nd to something sensible for your task rate — 50 to 200 rad/s is a normal starting range on a 1 ms task.

  3. Link error from your comparison. Link actual only if you intend to use the velocity window; otherwise leave it at 0.

  4. Raise kp from 0 until the loop responds usefully. output should track error × kp exactly while ki and kd are still 0.

  5. Raise ki from 0 until steady-state error disappears. Watch iTerm — if it sits pinned at iMax or iMin, go back to step 1.

  6. Raise kd from 0 only if you need to damp overshoot. Watch dTerm on a trace; if it is noisy, lower Nd.

  7. Bound output in a limiter downstream. This block will not do it for you.

Tuning

  1. Set iMin and iMax before any gain, per Setup step 1.
  2. Tune kp alone. Raise it until the response is fast enough, then back off until any oscillation is gone. Leave ki and kd at 0 throughout.
  3. Add ki next. Raise it until steady-state error is removed within an acceptable time. Too much shows as a slow oscillation the proportional gain cannot explain.
  4. Add kd last, and only for overshoot. Read dTerm on a trace before you trust it — if the trace is noise, kd is amplifying your sensor and you should lower Nd or leave kd at 0.
  5. Set Nd against your noise, not against your response. Start at 100 rad/s on a 1 ms task and lower it until dTerm is smooth.
  6. Use the error thresholds and the velocity window only when the integral must be suppressed during motion. Leave them at their defaults otherwise.
  7. Re-check Nd after any task-rate change. Its usable range scales with the task period.

Controller output for a unit step error with kp 1 and ki 5 at three integralclamps: iMax 5 lets the output ramp to 3.5, iMax 1 flattens at 2.0, and thedefault iMax 0.1 flattens at 1.1 within 20ms.

Read where each curve flattens as the point its integral term hit iMax.

Symptom Cause Action
Steady-state error never goes away ki is 0, or iTerm is pinned at iMax or iMin Raise ki; if iTerm is pinned, raise iMax and lower iMin first
Raising ki seems to do nothing Expected with the ±0.1 default limits: the integral saturates almost immediately Set iMax and iMin to real values for your output unit
output went far beyond what the actuator can take Expected: this block has no output limit Add a limiter downstream, or use PID, which clamps its own output
The loop stays saturated long after the error reversed Wind-up, and this block has no anti-windup beyond the integral clamp Tighten iMax and iMin, or move to PID
The loop overshoots and rings kp too high, or ki too high Lower kp first; if the oscillation is slow, lower ki
The output buzzes or is audibly rough kd amplifying sensor noise Lower Nd, or set kd to 0
dTerm is pure noise on a trace Nd too high for the noise on error Lower Nd
dTerm froze at a value and never moves Nd is 0 Set Nd above 0
dTerm oscillates cycle to cycle Nd above 1/task period Lower Nd below 1000 on a 1 ms task
output or dTerm grew without bound Nd above 2/task period, or negative Set Nd within range, then restart the controller
A configuration copied from a PID block did not apply the derivative filter Expected: this block spells it Nd, PID spells it nd Write Nd explicitly; the two are not interchangeable
iTerm sits at iMin no matter what the error does iMin was set above iMax Keep iMin below iMax
A large kick on output on the first cycle after a start Expected with a non-zero kd: the derivative sees the initial error as a step Ramp the reference in, or start with the error near zero
A full-scale output immediately after a restart Expected: the integral state is not cleared by a stop Pulse iReset before releasing the loop
The integral never accumulates at all The velocity window is closed — writing 0 to both ends admits nothing Set iActualVelocityWindowPos to +infinity and the negative one to −infinity
The integral stops accumulating whenever the machine moves Expected if the velocity window is set: that is what it is for Widen the window, or leave it at its defaults
You cannot find the variable-gain parameters Expected: that branch is not exposed and contributes nothing Use the ordinary ki integral, or PID
output and iTerm went to a non-numeric value and iReset did not clear it A non-numeric value reached the derivative path, which iReset does not clear Restart the controller, then fix the source
You need to close several loops Not possible — this block is single channel Use one instance per loop

A conservative starting point for a loop on a 1 ms task, with the integral bounds in the actuator’s own units:

iMin = -5.0
iMax =  5.0
Nd   = 100.0
kp   =   1.0
ki   =   0.0
kd   =   0.0

Work steps 2 to 4 of Tuning from there. This is a starting point, not a final tuning.

Limits and errors

Limit Set by What happens Reported
output Nothing Unbounded. This block has no output limit, no back-calculation and no directional anti-windup. Bound it in a limiter downstream, or use PID Not reported
Integral term iMax, iMin Clamped to the range. This is the block’s only wind-up protection Not reported; watch iTerm
iMin < iMax Nothing Not checked. With iMin above iMax the integral is pinned at iMin and the gain has no effect Not reported
Nd within 0 and 2/task period [s] Nothing Not checked. Above 1/task period the derivative oscillates, above 2/task period it grows without bound, at 0 it freezes, below 0 it diverges at once Not reported
Variable-gain integrator Not exposed The branch named in the block’s title has no parameters and no way to enable it. It contributes nothing to output Not reported
Integral state across a stop Nothing Not cleared by a stop or a start. A loop that was wound when it stopped resumes wound. Pulse iReset before releasing the loop Not reported
Derivative state Nothing Not cleared by iReset, a stop, or a start. Only a controller restart clears it Not reported
Channel count Fixed One loop per instance, always Not reported

The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.


Verified against motorcortex-control3 3.30.0 (bc348fd).