Skip to content

Commit cd60912

Browse files
authored
Merge pull request #953 from JuliaControl/filterorder
add `filter_order` option to `pid`
2 parents 38afacc + d90fa3c commit cd60912

File tree

3 files changed

+84
-37
lines changed

3 files changed

+84
-37
lines changed

lib/ControlSystemsBase/src/freqresp.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ end
391391
_default_freq_vector(sys::LTISystem, plot) = _default_freq_vector(
392392
[sys], plot)
393393

394-
395394
function _bounds_and_features(sys::LTISystem, plot::Val)
396395
# Get zeros and poles for each channel
397396
if !isa(plot, Val{:sigma})

lib/ControlSystemsBase/src/pid_design.jl

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export pid, pid_tf, pid_ss, pid_2dof, pid_ss_2dof, pidplots, leadlink, laglink, leadlinkat, leadlinkcurve, stabregionPID, loopshapingPI, placePI, loopshapingPID
22

33
"""
4-
C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts])
4+
C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts], filter_order=2)
55
66
Calculates and returns a PID controller.
77
@@ -13,11 +13,14 @@ The `form` can be chosen as one of the following (determines how the arguments `
1313
If `state_space` is set to `true`, either `Kd` has to be zero
1414
or a positive `Tf` has to be provided for creating a filter on
1515
the input to allow for a state-space realization.
16-
The filter used is `1 / (1 + s*Tf + (s*Tf)^2/2)`, where `Tf` can typically
17-
be chosen as `Ti/N` for a PI controller and `Td/N` for a PID controller,
16+
17+
The filter used is either
18+
- `filter_order = 2` (default): `1 / (1 + s*Tf + (s*Tf)^2/2)` in series with the controller
19+
- `filter_order = 1`: `1 / (1 + s*Tf)` applied to the derivative term only
20+
21+
`Tf` can typically be chosen as `Ti/N` for a PI controller and `Td/N` for a PID controller,
1822
and `N` is commonly in the range 2 to 20.
19-
A balanced state-space realization is returned, unless `balance = false`
20-
in which case a controllable canonical form is used.
23+
A balanced state-space realization is returned, unless `balance = false`.
2124
2225
For a discrete controller a positive `Ts` can be supplied.
2326
In this case, the continuous-time controller is discretized using the Tustin method.
@@ -32,11 +35,11 @@ C3 = pid(2., 3, 0; Ts=0.4, state_space=true) # Discrete
3235
The functions `pid_tf` and `pid_ss` are also exported. They take the same parameters
3336
and is what is actually called in `pid` based on the `state_space` parameter.
3437
"""
35-
function pid(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Ts=nothing, Tf=nothing, state_space=false, balance=true)
38+
function pid(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Ts=nothing, Tf=nothing, state_space=false, balance=true, filter_order=2)
3639
C = if state_space # Type instability? Can it be fixed easily, does it matter?
37-
pid_ss(param_p, param_i, param_d; form, Tf, balance)
40+
pid_ss(param_p, param_i, param_d; form, Tf, filter_order, balance)
3841
else
39-
pid_tf(param_p, param_i, param_d; form, Tf)
42+
pid_tf(param_p, param_i, param_d; form, Tf, filter_order)
4043
end
4144
if Ts === nothing
4245
return C
@@ -48,42 +51,66 @@ end
4851

4952
@deprecate pid(; kp=0, ki=0, kd=0, series = false) pid(kp, ki, kd; form=series ? :series : :parallel)
5053

