Tuning the parameters of a PID controller

In this notebook you can test your intuition for how to adjust the parameters of a PID controller.

Start by watching this excellent video.

Blockdiagram

We consider the model used in the video: Velocity control of a car. The plant model describes how the velocity of the car responds to the position of the accelerator (the gas pedal). In addition to the signal from the accelerator, there are also unknown forces such as wind resistance and gravity when the car is going uphill or downhill. These forces are represented by a disturbance signal entering at the input to the system.

The PID controller

The PID-controller is on so-called parallel form \begin{equation} F(s) = K_p + \frac{K_i}{s} + K_d s. \end{equation}

The closed-loop system from the reference signal to the output

The model is linear and hence the principle of superposition holds. This mean that we can look at the response to the reference signal and the response to the disturbance signal separately. Setting $$d=0,$$ we get a closed-loop response given by \begin{equation} Y(s) = \frac{\frac{1}{s(s+1)}F(s)}{1 + \frac{1}{s(s+1)}F(s)}R(s). \end{equation}

The closed-loop system from disturbance to the output

Setting $$r=0,$$ the reponse to the disturbance is given by \begin{equation} Y(s) = \frac{\frac{1}{s(s + 1)}}{1 + \frac{1}{s(s+1)}F(s)}D(s) \end{equation}

The full closed-loop system

We can find the response to a combination of input signals $r$ and $d$ by summation: \begin{equation} Y(s) = \frac{\frac{1}{s(s+1)}F(s)}{1 + \frac{1}{s(s+1)}F(s)}R(s) + \frac{\frac{1}{s(s + 1)}}{1 + \frac{1}{s(s+1)}F(s)}D(s) \end{equation}


In [1]:
# Uncomment and run the commands in this cell if a packages is missing
!pip install slycot
!pip install control

In [3]:
%matplotlib widget
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import control.matlab as cm

Step response

Below you can manipulate the $K_p$, $K_i$ and $K_d$ parameters of the PID-controller, and see a time-response of the system. At time $t_1=1$ there is a unit step in the reference signal, and at time $t_2=10$ yhere is a negative step in the disturbance signal. Note that since we scaled time using the time constant of the system, the time is not measured in seconds but in the length of time constant. So to get $t_2$ in seconds you will have to multiply with the time constant \begin{equation} t_2 = 5 T = 5 \frac{1}{\omega} \end{equation} where $\omega$ has unit $1/s$ or $Hz$.


In [14]:
G1 = cm.tf([1.], [1, 1.])
Gint = cm.tf([1], [1, 0])
G = Gint*G1
print(G)
N = 600
t_end = 30
t = np.linspace(0, t_end, N)  

# The reference signal
r = np.zeros(N)
r[int(N/t_end):] = 1.0

# The disturbance signal
d = np.zeros(N)
d[int(N/t_end)*10:] = -1.0

# set up plot
fig, ax = plt.subplots(figsize=(8, 3))
#ax.set_ylim([-.1, 4])
ax.grid(True)
 
def sim_PID(G, Kp, Ki, Kd, r,d,t):
    """
    Returns the simulated response of the closed-loop system with a 
    PID controller.
    """
    F = cm.tf([Kd, Kp, Ki], [1.0, 0])
    Gr = cm.feedback(G*F,1)
    Gd = cm.feedback(G,F)
    yr = cm.lsim(Gr, r, t)
    yd = cm.lsim(Gd, d, t)
    return (yr, yd)
    
 
@widgets.interact(Kp=(0, 10, .2), Ki=(0, 8, .2), Kd=(0, 8, .2))
def update(Kp = 1.0, Ki=0, Kd=0):
    """Remove old lines from plot and plot new one"""
    [l.remove() for l in ax.lines]
    yr, yd = sim_PID(G, Kp, Ki, Kd, r,d,t)
    ax.plot(yr[1], yr[0]+yd[0], color='C0')
    #ax.plot(yd[1], yd[0], color='C1')


   1
-------
s^2 + s

Exercise

Try to find PID parameters that give

  1. about 10% overshoot,
  2. settling time of about 4,
  3. negligable stationary error at $t=14$ (4 after onset of constant disturbance)

Ramp response

A negative unit ramp disturbance starts at time $t_1=0$.


In [18]:
# The reference signal
dramp = np.linspace(0, t_end, N)
rr = np.zeros(N)

# set up plot
fig, ax = plt.subplots(figsize=(8, 3))
#ax.set_ylim([-.1, 4])
ax.grid(True)
 

@widgets.interact(Kp=(0, 10, .2), Ki=(0, 8, .2), Kd=(0, 8, .2))
def update(Kp = 1.0, Ki=1, Kd=0):
    """Remove old lines from plot and plot new one"""
    [l.remove() for l in ax.lines]
    yr, yd = sim_PID(G, Kp, Ki, Kd, rr,dramp,t)
    ax.plot(yr[1], yr[0]+yd[0], color='C0')
    #ax.plot(yd[1], yd[0], color='C1')


Exercise

Why does the error in the ramp response keep growing although the controller contains an integrating term?


In [ ]: