Skip to content

Commit bc91cd2

Browse files
baggepinnenhurak
andauthored
Modified version of #14 (#15)
* clarify note * Minor polishing of README. A few more bits of polishing of README. * revert selected changes --------- Co-authored-by: Zdeněk Hurák <[email protected]>
1 parent dff6637 commit bc91cd2

File tree

1 file changed

+42
-32
lines changed

1 file changed

+42
-32
lines changed

README.md

+42-32
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
[![Coverage](https://codecov.io/gh/JuliaControl/DiscretePIDs.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaControl/DiscretePIDs.jl)
55

66

7-
This package implements a discrete-time PID controller on the form
8-
$$U(s) = K \left( bR(s) - Y(s) + \dfrac{1}{sT_i} \left( R(s) - Y(s) \right) - \dfrac{sT_d}{1 + s T_d / N}Y(s) \right) + U_\textrm{ff}(s)$$
9-
7+
This package implements a discrete-time PID controller as an approximation of the continuous-time PID controller given by
8+
$$U(s) = K \left( bR(s) - Y(s) + \dfrac{1}{sT_i} \left( R(s) - Y(s) \right) - \dfrac{sT_d}{1 + s T_d / N}Y(s) \right) + U_\textrm{ff}(s),$$
109
where
1110
- $u(t) \leftrightarrow U(s)$ is the control signal
1211
- $y(t) \leftrightarrow Y(s)$ is the measurement signal
@@ -15,16 +14,18 @@ where
1514
- $K$ is the proportional gain
1615
- $T_i$ is the integral time
1716
- $T_d$ is the derivative time
18-
- $N$ is the maximum derivative gain
19-
- $b \in [0, 1]$ is the proportion of the reference signal that appears in the proportional term.
17+
- $N$ is a parameter that limits the gain of the derivative term at high frequencies, typically ranges from 2 to 20,
18+
- $b \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the proportional term.
19+
20+
*Saturation* of the controller output is parameterized by $u_{\min}$ and $u_{\max}$, and the integrator *anti-windup* is parameterized by the tracking time $T_\mathrm{t}$.
2021

21-
The controller further has output saturation controlled by `umin, umax` and integrator anti-windup controlled by the tracking time $T_t$.
22+
## Usage
2223

23-
Construct a controller using
24+
Construct a controller by
2425
```julia
2526
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)
2627
```
27-
and compute a control signal using
28+
and compute the control signal at a given time using
2829
```julia
2930
u = pid(r, y, uff)
3031
```
@@ -33,14 +34,17 @@ or
3334
u = calculate_control!(pid, r, y, uff)
3435
```
3536

36-
The parameters $K, T_i, T_d$ may be updated using the functions, `set_K!, set_Ti!, set_Td!`.
37+
The parameters $K$, $T_i$, and $T_d$ may be updated using the functions `set_K!`, `set_Ti!`, and `set_Td!`, respectively.
3738

3839
The numeric type used by the controller (the `T` in `DiscretePID{T}`) is determined by the types of the parameters. To use a custom number type, e.g., a fixed-point number type, simply pass the parameters as that type, see example below. The controller will automatically convert measurements and references to this type before performing the control calculations.
3940

4041
The **internal state** of the controller can be reset to zero using the function `reset_state!(pid)`. If repeated simulations using the same controller object are performed, the state should likely be reset between simulations.
4142

42-
## Example using ControlSystems:
43-
The following example simulates the PID controller using ControlSystems.jl. We will simulate a load disturbance $d(t) = 1$ entering on the process input, while the reference is $r(t) = 0$.
43+
## Examples
44+
45+
### Example using ControlSystems.jl
46+
47+
The following example simulates a feedback control system containing a PID controller using [ControlSystems.jl](https://juliacontrol.github.io/ControlSystems.jl) package. We simulate a response of the closed-loop system to the step disturbance $d(t) = 1$ entering at the *plant* (the system to be controlled) input, while the reference is $r(t) = 0$.
4448

4549
```julia
4650
using DiscretePIDs, ControlSystemsBase, Plots
@@ -57,7 +61,7 @@ ctrl = function(x,t)
5761
y = (P.C*x)[] # measurement
5862
d = 1 # disturbance
5963
r = 0 # reference
60-
u = pid(r, y)
64+
u = pid(r, y) # control signal
6165
u + d # Plant input is control signal + disturbance
6266
end
6367

@@ -67,16 +71,20 @@ plot(res, plotu=true); ylabel!("u + d", sp=2)
6771
```
6872
![Simulation result](https://user-images.githubusercontent.com/3797491/172366365-c1533aed-e877-499d-9ebb-01df62107dfb.png)
6973

70-
In this case, we simulated a linear plant, in which case we get an exact result using `ControlSystems.lsim`. Below, we show two methods for simulation of the controller that works also when the plant is nonlinear (but we will still use the linear system here for comparison).
74+
Here we simulated a linear plant, in which case we were able to call `ControlSystems.lsim` specialized for linear systems. Below, we show two methods for simulation that works with a nonlinear plant, but we still use a linear system to make the comparison easier.
75+
76+
### Example using DifferentialEquations.jl
7177

72-
## Example using DifferentialEquations:
73-
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.
78+
This example is identical to the one above except for using [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) for the simulation, which makes it possible to consider more complex plants, in particular nonlinear ones.
7479

75-
There are several different ways one could go about including a discrete-time controller in a continuous-time simulation, in particular, we must choose a way to store the computed control signal
76-
1. Use a global variable into which we write the control signal at each discrete time step.
77-
2. 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.
80+
There are several different ways one could go about including a discrete-time controller in a continuous-time simulation, in particular, we must choose a way to store the computed control variable. Two common approaches are
7881

79-
We will use a `DiffEqCallbacks.PeriodicCallback` in which we perform the PID-controller update, and store the computed control signal in the extra state variable.
82+
1. We use a global variable into which we write the control signal at each discrete time step.
83+
2. We add an extra state variable to the system, and use it to store the control variable.
84+
85+
In this example we choose the latter approach, since it has the added benefit of adding the computed control variable to the solution object.
86+
87+
We use `DiffEqCallbacks.PeriodicCallback`, in which we perform the PID-controller update, and store the computed control signal in the extra state variable.
8088

8189
```julia
8290
using DiscretePIDs, ControlSystemsBase, OrdinaryDiffEq, DiffEqCallbacks, Plots
@@ -117,20 +125,22 @@ plot(sol, layout=(2, 1), ylabel=["x" "u"], lab="")
117125
```
118126
The figure should look more or less identical to the one above, except that we plot the control signal $u$ instead of the combined input $u + d$ like we did above. Due to the fast sample rate $T_s$, the control signal looks continuous, however, increase $T_s$ and you'll notice the zero-order-hold nature of $u$.
119127

120-
## Example using SeeToDee:
121-
[SeeToDee.jl](https://baggepinnen.github.io/SeeToDee.jl/dev/) is a library of fixed time-step integrators that take inputs as function arguments and are useful for manual simulation of control systems. The same example as above is simulated using [`SeeToDee.Rk4`](https://baggepinnen.github.io/SeeToDee.jl/dev/api/#SeeToDee.Rk4) below. The call to
128+
### Example using SeeToDee.jl
129+
130+
[SeeToDee.jl](https://baggepinnen.github.io/SeeToDee.jl/dev/) is a library of fixed-time-step integrators useful for "manual" (=one integration step at a time) simulation of control systems. The same example as above is simulated using [`SeeToDee.Rk4`](https://baggepinnen.github.io/SeeToDee.jl/dev/api/#SeeToDee.Rk4) here. The call to
131+
122132
```julia
123133
discrete_dynamics = SeeToDee.Rk4(dynamics, Ts)
124134
```
125-
converts the continuous-time dynamics function
135+
considers the continuous-time dynamical system modelled by
126136
```math
127-
\dot x = f(x, u, p, t)
137+
\dot x(t) = f(x(t), u(t), p(t), t)
128138
```
129-
into a discrete-time version
139+
and at a given state $x$ and time $t$ and for a given control $u$, it computes an approximation $x^+$ to the state $x(t+T_s)$ at the next time step $t+T_s$
140+
130141
```math
131-
x_{t+T_s} = f(x_t, u_t, p, t)
142+
x(t+T_s) \approx x^+ = \phi(x(t), u(t), p(t), t,T_s).
132143
```
133-
that we can use to advance the state of the system forward in time in a loop.
134144

135145
```julia
136146
using DiscretePIDs, ControlSystemsBase, SeeToDee, Plots
@@ -172,18 +182,18 @@ Um = reduce(hcat, U)'
172182

173183
plot(t, [Ym Um], layout=(2,1), ylabel = ["y" "u"], legend=false)
174184
```
175-
Once again, the output looks identical and is omitted here.
185+
Once again, the output looks identical and is therefore omitted here.
176186

177187
## Details
178188
- 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.
179189
- Bumpless transfer when updating `K` is realized by updating the state `I`. See the docs for `set_K!` for more details.
180-
- The total control signal $u(t)$ (PID + feed-forward) is limited by the integral anti-windup.
190+
- The total control signal $u(t)$ (PID + feedforward) is limited by the integral anti-windup.
181191
- The integrator is discretized using a forward difference (no direct term between the input and output through the integral state) while the derivative is discretized using a backward difference.
182-
- This particular implementation of a discrete-time PID controller is detailed in Ch 8 of "Computer Control: An Overview (IFAC professional brief)", Wittenmark, Åström, Årzén.
192+
- This particular implementation of a discrete-time PID controller is detailed in Chapter 8 of [Wittenmark, Björn, Karl-Erik Årzén, and Karl Johan Åström. ‘Computer Control: An Overview’. IFAC Professional Brief. International Federation of Automatic Control, 2002](https://www.ifac-control.org/publications/list-of-professional-briefs/pb_wittenmark_etal_final.pdf/view).
183193
- When used with input arguments of standard types, such as `Float64` or `Float32`, the controller is guaranteed not to allocate any memory or contain any dynamic dispatches. This analysis is carried out in the tests, and is performed using [AllocCheck.jl](https://github.com/JuliaLang/AllocCheck.jl).
184194

185-
## Simulation of fixed-point arithmetic
186-
If the controller is ultimately to be implemented on a platform without floating-point hardware, you can simulate how it will behave with fixed-point arithmetics using the `FixedPointNumbers` package. The following example modifies the first example above and shows how to simulate the controller using 16-bit fixed-point arithmetics with 10 bits for the fractional part:
195+
### Simulation with fixed-point arithmetic
196+
If the controller is ultimately to be implemented on a platform without floating-point hardware, we can simulate how it will behave with fixed-point arithmetics using the [FixedPointNumbers.jl](https://github.com/francescoalemanno/FixedPoint.jl) package. The following example modifies the first example above and shows how to simulate the controller using 16-bit fixed-point arithmetics with 10 bits for the fractional part:
187197
```julia
188198
using FixedPointNumbers
189199
T = Fixed{Int16, 10} # 16-bit fixed-point with 10 bits for the fractional part
@@ -197,7 +207,7 @@ The fixed-point controller behaves roughly the same in this case, but artifacts
197207

198208
## Compilation using JuliaC
199209
> [!IMPORTANT]
200-
> At the time of writing, this requires a nightly version of julia
210+
> At the time of writing, this requires a nightly version of julia. Consider this example to be highly experimental for now!
201211
202212
The file [`examples/juliac/juliac_pid.jl`](https://github.com/JuliaControl/DiscretePIDs.jl/blob/main/examples/juliac/juliac_pid.jl) contains a JuliaC-compatible interface that can be compiled into a C-callable shared library using JuliaC. To compile the file, run the following from the [`examples/juliac`](https://github.com/JuliaControl/DiscretePIDs.jl/tree/main/examples/juliac) folder:
203213
```bash

0 commit comments

Comments
 (0)