51-
function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing)
54+
function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, filter_order=2)
5255
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form)
53-
if isnothing(Tf)
54-
if Ki != 0
55-
return tf([Kd, Kp, Ki], [1, 0])
56-
else
56+
filter_order (1,2) || throw(ArgumentError("Filter order must be 1 or 2"))
57+
if isnothing(Tf) || (Kd == 0 && filter_order == 1)
58+
if Ki == 0
5759
return tf([Kd, Kp], [1])
60+
else
61+
return tf([Kd, Kp, Ki], [1, 0])
5862
end
5963
else
60-
if Ki != 0
61-
return tf([Kd, Kp, Ki], [Tf^2/2, Tf, 1, 0])
64+
if Ki == 0
65+
if filter_order == 1
66+
tf([Kd*Tf + Kd, Kd], [Tf, 1])
67+
else
68+
return tf([Kd, Kp], [Tf^2/2, Tf, 1])
69+
end
6270
else
63-
return tf([Kd, Kp], [Tf^2/2, Tf, 1])
71+
if filter_order == 1
72+
return tf([Kd + Kp*Tf, Ki*Tf + Kp, Ki], [Tf, 1, 0])
73+
else
74+
return tf([Kd, Kp, Ki], [Tf^2/2, Tf, 1, 0])
75+
end
6476
end
6577
end
6678
end
6779

