Generated reference › PID Controller 2DOF — Control Systems/Continues
kind: generated#block#control-systems-continues

PID Controller 2DOF — Control Systems/Continues

PID 2DOF

Control_Systems/Continues/PID_Controller_2DOF · 2 input / 1 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Description#

The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.

PID Controller (2DOF)

Control Systems / Continues

A two-degree-of-freedom continuous-time PID controller with a filtered derivative. It takes the reference r and the measurement y separately, so the setpoint weights b and c can route the reference into the proportional and derivative branches at a different strength than into the integral one:

Parallel:  u = P·(b·r − y) + (I/s)·(r − y) + D·N/(1 + N/s)·(c·r − y)
Ideal:     u = P·[ (b·r − y) + (I/s)·(r − y) + D·N/(1 + N/s)·(c·r − y) ]

That is the second degree of freedom: the integral branch always sees the true error r − y, so steady-state tracking is unchanged, while b and c set how hard a setpoint change hits the other two branches. b < 1 softens the proportional kick on a step, and c = 0 removes derivative kick outright – neither of which affects the response to a disturbance. With b = c = 1 the block is exactly the one-degree-of-freedom PID Controller.

Ports

  • Input 1 (r) – the reference, or setpoint, of any size [p,m].
  • Input 2 (y) – the measurement, or plant output, of the same size as r.
  • Output – the control signal u, of that same size.

The port order is Simulink's for this block, and the labels on the block face are the symbols the math above uses. The controller is scalar, but it is applied independently to every entry of the input signals, each entry carrying its own two states.

Parameters

  • Proportional (P) – the proportional gain, a scalar.
  • Integral (I) – the integral gain, a scalar. Zero removes the integral action.
  • Derivative (D) – the derivative gain, a scalar. Zero removes the derivative action, whatever N and c are.
  • Filter Coefficient (N) – the derivative filter bandwidth, a positive scalar. The filter pole sits at −N, so a large N tracks the ideal derivative closely and a small one smooths it heavily. Zero removes the derivative action outright.
  • Setpoint Weight (b) – how much of the reference reaches the proportional branch, a scalar. 1 is the full reference (the 1DOF behaviour); 0 feeds that branch −y alone.
  • Setpoint Weight (c) – the same for the derivative branch. 0 is the common choice, since it removes derivative kick on a setpoint step.
  • Controller Form – how P enters the sum:
    • Parallel – P, I and D are independent gains on the three branches.
    • Ideal – P multiplies the whole controller, so it scales the integral and derivative action too.
  • Initial Condition (Integrator) – the integrator state at the start of the run, a scalar used for every entry. The I gain sits before the integrator, so this is the initial value of the integral term (I·∫(r − y) dt), not of the bare integral.
  • Initial Condition (Filter) – the derivative filter's state at the start of the run, a scalar used for every entry.
  • Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.

Code export

All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. A deployable target runs a discrete model, so the export carries the controller discretized by the run's method and the generated code matches the in-app simulation.

P, I, D, N, b and c are not tunable on the generated core: the discretization mixes them into the A/B/C/D it embeds, so retuning would mean re-discretizing on the target. Change them here and export again. The two initial conditions are baked in as the state seed for the same reason.

Simulink bridge

Import and export, mapped to simulink/Continuous/PID Controller (2DOF). "Proportional (P)" to P, "Integral (I)" to I, "Derivative (D)" to D, "Filter Coefficient (N)" to N, "Setpoint Weight (b)" to b, "Setpoint Weight (c)" to c, "Controller Form" to Form (Parallel/Ideal, a 1:1 and therefore lossless pair), "Initial Condition (Integrator)" to InitialConditionForIntegrator, "Initial Condition (Filter)" to InitialConditionForFilter, and "Sampling Time (s)" to SampleTime, as on every block.

Three Simulink parameters are always implied rather than offered as a choice: Controller = PID, TimeDomain = Continuous-time and UseFilter = on. The full PID structure covers Simulink's PI and PD 2DOF controllers numerically – set D to zero for PI, I to zero for PD – so nothing is lost by always emitting it, and an imported block whose values differ is reported rather than silently mistranslated.

What does not cross: output saturation with anti-windup (LimitOutput), external reset (ExternalReset), external initial conditions and external tracking all add input ports in Simulink, and no config value here can add or remove a port. An unfiltered derivative (UseFilter = off) does not cross either: D·s is improper, so it has no state-space form and none of the ten targets could carry it.

Notes

  • Stateful and continuous: two states per input entry – the integrator and the derivative filter – which the solver integrates between steps.
  • Being linear, the block is directly usable by the model reduction and linear-analysis commands. Its state space has TWO inputs, so it is not a SISO system and the series/parallel merge commands will not absorb it.
  • The two states are Simulink's own, which is why its two initial conditions carry over without rescaling.

Code facts#

FactValue
registered typeControl_Systems/Continues/PID_Controller_2DOF
familyControl_Systems/Continues
solver environment classICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller_2DOF
sourcesrc/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Continues/PID_Controller_2DOF/ICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller_2DOF.cpp
headersrc/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Continues/PID_Controller_2DOF/ICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller_2DOF.h
default size on canvas120 × 90 px
ports at insert2 in, 1 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoubler
2inICoreDoubley
3outICoreDoubleu

Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.

