Skip to content

Commit adb24dc

Browse files
authored
Merge pull request #954 from JuliaControl/filterdamp
add support for setting filter damping in `pid`
2 parents 7d43e17 + d0ab85a commit adb24dc

File tree

2 files changed

+38
-26
lines changed

2 files changed

+38
-26
lines changed

lib/ControlSystemsBase/src/pid_design.jl

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
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], filter_order=2)
4+
C = pid(param_p, param_i, [param_d]; form=:standard, state_space=false, [Tf], [Ts], filter_order=2, d=1/√(2))
55
66
Calculates and returns a PID controller.
77
88
The `form` can be chosen as one of the following (determines how the arguments `param_p, param_i, param_d` are interpreted)
9-
* `:standard` - `Kp*(1 + 1/(Ti*s) + Td*s)`
10-
* `:series` - `Kc*(1 + 1/(τi*s))*(τd*s + 1)`
11-
* `:parallel` - `Kp + Ki/s + Kd*s`
9+
* `:standard` - ``K_p(1 + 1/(T_i s) + T_d s)``
10+
* `:series` - ``K_c(1 + 1/(τ_i s))(τ_d s + 1)``
11+
* `:parallel` - ``K_p + K_i/s + K_d s``
1212
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.
1616
1717
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
18+
- `filter_order = 2` (default): ``1 / ((sT_f)^2/(4d^2) + sT_f + 1)`` in series with the controller
19+
- `filter_order = 1`: ``1 / (1 + sT_f)`` applied to the derivative term only
20+
21+
``T_f`` can typically be chosen as ``T_i/N`` for a PI controller and ``T_d/N`` for a PID controller,
22+
and `N` is commonly in the range 2 to 20. With a second-order filter, `d` controls the damping. `d = 1/√(2)` gives a Butterworth configuration of the poles, and `d=1` gives a critically damped filter (no overshoot).
2023
21-
`Tf` can typically be chosen as `Ti/N` for a PI controller and `Td/N` for a PID controller,
22-
and `N` is commonly in the range 2 to 20.
2324
A balanced state-space realization is returned, unless `balance = false`.
2425
2526
For a discrete controller a positive `Ts` can be supplied.
@@ -35,11 +36,11 @@ C3 = pid(2., 3, 0; Ts=0.4, state_space=true) # Discrete
3536
The functions `pid_tf` and `pid_ss` are also exported. They take the same parameters
3637
and is what is actually called in `pid` based on the `state_space` parameter.
3738
"""
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)
39+
function pid(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Ts=nothing, Tf=nothing, state_space=false, balance=true, kwargs...)
3940
C = if state_space # Type instability? Can it be fixed easily, does it matter?
40-
pid_ss(param_p, param_i, param_d; form, Tf, filter_order, balance)
41+
pid_ss(param_p, param_i, param_d; form, Tf, balance, kwargs...)
4142
else
42-
pid_tf(param_p, param_i, param_d; form, Tf, filter_order)
43+
pid_tf(param_p, param_i, param_d; form, Tf, kwargs...)
4344
end
4445
if Ts === nothing
4546
return C
@@ -51,7 +52,7 @@ end
5152

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

54-
function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, filter_order=2)
55+
function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, filter_order=2, d=1/√(2))
5556
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form)
5657
filter_order (1,2) || throw(ArgumentError("Filter order must be 1 or 2"))
5758
if isnothing(Tf) || (Kd == 0 && filter_order == 1)
@@ -65,31 +66,32 @@ function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard,
6566
if filter_order == 1
6667
tf([Kd*Tf + Kd, Kd], [Tf, 1])
6768
else
68-
return tf([Kd, Kp], [Tf^2/2, Tf, 1])
69+
return tf([Kd, Kp], [Tf^2/(4d^2), Tf, 1])
6970
end
7071
else
7172
if filter_order == 1
7273
return tf([Kd + Kp*Tf, Ki*Tf + Kp, Ki], [Tf, 1, 0])
7374
else
74-
return tf([Kd, Kp, Ki], [Tf^2/2, Tf, 1, 0])
75+
return tf([Kd, Kp, Ki], [Tf^2/(4d^2), Tf, 1, 0])
7576
end
7677
end
7778
end
7879
end
7980

80-
function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, balance=true, filter_order)
81+
function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing, balance=true, filter_order=2, d=nothing)
8182
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form)
8283
if !isnothing(Tf)
84+
d42 = d === nothing ? 2.0 : 4d^2 # To avoid d = 1/sqrt(2) not yielding exactly 2
8385
if Ki == 0
8486
if filter_order == 1
8587
A = [-1 / Tf;;]
8688
B = [-Kd/Tf^2]
8789
C = [1.0;;]
8890
D = [Kd/Tf + Kp;;]
8991
else # 2
90-
A = [0 1; -2/Tf^2 -2/Tf]
92+
A = [0 1; -d42/Tf^2 -d42/Tf]
9193
B = [0; 1]
92-
C = 2 / Tf^2 * [Kp Kd]
94+
C = d42 / Tf^2 * [Kp Kd]
9395
D = [0.0;;]
9496
end
9597
else
@@ -99,9 +101,9 @@ function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard,
99101
C = [1.0 1]
100102
D = [Kd/Tf + Kp;;]
101103
else # 2
102-
A = [0 1 0; 0 0 1; 0 -2/Tf^2 -2/Tf]
104+
A = [0 1 0; 0 0 1; 0 -d42/Tf^2 -d42/Tf]
103105
B = [0; 0; 1]
104-
C = 2 / Tf^2 * [Ki Kp Kd]
106+
C = d42 / Tf^2 * [Ki Kp Kd]
105107
D = [0.0;;]
106108
end
107109
end
@@ -144,8 +146,8 @@ r ┌─────┐ ┌─────┐ │ │ │
144146
```
145147
146148
The `form` can be chosen as one of the following (determines how the arguments `param_p, param_i, param_d` are interpreted)
147-
* `:standard` - `Kp*(b*r-y + (r-y)/(Ti*s) + Td*s*(c*r-y)/(Tf*s + 1))`
148-
* `:parallel` - `Kp*(b*r-y) + Ki*(r-y)/s + Kd*s*(c*r-y)/(Tf*s + 1)`
149+
* `:standard` - ``K_p*(br-y + (r-y)/(T_i s) + T_d s (cr-y)/(T_f s + 1))``
150+
* `:parallel` - ``K_p*(br-y) + K_i (r-y)/s + K_d s (cr-y)/(Tf s + 1)``
149151
150152
- `b` is a set-point weighting for the proportional term
151153
- `c` is a set-point weighting for the derivative term, this defaults to 0.

lib/ControlSystemsBase/test/test_pid_design.jl

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Test
2-
@testset "test_pid_design" begin
3-
42
freqresptest(A,B) = norm(freqresp(A-B, exp10.(LinRange(-3, 3, 10))))
3+
@testset "test_pid_design" begin
54

65
CSB = ControlSystemsBase
76

@@ -20,16 +19,16 @@ C, kp, ki = loopshapingPI(P, ωp, phasemargin=60, form=:parallel, doplot=true)
2019
@test pid(1.0, Inf, 1) == tf(1) + tf([1, 0], [1])
2120
@test pid(1.0, 0, 1) == tf(1) + tf([1, 0], [1])
2221
@test pid(0.0, 1, 1; form=:parallel) == tf(0) + tf(1,[1,0]) + tf([1,0],[1])
23-
@test pid(1.0, 2, 3; Tf=2) == tf([3,1,0.5], [2,2,1,0])
22+
@test pid(1.0, 2, 3; Tf=2) tf([3,1,0.5], [2,2,1,0])
2423
@test all(CSB.convert_pidparams_from_standard(CSB.convert_pidparams_from_parallel(1, 2, 3, :standard)...,
2524
:parallel) .≈ (1,2,3))
2625
@test_throws DomainError CSB.convert_pidparams_from_parallel(2, 3, 0.5, :series)
2726
@test_throws DomainError CSB.convert_pidparams_from_parallel(0, 3, 0.5, :standard)
2827
@test_throws DomainError CSB.convert_pidparams_from_standard(2, 1, 0.5, :series)
2928
# ss
3029
@test tf(pid(1.0, 1, 0; state_space=true)) == tf(1) + tf(1,[1,0])
31-
@test tf(pid(0.0, 2, 3; form=:parallel, state_space=true, Tf=2)) == tf([3,0,2], [2, 2, 1, 0])
32-
@test tf(pid(1.0, 2, 3; state_space=true, Tf=2)) == tf([3, 1, 0.5], [2, 2, 1, 0])
30+
@test tf(pid(0.0, 2, 3; form=:parallel, state_space=true, Tf=2)) tf([3,0,2], [2, 2, 1, 0])
31+
@test tf(pid(1.0, 2, 3; state_space=true, Tf=2)) tf([3, 1, 0.5], [2, 2, 1, 0])
3332

3433
# Discrete
3534
@test_throws ArgumentError pid(1.0, 1, 1, Ts=0.1)
@@ -51,6 +50,17 @@ Tf = 0.01
5150

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

53+
# Different damping
54+
Ctf = pid(1,1,1, Tf=0.1, d = 1)
55+
@test all(p->imag(p) == 0, poles(Ctf))
56+
Css = pid(1,1,1, Tf=0.1, d = 1, state_space=true)
57+
@test all(p->imag(p) == 0, poles(Css))
58+
@test tf(Css) Ctf
59+
60+
Ctf = pid(1,0,1, Tf=0.1, d = 0.9)
61+
Css = pid(1,0,1, Tf=0.1, d = 0.9, state_space=true)
62+
@test tf(Css) Ctf
63+
5464
# test filter order 1
5565
# All params
5666
Ctf = pid(1.0, 1, 1, Tf=0.1, filter_order=1)

0 commit comments

Comments
 (0)