diff --git a/Project.toml b/Project.toml index d5b103a..e2f1e56 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DiscretePIDs" uuid = "c1363496-6848-4723-8758-079b737f6baf" authors = ["Fredrik Bagge Carlson"] -version = "0.1.7" +version = "1.0.0" [deps] Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" diff --git a/README.md b/README.md index bebd61d..9ab049e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This package implements a discrete-time PID controller as an approximation of the continuous-time PID controller given by -$$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),$$ +$$U(s) = K \left( w_p R(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),$$ where - $u(t) \leftrightarrow U(s)$ is the control signal - $y(t) \leftrightarrow Y(s)$ is the measurement signal @@ -15,7 +15,7 @@ where - $T_i$ is the integral time - $T_d$ is the derivative time - $N$ is a parameter that limits the gain of the derivative term at high frequencies, typically ranges from 2 to 20, -- $b \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the proportional term. +- $w_p \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the proportional term. - $w_d \in [0, 1]$ is a parameter that gives the proportion of the reference signal that appears in the derivative term (default 0). *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}$. @@ -24,7 +24,7 @@ where Construct a controller by ```julia -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) +pid = DiscretePID(; K = 1, Ti = false, Td = false, Tt = √(Ti*Td), N = 10, wp = 1, wd = 0, umin = -Inf, umax = Inf, Ts, I = 0, D = 0, yold = 0) ``` and compute the control signal at a given time using ```julia @@ -220,7 +220,7 @@ K (b r - y + 1/T_i (r - y) - s T_d y/(1 + s T_d / N)) 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. ## Details -- 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. +- 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 $w_p$ 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 state `I`. See the docs for `set_K!` for more details. - The total control signal $u(t)$ (PID + feedforward) is limited by the integral anti-windup. - 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. diff --git a/src/DiscretePIDs.jl b/src/DiscretePIDs.jl index d52f6b8..e4a3414 100644 --- a/src/DiscretePIDs.jl +++ b/src/DiscretePIDs.jl @@ -19,7 +19,7 @@ mutable struct DiscretePID{T} <: Function "Maximum derivative gain" const N::T "Fraction of set point in prop. term" - b::T + wp::T "Fraction of set point in derivative term" wd::T "Low output limit" @@ -41,7 +41,7 @@ mutable struct DiscretePID{T} <: Function end """ - 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) + DiscretePID(; K = 1, Ti = false, Td = false, Tt = √(Ti*Td), N = 10, wp = 1, wd = 0, umin = -Inf, umax = Inf, Ts, I = 0, D = 0, yold = 0) A discrete-time PID controller with set-point weighting and integrator anti-windup. The controller is implemented on the standard form @@ -50,7 +50,7 @@ u = K \\left( e + \\dfrac{1}{Ti} \\int e dt + T_d \\dfrac{de}{dt} \\right) ``` ```math -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)) +U(s) = K \\left( wp R(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)) ``` Call the controller like this @@ -65,7 +65,7 @@ u = calculate_control!(pid, r, y, uff) # Equivalent to the above - `Td`: Derivative time - `Tt`: Reset time for anti-windup - `N`: Maximum derivative gain -- `b`: Fraction of set point in proportional term +- `wp`: Fraction of set point in proportional term - `wd`: Fraction of set point in derivative term (default 0) - `umin`: Low output limit - `umax`: High output limit @@ -82,8 +82,9 @@ function DiscretePID(; Td = false, Tt = Ti > 0 && Td > 0 ? typeof(K)(√(Ti*Td)) : typeof(K)(10), N = typeof(K)(10), - b = typeof(K)(1), + wp = typeof(K)(1), wd = zero(typeof(K)), + b = nothing, umin = typemin(typeof(K)), umax = typemax(typeof(K)), Ts, @@ -96,10 +97,14 @@ function DiscretePID(; else bi = zero(K * Ts) end + if b !== nothing + wp = typeof(K)(b) + @warn "Parameter `b` is deprecated. Use `wp` instead." + end Tt ≥ 0 || throw(ArgumentError("Tt must be positive")) Td ≥ 0 || throw(ArgumentError("Td must be positive")) N ≥ 0 || throw(ArgumentError("N must be positive")) - 0 ≤ b ≤ 1 || throw(ArgumentError("b must be ∈ [0, 1]")) + 0 ≤ wp ≤ 1 || throw(ArgumentError("wp must be ∈ [0, 1]")) 0 ≤ wd ≤ 1 || throw(ArgumentError("wd must be ∈ [0, 1]")) umax > umin || throw(ArgumentError("umax must be greater than umin")) @@ -111,9 +116,9 @@ function DiscretePID(; ad = Td / (Td + N * Ts) bd = K * N * ad - T2 = promote_type(typeof.((K, Ti, Td, Tt, N, b, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...) + T2 = promote_type(typeof.((K, Ti, Td, Tt, N, wp, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...) - DiscretePID(T2.((K, Ti, Td, Tt, N, b, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...) + DiscretePID(T2.((K, Ti, Td, Tt, N, wp, wd, umin, umax, Ts, bi, ar, bd, ad, I, D, yold))...) end """ @@ -129,7 +134,7 @@ function set_K!(pid::DiscretePID, K, r, y) pid.bd = K * pid.N * pid.ad if pid.Ti > 0 pid.bi = K * pid.Ts / pid.Ti - pid.I = pid.I + Kold*(pid.b*r - y) - K*(pid.b*r - y) + pid.I = pid.I + Kold*(pid.wp*r - y) - K*(pid.wp*r - y) end nothing end @@ -177,7 +182,7 @@ function calculate_control!(pid::DiscretePID{T}, r0, y0, uff0=0; yd=nothing) whe r = T(r0) y = T(y0) uff = T(uff0) - P = pid.K * (pid.b * r - y) + P = pid.K * (pid.wp * r - y) e = pid.wd * r - y # weighted error for derivative if yd === nothing pid.D = pid.ad * pid.D + pid.bd * (e - pid.yold) @@ -233,10 +238,10 @@ end K, Ti, Td, N = parallel2standard(Kp, Ki, Kd, Tf) Convert parameters from form "parallel" form with first-order filter -``K_p (br-y) + K_i (r-y)/s - K_d s y/(Tf s + 1)`` +``K_p (w_p r-y) + K_i (r-y)/s - K_d s y/(Tf s + 1)`` to "standard" form used in DiscretePID: -``K (br-y + (r-y)/(T_i s) - T_d s y/(T_d / N s + 1))`` +``K (w_p r-y + (r-y)/(T_i s) - T_d s y/(T_d / N s + 1))`` You may provide either four arguments or an array with four elements in the same order. """ diff --git a/test/runtests.jl b/test/runtests.jl index e48600b..3cbffa0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -148,8 +148,8 @@ res3 = lsim(P, ctrl, Tf) ## PI control with sp weighting Tf = 10 Ti = 1 -b = 0.0 -pid = DiscretePID(; K, Ts, Ti, b) +wp = 0.0 +pid = DiscretePID(; K, Ts, Ti, wp) ctrl = function(x,t) y = (P.C*x)[] r = 1