Configuration variables#

Config variableDefaultSimulink parameter
Proportional (P)1P
Integral (I)1I
Derivative (D)0D
Filter Coefficient (N)100N
Setpoint Weight (b)1b
Setpoint Weight (c)1c
Controller FormParallel%~%Ideal~~ParallelForm
Initial Condition (Integrator)0InitialConditionForIntegrator
Initial Condition (Filter)0InitialConditionForFilter

Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.

supportSupport::Both
Simulink pathsimulink/Continuous/PID Controller (2DOF)
port-count rulePortsParam::None
SampleTime parameteryes
always setController = PID, TimeDomain = Continuous-time, UseFilter = on
ICore configSimulink parameterValue translation
Proportional (P)Ppasses through
Integral (I)Ipasses through
Derivative (D)Dpasses through
Filter Coefficient (N)Npasses through
Setpoint Weight (b)bpasses through
Setpoint Weight (c)cpasses through
Controller FormFormParallelParallel, IdealIdeal
Initial Condition (Integrator)InitialConditionForIntegratorpasses through
Initial Condition (Filter)InitialConditionForFilterpasses through

Caveat (shown to the user): output saturation/anti-windup, external reset, external initial conditions and tracking mode are not supported (each adds an input port in Simulink); the derivative is always filtered, since an unfiltered D*s is improper and has no state-space form

Catalog contract: src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

Description vs code#

The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.

The verdict above is tools/docs/check_block_descriptions.py (P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.

File banner (developer view)#

The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).

PID Controller (2DOF) block — two-degree-of-freedom filtered-derivative PID, element-wise Two inputs (the reference r and the measurement y), one output (the control signal u):

Parallel: u = P*(b*r - y) + I/s*(r - y) + D*N/(1 + N/s)(c*r - y) Ideal: u = P[ (b*r - y) + I/s*(r - y) + D*N/(1 + N/s)*(c*r - y) ]

Taking r and y separately rather than a pre-formed error is the whole point: the setpoint weights b and c let the reference reach the proportional and derivative branches at a different strength than it reaches the integral one, so setpoint tracking can be softened without touching the disturbance response. b = c = 1 collapses the block to the 1DOF PID.

THE REALIZATION IS SIMULINK'S, exactly as in the 1DOF block: the I gain sits BEFORE the integrator and the derivative filter is xF' = N*(D*e_d - xF), so Simulink's two initial conditions map across 1:1 rather than needing a scaling nobody would guess.

xI' = I*(r - y) xF' = N*(D*(c*r - y) - xF) u = P*(b*r - y) + xI - N*xF + N*D*(c*r - y) (Parallel) u = P*[(b*r - y) + xI - N*xF + N*D*(c*r - y)] (Ideal)

so with x = [xI; xF] and the input vector [r; y]: A = [0 0; 0 -N] B = [I -I; N*D*c -N*D] C = [1 -N] D = [P*b + N*D*c, -(P + N*D)] (Parallel) C = [P -P*N] D = [P*(b + N*D*c), -P*(1 + N*D)] (Ideal)

Being LINEAR, the block carries a continuous state space, which is what makes it mergeable by the model-reduction commands and lets every export carry the run's own discretization.

Element-wise, like the 1DOF block The controller is 2-input/1-output but the ports may carry [p,m] signals (both the same size), and the same controller runs on every entry independently. The block state is X[2, K] with K = p*m - column e is entry e's own [xI; xF] - and the two inputs are fed as the two ROWS of U[2, K], so A*X + B*U and C*X + D*U evaluate all K entries at once.

Code export (Python/MATLAB/Java/Rust/C/C++ and HDL/PLC) Targets cannot integrate an ODE, so every generator realizes the DISCRETIZED recursion, structurally identical to the in-app discrete simulation, per entry: y[k] = C x[k] + D u[k] (output from the CURRENT state) x[k+1] = A x[k] + B u[k] (state update; uses the same x[k]) pidDiscreteExport() discretizes per the MODEL's method, so the export matches the in-app

Sample results#

PID Controller 2DOF — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per samplePID Controller 2DOF — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample-202012345t (s)in ICoreDouble-Out-0in ICoreDouble-Out-0out ICoreDouble-Out-0
tin ICoreDouble-Out-0in ICoreDouble-Out-0out ICoreDouble-Out-0
0-2-20
0.40.50.50
0.8-2-20
1.20.50.50
1.6-2-20
20.50.50
2.4-2-20
2.80.50.50
3.2-2-20
3.60.50.50
4-2-20
4.40.50.50
4.8-2-20
5.20.50.50

Every 4th of 60 samples, from the table stimulus.

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)0 … 0
rampRamp: slope 1 from t = 00 … 0
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias0 … 0
stepStep: 0 -> 1 at t = 1 s0 … 0

Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample

Category static · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Control_Systems__Continues__PID_Controller_2DOF.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).