Class: DeviceControl::PIDController
- Inherits:
-
Controller
- Object
- Controller
- DeviceControl::PIDController
- Defined in:
- lib/device_control.rb
Overview
A PIDController is a Controller that tracks its error over time in order to calculate:
Proportion (current error)
Integral (accumulated error)
Derivative (error slope, last_error)
The sum of these terms is the output
Constant Summary collapse
- HZ =
1000- TICK =
Rational(1) / HZ
- ZN =
Ziegler-Nichols method for tuning PID gain knobs en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
{ # Kp Ti Td Ki Kd # Var: Ku Tu Tu Ku/Tu Ku*Tu 'P' => [1/2r], 'PI' => [9/20r, 4/5r, nil, 27/50r], 'PD' => [ 4/5r, nil, 1/8r, nil, 1/10r], 'PID' => [ 3/5r, 1/2r, 1/8r, 6/5r, 3/40r], 'PIR' => [7/10r, 2/5r, 3/20r, 7/4r, 21/200r], # less overshoot than standard PID 'some' => [ 1/3r, 1/2r, 1/3r, 2/3r, 1/11r], 'none' => [ 1/5r, 1/2r, 1/3r, 2/5r, 2/30r], }
Instance Attribute Summary collapse
-
#d_range ⇒ Object
Returns the value of attribute d_range.
-
#dt ⇒ Object
Returns the value of attribute dt.
-
#e_range ⇒ Object
Returns the value of attribute e_range.
-
#error ⇒ Object
Returns the value of attribute error.
-
#i_range ⇒ Object
Returns the value of attribute i_range.
-
#kd ⇒ Object
Returns the value of attribute kd.
-
#ki ⇒ Object
Returns the value of attribute ki.
-
#kp ⇒ Object
Returns the value of attribute kp.
-
#last_error ⇒ Object
Returns the value of attribute last_error.
-
#low_pass_ticks ⇒ Object
Returns the value of attribute low_pass_ticks.
-
#mavg ⇒ Object
readonly
Returns the value of attribute mavg.
-
#o_range ⇒ Object
Returns the value of attribute o_range.
-
#p_range ⇒ Object
Returns the value of attribute p_range.
-
#sum_error ⇒ Object
Returns the value of attribute sum_error.
Attributes inherited from Controller
Class Method Summary collapse
-
.tune(type, ku, tu) ⇒ Object
ku = ultimate gain, tu = oscillation period output includes ti and td, which are not necessary typically kp, ki, and kd are used.
Instance Method Summary collapse
- #derivative ⇒ Object
-
#initialize(setpoint, dt: TICK, low_pass_ticks: 0) {|_self| ... } ⇒ PIDController
constructor
A new instance of PIDController.
-
#input=(val) ⇒ Object
update @error, @last_error, and @sum_error.
-
#integral ⇒ Object
It may seem funny to clamp both @sum_error and the integral term, but we may want different values for these clamps.
- #output ⇒ Object
- #proportion ⇒ Object
- #to_s ⇒ Object
Methods included from Updateable
Constructor Details
#initialize(setpoint, dt: TICK, low_pass_ticks: 0) {|_self| ... } ⇒ PIDController
Returns a new instance of PIDController.
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/device_control.rb', line 204 def initialize(setpoint, dt: TICK, low_pass_ticks: 0) super(setpoint) @dt = dt @error, @last_error, @sum_error = 0.0, 0.0, 0.0 if low_pass_ticks > 0 @mavg = MovingAverage.new(low_pass_ticks) else @mavg = nil end # gain / multipliers for PID; tunables @kp, @ki, @kd = 1.0, 1.0, 1.0 # optional clamps for PID terms and output @p_range = (-Float::INFINITY..Float::INFINITY) @i_range = (-Float::INFINITY..Float::INFINITY) @d_range = (-Float::INFINITY..Float::INFINITY) @o_range = (-Float::INFINITY..Float::INFINITY) @e_range = (-Float::INFINITY..Float::INFINITY) yield self if block_given? end |
Instance Attribute Details
#d_range ⇒ Object
Returns the value of attribute d_range.
198 199 200 |
# File 'lib/device_control.rb', line 198 def d_range @d_range end |
#dt ⇒ Object
Returns the value of attribute dt.
198 199 200 |
# File 'lib/device_control.rb', line 198 def dt @dt end |
#e_range ⇒ Object
Returns the value of attribute e_range.
198 199 200 |
# File 'lib/device_control.rb', line 198 def e_range @e_range end |
#error ⇒ Object
Returns the value of attribute error.
198 199 200 |
# File 'lib/device_control.rb', line 198 def error @error end |
#i_range ⇒ Object
Returns the value of attribute i_range.
198 199 200 |
# File 'lib/device_control.rb', line 198 def i_range @i_range end |
#kd ⇒ Object
Returns the value of attribute kd.
198 199 200 |
# File 'lib/device_control.rb', line 198 def kd @kd end |
#ki ⇒ Object
Returns the value of attribute ki.
198 199 200 |
# File 'lib/device_control.rb', line 198 def ki @ki end |
#kp ⇒ Object
Returns the value of attribute kp.
198 199 200 |
# File 'lib/device_control.rb', line 198 def kp @kp end |
#last_error ⇒ Object
Returns the value of attribute last_error.
198 199 200 |
# File 'lib/device_control.rb', line 198 def last_error @last_error end |
#low_pass_ticks ⇒ Object
Returns the value of attribute low_pass_ticks.
198 199 200 |
# File 'lib/device_control.rb', line 198 def low_pass_ticks @low_pass_ticks end |
#mavg ⇒ Object (readonly)
Returns the value of attribute mavg.
202 203 204 |
# File 'lib/device_control.rb', line 202 def mavg @mavg end |
#o_range ⇒ Object
Returns the value of attribute o_range.
198 199 200 |
# File 'lib/device_control.rb', line 198 def o_range @o_range end |
#p_range ⇒ Object
Returns the value of attribute p_range.
198 199 200 |
# File 'lib/device_control.rb', line 198 def p_range @p_range end |
#sum_error ⇒ Object
Returns the value of attribute sum_error.
198 199 200 |
# File 'lib/device_control.rb', line 198 def sum_error @sum_error end |
Class Method Details
.tune(type, ku, tu) ⇒ Object
ku = ultimate gain, tu = oscillation period output includes ti and td, which are not necessary typically kp, ki, and kd are used
187 188 189 190 191 192 193 194 195 196 |
# File 'lib/device_control.rb', line 187 def self.tune(type, ku, tu) record = ZN[type.downcase] || ZN[type.upcase] || ZN.fetch(type) kp, ti, td, ki, kd = *record kp *= ku if kp ti *= tu if ti td *= tu if td ki *= (ku / tu) if ki kd *= (ku * tu) if kd { kp: kp, ti: ti, td: td, ki: ki, kd: kd } end |
Instance Method Details
#derivative ⇒ Object
261 262 263 |
# File 'lib/device_control.rb', line 261 def derivative (@kd * (@error - @last_error) / @dt).clamp(@d_range.begin, @d_range.end) end |
#input=(val) ⇒ Object
update @error, @last_error, and @sum_error
228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/device_control.rb', line 228 def input=(val) @measure = val @last_error = @error @error = @setpoint - @measure # Incorporate @ki here for better behavior when @ki is updated # It's a good idea to clamp the accumulated error so that if we start # way under setpoint, we don't accumulate so much error that we spend # too much time overshooting to counteract it @sum_error = (@sum_error + @ki * @error * @dt).clamp(@e_range.begin, @e_range.end) # update mavg here to ensure only one update per PID input @mavg.input = self.derivative if @mavg end |
#integral ⇒ Object
It may seem funny to clamp both @sum_error and the integral term, but
we may want different values for these clamps. @e_range is just to
make sure we don't create a mountain to chew through. @i_range gives
additional flexibility for balancing P I & D
257 258 259 |
# File 'lib/device_control.rb', line 257 def integral @sum_error.clamp(@i_range.begin, @i_range.end) end |
#output ⇒ Object
242 243 244 245 246 247 |
# File 'lib/device_control.rb', line 242 def output drv = @mavg ? @mavg.output : self.derivative (self.proportion + self.integral + drv).clamp(@o_range.begin, @o_range.end) end |
#proportion ⇒ Object
249 250 251 |
# File 'lib/device_control.rb', line 249 def proportion (@kp * @error).clamp(@p_range.begin, @p_range.end) end |
#to_s ⇒ Object
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/device_control.rb', line 265 def to_s [super, format("Error: %+.3f\tLast: %+.3f\tSum: %+.3f", @error, @last_error, @sum_error), format(" Gain:\t%.3f\t%.3f\t%.3f", @kp, @ki, @kd), format(" PID:\t%+.3f\t%+.3f\t%+.3f\t= %.5f", self.proportion, self.integral, self.derivative, self.output), ].join("\n") end |