Class: DrivingPhysics::PIDController

Inherits:
Object
  • Object
show all
Defined in:
lib/driving_physics/pid_controller.rb

Overview

for example, where to set the throttle to maintain 1000 RPM SP - 1000 RPM PV - current RPM CV - throttle position

Constant Summary collapse

HZ =
1000
TICK =
Rational(1) / HZ
ZN =

Ziegler-Nichols method for tuning PID gain knobs

{
  #            Kp     Ti     Td     Ki     Kd
  #     Var:   Ku     Tu     Tu    Ku/Tu  Ku*Tu
  'P'  =>   [0.500],
  'PI' =>   [0.450, 0.800,   nil, 0.540],
  'PD' =>   [0.800,   nil, 0.125,   nil, 0.100],
  'PID' =>  [0.600, 0.500, 0.125, 1.200, 0.075],
  'PIR' =>  [0.700, 0.400, 0.150, 1.750, 0.105],
  # less overshoot than standard PID below
  'some' => [0.333, 0.500, 0.333, 0.666, 0.111],
  'none' => [0.200, 0.500, 0.333, 0.400, 0.066],
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(setpoint, dt: TICK) {|_self| ... } ⇒ PIDController

Returns a new instance of PIDController.

Yields:

  • (_self)

Yield Parameters:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/driving_physics/pid_controller.rb', line 47

def initialize(setpoint, dt: TICK)
  @setpoint, @dt, @measure = setpoint, dt, 0.0

  # track error over time for integral and derivative
  @error, @last_error, @sum_error = 0.0, 0.0, 0.0

  # 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)

  yield self if block_given?
end

Instance Attribute Details

#d_rangeObject

Returns the value of attribute d_range.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def d_range
  @d_range
end

#dtObject

Returns the value of attribute dt.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def dt
  @dt
end

#errorObject (readonly)

Returns the value of attribute error.



45
46
47
# File 'lib/driving_physics/pid_controller.rb', line 45

def error
  @error
end

#i_rangeObject

Returns the value of attribute i_range.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def i_range
  @i_range
end

#kdObject

Returns the value of attribute kd.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def kd
  @kd
end

#kiObject

Returns the value of attribute ki.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def ki
  @ki
end

#kpObject

Returns the value of attribute kp.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def kp
  @kp
end

#last_errorObject (readonly)

Returns the value of attribute last_error.



45
46
47
# File 'lib/driving_physics/pid_controller.rb', line 45

def last_error
  @last_error
end

#measureObject

Returns the value of attribute measure.



45
46
47
# File 'lib/driving_physics/pid_controller.rb', line 45

def measure
  @measure
end

#o_rangeObject

Returns the value of attribute o_range.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def o_range
  @o_range
end

#p_rangeObject

Returns the value of attribute p_range.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def p_range
  @p_range
end

#setpointObject

Returns the value of attribute setpoint.



43
44
45
# File 'lib/driving_physics/pid_controller.rb', line 43

def setpoint
  @setpoint
end

#sum_errorObject (readonly)

Returns the value of attribute sum_error.



45
46
47
# File 'lib/driving_physics/pid_controller.rb', line 45

def sum_error
  @sum_error
end

Class Method Details

.tune(type, ku, tu) ⇒ Object

ultimate gain, oscillation



32
33
34
35
36
37
38
39
40
41
# File 'lib/driving_physics/pid_controller.rb', line 32

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

#derivativeObject



95
96
97
# File 'lib/driving_physics/pid_controller.rb', line 95

def derivative
  (@kd * (@error - @last_error) / @dt).clamp(@d_range.begin, @d_range.end)
end

#integralObject



91
92
93
# File 'lib/driving_physics/pid_controller.rb', line 91

def integral
  (@ki * @sum_error * @dt).clamp(@i_range.begin, @i_range.end)
end

#outputObject



81
82
83
84
85
# File 'lib/driving_physics/pid_controller.rb', line 81

def output
  (self.proportion +
   self.integral +
   self.derivative).clamp(@o_range.begin, @o_range.end)
end

#proportionObject



87
88
89
# File 'lib/driving_physics/pid_controller.rb', line 87

def proportion
  (@kp * @error).clamp(@p_range.begin, @p_range.end)
end

#to_sObject



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/driving_physics/pid_controller.rb', line 99

def to_s
  [format("Setpoint: %.3f  Measure: %.3f",
          @setpoint, @measure),
   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

#update(measure) ⇒ Object



65
66
67
68
# File 'lib/driving_physics/pid_controller.rb', line 65

def update(measure)
  self.measure = measure
  self.output
end