68-
function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, balance=true)
80+
function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, balance=true, filter_order)
6981
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form)
7082
if !isnothing(Tf)
71-
if Ki != 0
72-
A = [0 1 0; 0 0 1; 0 -2/Tf^2 -2/Tf]
73-
B = [0; 0; 1]
74-
C = 2 / Tf^2 * [Ki Kp Kd]
83+
if Ki == 0
84+
if filter_order == 1
85+
A = [-1 / Tf;;]
86+
B = [-Kd/Tf^2]
87+
C = [1.0;;]
88+
D = [Kd/Tf + Kp;;]
89+
else # 2
90+
A = [0 1; -2/Tf^2 -2/Tf]
91+
B = [0; 1]
92+
C = 2 / Tf^2 * [Kp Kd]
93+
D = [0.0;;]
94+
end
7595
else
76-
A = [0 1; -2/Tf^2 -2/Tf]
77-
B = [0; 1]
78-
C = 2 / Tf^2 * [Kp Kd]
96+
if filter_order == 1
97+
A = [0 0; 0 -1/Tf]
98+
B = [Ki; -Kd/Tf^2]
99+
C = [1.0 1]
100+
D = [Kd/Tf + Kp;;]
101+
else # 2
102+
A = [0 1 0; 0 0 1; 0 -2/Tf^2 -2/Tf]
103+
B = [0; 0; 1]
104+
C = 2 / Tf^2 * [Ki Kp Kd]
105+
D = [0.0;;]
106+
end
79107
end
80-
D = 0
81108
elseif Kd == 0
82109
if Ki != 0
83-
A = 0
84-
B = 1
85-
C = Ki # Ti == 0 would result in division by zero, but typically indicates that the user wants no integral action
86-
D = Kp
110+
A = [0.0;;]
111+
B = [1.0;;]
112+
C = [Ki;;] # Ti == 0 would result in division by zero, but typically indicates that the user wants no integral action
113+
D = [Kp;;]
87114
else
88115
return ss([Kp])
89116
end
@@ -155,13 +182,12 @@ function pid_ss_2dof(param_p, param_i, param_d=zero(typeof(param_p)); form=:stan
155182
A = [-(1 / Tf);;]
156183
B = [-kd*c/(Tf^2) kd/(Tf^2)]
157184
C = [1.0]
158-
D = [kd*c/Tf+kp*b -(kd/Tf + kp)]
159185
else
160186
A = [0 0; 0 -(1 / Tf)]
161187
B = [ki -ki; -kd*c/Tf^2 kd/Tf^2]
162188
C = [1.0 1]
163-
D = [kd*c/Tf+kp*b -(kd/Tf + kp)]
164189
end
190+
D = [kd*c/Tf+kp*b -(kd/Tf + kp)]
165191
K = ss(A, B, C, D)
166192
balance ? first(balance_statespace(K)) : K
167193
end
@@ -229,7 +255,7 @@ function pidplots(P::LTISystem, args...;
229255
pzmap(Ts; title="Pole-zero map", kwargs...) |> display
230256
end
231257
if :controller args
232-
bodeplot(Cs, ω; lab=labels, title="Controller bode plot", kwargs...) |> display
258+
bodeplot(Cs, ω; lab=repeat(labels, inner=(1,2)), title="Controller bode plot", kwargs...) |> display
233259
end
234260
end
235261

lib/ControlSystemsBase/test/test_pid_design.jl

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
using Test
12
@testset "test_pid_design" begin
23

4+
freqresptest(A,B) = norm(freqresp(A-B, exp10.(LinRange(-3, 3, 10))))
5+
36
CSB = ControlSystemsBase
47

58
# Test gof plot and loopshaping
@@ -48,6 +51,25 @@ Tf = 0.01
4851

4952
@test tf(pid(2.0, 0, 1; state_space=true, Tf)) minreal(pid(2.0, 0, 1; state_space=false, Tf))
5053

54+
# test filter order 1
55+
# All params
56+
Ctf = pid(1.0, 1, 1, Tf=0.1, filter_order=1)
57+
Css = pid(1.0, 1, 1, Tf=0.1, filter_order=1, state_space=true)
58+
@test freqresptest(Ctf, Css) < 1e-10
59+
60+
# No Ki
61+
Ctf = pid(1.0, 0, 1, Tf=0.1, filter_order=1)
62+
Css = pid(1.0, 0, 1, Tf=0.1, filter_order=1, state_space=true)
63+
@test freqresptest(Ctf, Css) < 1e-10
64+
65+
# No Kd (no filter either in this case)
66+
Ctf = pid(1.0, 1, 0, Tf=0.1, filter_order=1)
67+
Css = pid(1.0, 1, 0, Tf=0.1, filter_order=1, state_space=true)
68+
@test freqresptest(Ctf, Css) < 1e-10
69+
70+
# bodeplot([Ctf, Css])
71+
72+
5173
# pid 2 DOF
5274

5375
# PID controller on 2DOF form constructed with transfer functions for comparison
@@ -56,24 +78,24 @@ kp, ki, kd, b, c, Tf = rand(6)
5678
ki = 0
5779
Ktf = [(kp*b + kd*s*c/(Tf*s + 1)) -(kp + kd*s/(Tf*s + 1))]
5880
Kss = ControlSystemsBase.pid_ss_2dof(kp, ki, kd; Tf, b, c, form=:parallel)
59-
@test norm(freqresp(Kss-Ktf, exp10.(LinRange(-3, 3, 10)))) < 1e-10
81+
@test freqresptest(Kss, Ktf) < 1e-10
6082

6183
kp, ki, kd, b, c, Tf = rand(6)
6284
Ktf = [(kp*b + ki/s + kd*s*c/(Tf*s + 1)) -(kp + ki/s + kd*s/(Tf*s + 1))]
6385
Kss = ControlSystemsBase.pid_ss_2dof(kp, ki, kd; Tf, b, c, form=:parallel)
64-
@test norm(freqresp(Kss-Ktf, exp10.(LinRange(-3, 3, 10)))) < 1e-10
86+
@test freqresptest(Kss, Ktf) < 1e-10
6587

6688
kp, ki, kd, b, c, N = rand(6)
6789
Tf = kd/N
6890
Ktf = [(kp*b + ki/s + kd*s*c/(Tf*s + 1)) -(kp + ki/s + kd*s/(Tf*s + 1))]
6991
Kss = ControlSystemsBase.pid_ss_2dof(kp, ki, kd; N, b, c, form=:parallel)
70-
@test norm(freqresp(Kss-Ktf, exp10.(LinRange(-3, 3, 10)))) < 1e-10
92+
@test freqresptest(Kss, Ktf) < 1e-10
7193

7294

7395
kp, ki, kd, b, c, Tf = rand(6)
7496
Ktf = c2d(ss([(kp*b + ki/s + kd*s*c/(Tf*s + 1)) -(kp + ki/s + kd*s/(Tf*s + 1))]), 0.01, :tustin)
7597
Kss = pid_2dof(kp, ki, kd; Tf, b, c, form=:parallel, Ts=0.01, state_space = false)
76-
@test norm(freqresp(Kss-Ktf, exp10.(LinRange(-3, 3, 10)))) < 1e-5
98+
@test freqresptest(Kss, Ktf) < 1e-5
7799

78100
# Test pidplots
79101
C = pid(1.0, 1, 1)

0 commit comments

Comments
 (0)