Skip to content

Commit b347dcd

Browse files
authored
Merge pull request #17 from JuliaControl/wd
add derivative set-point weighting (`wd` parameter)
2 parents 364f3fd + b09f263 commit b347dcd

File tree

3 files changed

+48
-12
lines changed

3 files changed

+48
-12
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
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),$$
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}(w_d R(s) - Y(s)) \right) + U_\textrm{ff}(s),$$
99
where
1010
- $u(t) \leftrightarrow U(s)$ is the control signal
1111
- $y(t) \leftrightarrow Y(s)$ is the measurement signal
@@ -16,14 +16,15 @@ where
1616
- $T_d$ is the derivative time
1717
- $N$ is a parameter that limits the gain of the derivative term at high frequencies, typically ranges from 2 to 20,
1818
- $b \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the proportional term.
19+
- $w_d \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the derivative term (default 0).
1920

2021
*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}$.
2122

2223
## Usage
2324

2425
Construct a controller by
2526
```julia
26-
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)
27+
pid = DiscretePID(; K = 1, Ti = false, Td = false, Tt = (Ti*Td), N = 10, b = 1, wd = 0, umin = -Inf, umax = Inf, Ts, I = 0, D = 0, yold = 0)
2728
```
2829
and compute the control signal at a given time using
2930
```julia
@@ -219,7 +220,7 @@ K (b r - y + 1/T_i (r - y) - s T_d y/(1 + s T_d / N))
219220
using the function `K, Ti, Td = parallel2standard(kp, ki, kd)` or, if a filter parameter is included, `K, Ti, Td, N = parallel2standard(kp, ki, kd, Tf)`. This function also accepts a vector of parameters in the same order, in which case a vector is returned.
220221

221222
## Details
222-
- 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.
223+
- The derivative term by default 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. Set `wd = 1` to let the derivative act on the error `r-y` instead. 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.
223224
- Bumpless transfer when updating `K` is realized by updating the state `I`. See the docs for `set_K!` for more details.
224225
- The total control signal $u(t)$ (PID + feedforward) is limited by the integral anti-windup.
225226
- 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. This approximation has the advantage that it is always stable and that the sampled pole goes to zero when $T_d$ goes to zero. Tustin's approximation gives an approximation such that the pole instead goes to $z = −1$ as $T_d$ goes to zero.

src/DiscretePIDs.jl

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ mutable struct DiscretePID{T} <: Function
1919
"Maximum derivative gain"
2020
const N::T
2121
"Fraction of set point in prop. term"
22-
b::T
22+
b::T
23+
"Fraction of set point in derivative term"
24+
wd::T
2325
"Low output limit"
2426
umin::T
2527
"High output limit"
@@ -34,12 +36,12 @@ mutable struct DiscretePID{T} <: Function
3436
I::T
3537
"Derivative state"
3638
D::T
37-
"Last measurement signal"
39+
"Last derivative error (wd*r - y)"
3840
yold::T
3941
end
4042

4143
"""
42-
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)
44+
DiscretePID(; K = 1, Ti = false, Td = false, Tt = √(Ti*Td), N = 10, b = 1, wd = 0, umin = -Inf, umax = Inf, Ts, I = 0, D = 0, yold = 0)
4345
4446
A discrete-time PID controller with set-point weighting and integrator anti-windup.
4547
The controller is implemented on the standard form
@@ -48,7 +50,7 @@ u = K \\left( e + \\dfrac{1}{Ti} \\int e dt + T_d \\dfrac{de}{dt} \\right)
4850
```
4951
5052
```math
51-
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)
53+
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) - w_d R(s))
5254
```
5355
5456
Call the controller like this
@@ -64,12 +66,13 @@ u = calculate_control!(pid, r, y, uff) # Equivalent to the above
6466
- `Tt`: Reset time for anti-windup
6567
- `N`: Maximum derivative gain
6668
- `b`: Fraction of set point in proportional term
69+
- `wd`: Fraction of set point in derivative term (default 0)
6770
- `umin`: Low output limit
6871
- `umax`: High output limit
6972
- `Ts`: Sampling period
7073
- `I`: Integral part
7174
- `D`: Derivative part
72-
- `yold`: Last measurement signal
75+
- `yold`: Last derivative error (wd*r - y)
7376
7477
See also [`calculate_control!`](@ref), [`set_K!`](@ref), [`set_Ti!`](@ref), [`set_Td!`](@ref), [`reset_state!`](@ref).
7578
"""
@@ -80,6 +83,7 @@ function DiscretePID(;
8083
Tt = Ti > 0 && Td > 0 ? typeof(K)((Ti*Td)) : typeof(K)(10),
8184
N = typeof(K)(10),
8285
b = typeof(K)(1),
86+
wd = zero(typeof(K)),
8387
umin = typemin(typeof(K)),
8488
umax = typemax(typeof(K)),
8589
Ts,
@@ -96,6 +100,7 @@ function DiscretePID(;
96100
Td 0 || throw(ArgumentError("Td must be positive"))
97101
N 0 || throw(ArgumentError("N must be positive"))
98102
0 b 1 || throw(ArgumentError("b must be ∈ [0, 1]"))
103+
0 wd 1 || throw(ArgumentError("wd must be ∈ [0, 1]"))
99104
umax > umin || throw(ArgumentError("umax must be greater than umin"))
100105

101106
if Ti > 0
@@ -106,9 +111,9 @@ function DiscretePID(;
106111
ad = Td / (Td + N * Ts)
107112
bd = K * N * ad
108113

109-
T2 = promote_type(typeof.((K, Ti, Td, Tt, N, b, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...)
114+
T2 = promote_type(typeof.((K, Ti, Td, Tt, N, b, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...)
110115

111-
DiscretePID(T2.((K, Ti, Td, Tt, N, b, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...)
116+
DiscretePID(T2.((K, Ti, Td, Tt, N, b, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...)
112117
end
113118

114119
"""
@@ -173,15 +178,16 @@ function calculate_control!(pid::DiscretePID{T}, r0, y0, uff0=0; yd=nothing) whe
173178
y = T(y0)
174179
uff = T(uff0)
175180
P = pid.K * (pid.b * r - y)
181+
e = pid.wd * r - y # weighted error for derivative
176182
if yd === nothing
177-
pid.D = pid.ad * pid.D - pid.bd * (y - pid.yold)
183+
pid.D = pid.ad * pid.D + pid.bd * (e - pid.yold)
178184
else
179185
pid.D = - pid.K * pid.Td * T(yd)
180186
end
181187
v = P + pid.I + pid.D + uff
182188
u = clamp(v, pid.umin, pid.umax)
183189
pid.I = pid.I + pid.bi * (r - y) + pid.ar * (u - v)
184-
pid.yold = y
190+
pid.yold = e # store weighted error for next derivative calculation
185191
return u
186192
end
187193

