PID Controller — Control Systems/Continues
Control_Systems/Continues/PID_Controller · 1 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
Control Systems / Continues
A continuous-time PID controller with a filtered derivative. It takes the error signal e and produces the control signal u:
Parallel: u = P·e + (I/s)·e + D·N/(1 + N/s)·e
Ideal: u = P·( e + (I/s)·e + D·N/(1 + N/s)·e )
The derivative is never taken bare: D·N/(1 + N/s) is a first-order filtered derivative whose bandwidth is set by N, so the block stays proper and has a state-space form. N → ∞ approaches the ideal D·s.
Ports
- Input – the error e, of any size [p,m].
- Output – the control signal u, of the same size.
The controller is SISO, but it is applied independently to every entry of the input signal, 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 is.
- 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, since the filter gain N/(1 + N/s) is then zero.
- Controller Form – how P enters the sum:
- Parallel – P, I and D are independent gains on the three branches: u = P·e + (I/s)·e + D·N/(1 + N/s)·e.
- Ideal – P multiplies the whole controller, so it scales the integral and derivative action too: u = P·( e + (I/s)·e + D·N/(1 + N/s)·e ).
- 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·∫e dt), not of ∫e dt.
- 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 and N 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.
"Proportional (P)" to P, "Integral (I)" to I,
"Derivative (D)" to D, "Filter Coefficient (N)" to N,
"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, PD, P and I 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.
- The two states are Simulink's own, which is why its two initial conditions carry over without rescaling.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Continues/PID_Controller |
| family | Control_Systems/Continues |
| solver environment class | ICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Continues/PID_Controller/ICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Continues/PID_Controller/ICoreBlock_0_Control_Systems_1_Continues_2_PID_Controller.h |
| default size on canvas | 110 × 80 px |
| ports at insert | 1 in, 1 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | — |
| 2 | out | ICoreDouble | — |
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 variable | Default | Simulink parameter |
|---|---|---|
Proportional (P) | 1 | P |
Integral (I) | 1 | I |
Derivative (D) | 0 | D |
Filter Coefficient (N) | 100 | N |
Controller Form | Parallel%~%Ideal~~Parallel | Form |
Initial Condition (Integrator) | 0 | InitialConditionForIntegrator |
Initial Condition (Filter) | 0 | InitialConditionForFilter |
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.
Simulink bridge#
| support | Support::Both |
| Simulink path | simulink/Continuous/PID Controller |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
| always set | Controller = PID, TimeDomain = Continuous-time, UseFilter = on |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Proportional (P) | P | passes through |
Integral (I) | I | passes through |
Derivative (D) | D | passes through |
Filter Coefficient (N) | N | passes through |
Controller Form | Form | Parallel → Parallel, Ideal → Ideal |
Initial Condition (Integrator) | InitialConditionForIntegrator | passes through |
Initial Condition (Filter) | InitialConditionForFilter | passes 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 block — filtered-derivative PID, element-wise One input (the error e), one output (the control signal u):
Parallel: u = P*e + I/s*e + D*N/(1 + N/s)e Ideal: u = P( e + I/s*e + D*N/(1 + N/s)*e )
THE REALIZATION IS SIMULINK'S, not an arbitrary equivalent one. Simulink puts the I gain BEFORE the integrator and realizes the derivative filter as xF' = N*(D*e - xF), so its two initial conditions are the states of those two 1/s blocks. Choosing the same two states is what lets "Initial Condition (Integrator)" and "Initial Condition (Filter)" map across 1:1 instead of needing a scaling nobody would guess:
xI' = I*e xF' = N*(D*e - xF) u = P*e + xI - N*xF + N*D*e (Parallel) u = P*(e + xI - N*xF + N*D*e) (Ideal)
so with x = [xI; xF]: A = [0 0; 0 -N] B = [I; N*D] C = [1 -N] D = P + N*D (Parallel) C = [P -P*N] D = P*(1 + N*D) (Ideal)
Being LINEAR, the block carries a continuous state space (initializeStateSpace_Continues), which is what makes it mergeable by the model-reduction commands and lets every export carry the run's own discretization rather than a hardcoded Euler step.
Element-wise, like Transfer Function and Integrator The controller is SISO but the input may be a [p,m] signal, and the same PID 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 input is fed as a row U[1, K], so the plain state-space products A*X + B*U and C*X + D*U evaluate all K entries at once (B/C/D being SISO). Flatten and reshape go through rawData(), so the entry <-> entry mapping is purely positional.
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 and hands back A,B,C,D,x0 — so the export matches the in-app simulation for EVERY method, not just forward Euler.
Sample results#
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 1.05 |
ramp | Ramp: slope 1 from t = 0 | 0 … 22.62 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -0.6186 … 1.615 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -2.2 … 4.85 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Control_Systems__Continues__PID_Controller.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).