DiscretePIDs
This package implements a discretetime PID controller on the form
where

$u(t) \leftrightarrow U(s)$ is the control signal 
$y(t) \leftrightarrow Y(s)$ is the measurement signal 
$r(t) \leftrightarrow R(s)$ is the reference / set point 
$u_\textrm{ff}(t) \leftrightarrow U_\textrm{ff}(s)$ is the feedforward contribution 
$K$ is the proportional gain 
$T_i$ is the integral time 
$T_d$ is the derivative time 
$N$ is the maximum derivative gain 
$b \in [0, 1]$ is the proportion of the reference signal that appears in the proportional term.
The controller further has output saturation controlled by umin, umax
and integrator antiwindup controlled by the tracking time
Construct a controller using
pid = DiscretePID(; K = 1, Ti = false, Td = false, Tt = √(Ti*Td), N = 10, b = 1, umin = Inf, umax = Inf, Ts, I = 0, D = 0, yold = 0)
and compute a control signal using
u = pid(r, y, uff)
or
u = calculate_control!(pid, r, y, uff)
The parameters set_K!, set_Ti!, set_Td!
.
Example using ControlSystems:
The following example simulates the PID controller using ControlSystems.jl. We will simulate a load disturbance
using DiscretePIDs, ControlSystemsBase, Plots
Tf = 15 # Simulation time
K = 1 # Proportional gain
Ti = 1 # Integral time
Td = 1 # Derivative time
Ts = 0.01 # sample time
P = c2d(ss(tf(1, [1, 1])), Ts) # Process to be controlled, discretized using zeroorder hold
pid = DiscretePID(; K, Ts, Ti, Td)
ctrl = function(x,t)
y = (P.C*x)[] # measurement
d = 1 # disturbance
r = 0 # reference
u = pid(r, y)
u + d # Plant input is control signal + disturbance
end
res = lsim(P, ctrl, Tf)
plot(res, plotu=true); ylabel!("u + d", sp=2)
Example using DifferentialEquations:
The following example is identical to the one above, but uses DifferentialEquations.jl to simulate the PID controller. This is useful if you want to simulate the controller in a more complex system, e.g., with a nonlinear plant.
There are several different ways one could go about including a discretetime controller in a continuoustime simulation, in particular, we must choose a way to store the computed control signal
 Use a global variable into which we write the control signal at each discrete time step.
 Add an extra state variable to the system, and use this state to store the control signal. This is the approach taken in the example below since it has the added benefit of adding the computed control signal to the solution object.
We will use a DiffEqCallbacks.PeriodicCallback
in which we perform the PIDcontroller update, and store the computed control signal in the extra state variable.
using DiscretePIDs, ControlSystemsBase, OrdinaryDiffEq, DiffEqCallbacks, Plots
Tf = 15 # Simulation time
K = 1 # Proportional gain
Ti = 1 # Integral time
Td = 1 # Derivative time
Ts = 0.01 # sample time
P = ss(tf(1, [1, 1])) # Process to be controlled in continuous time
A, B, C, D = ssdata(P) # Extract the system matrices
pid = DiscretePID(; K, Ts, Ti, Td)
function dynamics!(dxu, xu, p, t)
A, B, C, r, d = p # We store the reference and disturbance in the parameter object
x = xu[1:P.nx] # Extract the state
u = xu[P.nx+1:end] # Extract the control signal
dxu[1:P.nx] .= A*x .+ B*(u .+ d) # Plant input is control signal + disturbance
dxu[P.nx+1:end] .= 0 # The control signal has no dynamics, it's updated by the callback
end
cb = PeriodicCallback(Ts) do integrator
p = integrator.p # Extract the parameter object from the integrator
(; C, r, d) = p # Extract the reference and disturbance from the parameter object
x = integrator.u[1:P.nx] # Extract the state (the integrator uses the variable name `u` to refer to the state, in control theory we typically use the variable name `x`)
y = (C*x)[] # Simulated measurement
u = pid(r, y) # Compute the control signal
integrator.u[P.nx+1:end] .= u # Update the controlsignal state variable
end
parameters = (; A, B, C, r=0, d=1) # reference = 0, disturbance = 1
xu0 = zeros(P.nx + P.nu) # Initial state of the system + control signals
prob = ODEProblem(dynamics!, xu0, (0, Tf), parameters, callback=cb) # reference = 0, disturbance = 1
sol = solve(prob, Tsit5(), saveat=Ts)
plot(sol, layout=(2, 1), ylabel=["x" "u"], lab="")
The figure should look more or less identical to the one above, except that we plot the control signal
Details
 The derivative term only acts on the (filtered) measurement and not the command signal. It is thus safe to pass step changes in the reference to the controller. The parameter
$b$ can further be set to zero to avoid step changes in the control signal in response to step changes in the reference.  Bumpless transfer when updating
K
is realized by updating the stateI
. See the docs forset_K!
for more details.  The total control signal
$u(t)$ (PID + feedforward) is limited by the integral antiwindup.
See also

TrajectoryLimiters.jl To generate dynamically feasible reference trajectories with bounded velocity and acceleration given an instantaneous reference
$r(t)$ which may change abruptly.  SymbolicControlSystems.jl For Ccode generation of LTI systems.