1
- __all__ = ['sisotool' ,'pid_designer ' ]
1
+ __all__ = ['sisotool' ,'rootlocus_pid_designer ' ]
2
2
3
3
from control .exception import ControlMIMONotImplemented
4
4
from .freqplot import bode_plot
@@ -180,19 +180,21 @@ def _SisotoolUpdate(sys, fig, K, bode_plot_params, tvect=None):
180
180
fig .subplots_adjust (top = 0.9 ,wspace = 0.3 ,hspace = 0.35 )
181
181
fig .canvas .draw ()
182
182
183
- # contributed by Sawyer Fuller, minster@uw.edu 2021.11.02
184
- def pid_designer (plant ,gain = 'P' ,sign = + 1 ,input_signal = 'r' ,
183
+ # contributed by Sawyer Fuller, minster@uw.edu 2021.11.02, based on
184
+ # an implementation in Matlab by Martin Berg.
185
+ def rootlocus_pid_designer (plant ,gain = 'P' ,sign = + 1 ,input_signal = 'r' ,
185
186
Kp0 = 0 ,Ki0 = 0 ,Kd0 = 0 ,tau = 0.01 ,
186
- C_ff = 0 ,derivative_in_feedback_path = False ):
187
- """Manual PID controller design using sisotool
187
+ C_ff = 0 ,derivative_in_feedback_path = False ,
188
+ noplot = False ):
189
+ """Manual PID controller design based on root locus using Sisotool
188
190
189
191
Uses `Sisotool` to investigate the effect of adding or subtracting an
190
192
amount `deltaK` to the proportional, integral, or derivative (PID) gains of
191
193
a controller. One of the PID gains, `Kp`, `Ki`, or `Kd`, respectively, can
192
194
be modified at a time. `Sisotool` plots the step response, frequency
193
195
response, and root locus.
194
196
195
- When first run, `deltaK` is set to1 ; click on a branch of the root locus
197
+ When first run, `deltaK` is set to0 ; click on a branch of the root locus
196
198
plot to try a different value. Each click updates plots and prints
197
199
the corresponding `deltaK`. To tune all three PID gains, repeatedly call
198
200
`pid_designer`, and select a different `gain` each time (`'P'`, `'I'`,
@@ -240,13 +242,13 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
240
242
plant : :class:`LTI` (:class:`TransferFunction` or :class:`StateSpace` system)
241
243
The dynamical system to be controlled
242
244
gain : string (optional)
243
- Which gain to vary by deltaK. Must be one of 'P', 'I', or 'D'
245
+ Which gain to vary by` deltaK` . Must be one of` 'P'`, ` 'I'` , or` 'D'`
244
246
(proportional, integral, or derative)
245
247
sign : int (optional)
246
248
The sign of deltaK gain perturbation
247
249
input : string (optional)
248
- The input used for the step response; must be 'r' (reference) or
249
- 'd' (disturbance) (see figure above)
250
+ The input used for the step response; must be` 'r'` (reference) or
251
+ ` 'd'` (disturbance) (see figure above)
250
252
Kp0, Ki0, Kd0 : float (optional)
251
253
Initial values for proportional, integral, and derivative gains,
252
254
respectively
@@ -257,16 +259,24 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
257
259
C_ff : float or :class:`LTI` system (optional)
258
260
Feedforward controller. If :class:`LTI`, must have timebase that is
259
261
compatible with plant.
262
+ derivative_in_feedback_path : bool (optional)
263
+ Whether to place the derivative term in feedback transfer function
264
+ `C_b` instead of the forward transfer function `C_f`.
265
+ noplot : bool (optional)
266
+
267
+ Returns
268
+ ----------
269
+ closedloop : class:`StateSpace` system
270
+ The closed-loop system using initial gains.
260
271
"""
261
272
262
273
plant = _convert_to_statespace (plant )
263
274
if plant .ninputs == 1 :
264
275
plant = ss2io (plant ,inputs = 'u' ,outputs = 'y' )
265
276
elif plant .ninputs == 2 :
266
- plant = ss2io (plant ,inputs = ( 'u' ,'d' ) ,outputs = 'y' )
277
+ plant = ss2io (plant ,inputs = [ 'u' ,'d' ] ,outputs = 'y' )
267
278
else :
268
279
raise ValueError ("plant must have one or two inputs" )
269
- #plant = ss2io(plant, inputs='u', outputs='y')
270
280
C_ff = ss2io (_convert_to_statespace (C_ff ),inputs = 'r' ,outputs = 'uff' )
271
281
dt = common_timebase (plant ,C_ff )
272
282
@@ -277,29 +287,30 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
277
287
else :
278
288
u_summer = summing_junction (['ufb' ,'uff' ,'d' ],'u' )
279
289
280
- prop = tf (1 ,1 )
281
290
if isctime (plant ):
282
- integ = tf (1 ,[1 ,0 ])
291
+ prop = tf (1 ,1 )
292
+ integ = tf (1 , [1 ,0 ])
283
293
deriv = tf ([1 ,0 ], [tau ,1 ])
284
- else :
285
- integ = tf ([dt / 2 ,dt / 2 ],[1 ,- 1 ],dt )
286
- deriv = tf ([1 ,- 1 ],[dt ,0 ],dt )
294
+ else :# discrete-time
295
+ prop = tf (1 ,1 ,dt )
296
+ integ = tf ([dt / 2 ,dt / 2 ], [1 ,- 1 ],dt )
297
+ deriv = tf ([1 ,- 1 ], [dt ,0 ],dt )
287
298
288
- # add signal names
299
+ # add signal names by turning into iosystems
289
300
prop = tf2io (prop ,inputs = 'e' ,outputs = 'prop_e' )
290
301
integ = tf2io (integ ,inputs = 'e' ,outputs = 'int_e' )
291
302
if derivative_in_feedback_path :
292
- deriv = tf2io (- deriv ,inputs = 'y' ,outputs = 'deriv_ ' )
303
+ deriv = tf2io (- deriv ,inputs = 'y' ,outputs = 'deriv ' )
293
304
else :
294
- deriv = tf2io (deriv ,inputs = 'e' ,outputs = 'deriv_ ' )
305
+ deriv = tf2io (deriv ,inputs = 'e' ,outputs = 'deriv ' )
295
306
296
307
# create gain blocks
297
308
Kpgain = tf2io (tf (Kp0 ,1 ),inputs = 'prop_e' ,outputs = 'ufb' )
298
309
Kigain = tf2io (tf (Ki0 ,1 ),inputs = 'int_e' ,outputs = 'ufb' )
299
- Kdgain = tf2io (tf (Kd0 ,1 ),inputs = 'deriv_ ' ,outputs = 'ufb' )
310
+ Kdgain = tf2io (tf (Kd0 ,1 ),inputs = 'deriv ' ,outputs = 'ufb' )
300
311
301
- # for the gain that is varied,create a special gain block withan
302
- # 'input' and an 'output'signal to create the loop transfer function
312
+ # for the gain that is varied,replace gain block witha special block
313
+ #that has an 'input' and an 'output'that creates loop transfer function
303
314
if gain in ('P' ,'p' ):
304
315
Kpgain = ss2io (ss ([],[],[],[[0 ,1 ], [- sign ,Kp0 ]]),
305
316
inputs = ['input' ,'prop_e' ],outputs = ['output' ,'ufb' ])
@@ -308,13 +319,15 @@ def pid_designer(plant, gain='P', sign=+1, input_signal='r',
308
319
inputs = ['input' ,'int_e' ],outputs = ['output' ,'ufb' ])
309
320
elif gain in ('D' ,'d' ):
310
321
Kdgain = ss2io (ss ([],[],[],[[0 ,1 ], [- sign ,Kd0 ]]),
311
- inputs = ['input' ,'deriv_ ' ],outputs = ['output' ,'ufb' ])
322
+ inputs = ['input' ,'deriv ' ],outputs = ['output' ,'ufb' ])
312
323
else :
313
324
raise ValueError (gain + ' gain not recognized.' )
314
325
315
326
# the second input and output are used by sisotool to plot step response
316
327
loop = interconnect ((plant ,Kpgain ,Kigain ,Kdgain ,prop ,integ ,deriv ,
317
328
C_ff ,e_summer ,u_summer ),
318
- inplist = ['input' ,input_signal ],outlist = ['output' ,'y' ])
319
- sisotool (loop )
320
- return loop [1 ,1 ]
329
+ inplist = ['input' ,input_signal ],
330
+ outlist = ['output' ,'y' ])
331
+ if ~ noplot :
332
+ sisotool (loop ,kvect = (0. ,))
333
+ return _convert_to_statespace (loop [1 ,1 ])