test/runtests.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,35 @@ res2 = lsim(P, ctrl, Tf)
164164
# @test res.y ≈ res2.y rtol=0.01
165165

166166

167+
## PID control with derivative set-point weighting (wd parameter)
168+
# Compare wd=0 (derivative only on -y) vs wd=1 (derivative on r-y)
169+
Tf = 30
170+
Ti = 1
171+
Td = 1
172+
pid_wd0 = DiscretePID(; K, Ts, Ti, Td, wd=0)
173+
pid_wd1 = DiscretePID(; K, Ts, Ti, Td, wd=1)
174+
175+
ctrl_wd0 = function(x, t)
176+
y = (P.C*x)[]
177+
r = (t >= 5) # Step in reference
178+
pid_wd0(r, y)
179+
end
180+
181+
ctrl_wd1 = function(x, t)
182+
y = (P.C*x)[]
183+
r = (t >= 5) # Step in reference
184+
pid_wd1(r, y)
185+
end
186+
187+
res_wd0 = lsim(P, ctrl_wd0, Tf)
188+
res_wd1 = lsim(P, ctrl_wd1, Tf)
189+
190+
# With wd=1, step in r causes derivative kick; with wd=0 it doesn't
191+
# So the control signals should differ, especially around t=5
192+
@test res_wd0.y != res_wd1.y # Results should be different
193+
@test maximum(abs.(res_wd1.u)) > 5*maximum(abs.(res_wd0.u)) # wd=1 has larger control signal due to derivative kick
194+
195+
167196
## PID control with bumpless transfer
168197
# Here we simulate a load disturbance instead since the discrete PID does not differentiate r, while the ControlSystemsBase.pid does.
169198
Tf = 10

0 commit comments

Comments
 (0)