In this tutorial, we will solve one very important problem that every control or signal processing engineer needs to know how to solve. Namely, in this tutorial, we will learn how to use the pole placement method to select control algorithm parameters such that the poles of the closed-loop system are placed at the desired locations. More precisely, on the basis of the desired locations of the closed-loop poles, we will determine the parameters of the Proportional Integral Derivative or PID control algorithm. We will also see that the control algorithm will introduce zeros in the closed-loop transfer function that will slightly degrade the desired closed-loop response. The YouTube tutorial accompanying this control engineering tutorial is given below.
Problem Statement
We explain the procedure by using the example presented below.
PROBLEM STATEMENT: For a given open-loop transfer function
(1)
Design a feedback controller such that the poles of the closed-loop transfer function are
(2)
and such that the steady-state error of following a step reference signal is asymptotically equal to zero.
The YouTube tutorial explaining how to solve this problem is given below.
Solution
ANALYSIS AND COMMENTS: First, it should be observed that the open-loop transfer function has poles on the imaginary axis. This is not what we want. Such a system exhibits an oscillatory behavior and cannot accurately track a step reference signal. Consequently, we need a feedback control algorithm to asymptotically stabilize the system, and to make sure that the closed-loop system can asymptotically track a reference signal. Also, we want to place closed-loop poles on the real axis (this is the design task). We want to do that in order to ensure a smooth system response with minimal oscillations. That is, we will see the exponential terms
(3)
which will ensure a relatively damped response. However, we will also see that the controller will introduce zeros that will induce some oscillations and overshoot in the response.
SOLUTION: The block diagram of the closed-loop system is given below.
This block diagram is the system representation in the complex Laplace domain. Here
(4)
The first step is to determine the closed-loop transfer function. We have
(5)
From the last equation, we have
(6)
From the last equation, we obtain
(7)
From the last equation, we can see that the closed-loop transfer function is
(8)
We are interested in the closed-loop poles of the system. The closed-loop poles are zeros of the characteristic polynomial of the closed-loop transfer function. The characteristic polynomial is determined by
(9)
The open-loop transfer function is given by the equation (1), on the other hand, we have the freedom to choose the control transfer function. That is, we have the complete freedom to choose our control algorithm. Let us select the Proportional Integral Derivative (PID) control algorithm. The transfer function of the PID controller can be written in this form
(10)
where
(11)
since
(12)
By substituting the controller (10) and the transfer function (1) into the equation for the characteristic polynomial (9), we obtain
(13)
By multiplying the last equation by
(14)
By multiplying the terms in the last equation and grouping them together, we obtain
(15)
On the other hand, our task is to assign the closed-loop poles at the following locations
(16)
From this, we conclude that the desired characteristic polynomial of the closed-loop system should be
(17)
By multiplying the terms in the last expression, we obtain
(18)
By equating (15) and (18), we obtain
(19)
Two polynomials are equal if their coefficients are equal. By using this fact, and the last equation, we obtain
(20)
Since the first equation gives
(21)
By multiplying the first equation with
(22)
Then, by substituting the second equation from (21) into (22), we obtain
(23)
We solve this equation in Python (see the Python code given below), the solution is
(24)
where
(25)
By substituting either
(26)
By substituting this PID controller in the characteristic polynomial equation, and by computing the roots. We can observe that the roots of the characteristic polynomial are equal to the desired values of
# -*- coding: utf-8 -*-
"""
Solution of nonlinear pole placement equation in Python
"""
import numpy as np
# coefficients of the equation for a
coeff = [1, -11/12, 3/8]
aCoeff=np.roots(coeff)
bCoeff=[(3/8)/(aCoeff[0]),(3/8)/(aCoeff[1])]
# double check the result
# a*b= 3/8=0.375
aCoeff[0]*bCoeff[0]
aCoeff[1]*bCoeff[1]
# a+b=11/12=0.9166666666666666
aCoeff[0]+bCoeff[0]
aCoeff[1]+bCoeff[1]
# this is the third coefficient of the controller
K=24
# Cofficients of the controller, first set of coefficients
# (k11*s^{2}+k21*s+k31)/s
k11=np.real(K*(aCoeff[0]*bCoeff[0]))
k21=np.real(K*(aCoeff[0]+bCoeff[0]))
k31=K
print(k11)
print(k21)
print(k31)
# Cofficients of the controller, first set of coefficients
# (k12*s^{2}+k22*s+k32)/s
k12=np.real(K*(aCoeff[1]*bCoeff[1]))
k22=np.real(K*(aCoeff[1]+bCoeff[1]))
k32=K
print(k12)
print(k22)
print(k32)
# double check the final characteristic polynomial
# c1*s^{3}+c2*s^{2}+c3*s+c4
c1=1
c2=np.real(K*aCoeff[0]*bCoeff[0])
c3=4+K*aCoeff[0]+K*bCoeff[0]
c4=K
polyFinal=[c1,c2,c3,c4]
np.roots(polyFinal)
Next, we will substitute the controller and the open-loop transfer function in the closed-loop system. The closed-loop transfer function is
(27)
The zeros of this transfer function are
(28)
and the poles are
(29)
We can observe that we have achieved the desired pole locations. However, we have also introduced zeros in the controller. The real parts of zero are close to the pole
From the figure above, we can observe that there is some overshoot. This overshoot is the direct consequence of the zeros that the controller introduced. Namely, we expected a response that would look similar to the first-order response without an overshoot. This is because the poles are at
To show that the zeros are the cause of this effect, let us modify the transfer function by removing the zeros and by keeping the DC gain to 1 by adding a constant
(30)
This function does not have zeros and it has a DC gain of
We can see that the response of the modified system does not suffer from the overshoot. That is, since
The Python code used to simulate the closed loop system is given below.
import matplotlib.pyplot as plt
import control as ct
import numpy as np
# this function is used for plotting of responses
# it generates a plot and saves the plot in a file
# xAxisVector - time vector
# yAxisVector - response vector
# titleString - title of the plot
# stringXaxis - x axis label
# stringYaxis - y axis label
# stringFileName - file name for saving the plot, usually png or pdf files
def plottingFunction(xAxisVector,yAxisVector,titleString,stringXaxis,stringYaxis,stringFileName):
plt.figure(figsize=(8,6))
plt.plot(xAxisVector,yAxisVector, color='blue',linewidth=4)
plt.title(titleString, fontsize=14)
plt.xlabel(stringXaxis, fontsize=14)
plt.ylabel(stringYaxis,fontsize=14)
plt.tick_params(axis='both',which='major',labelsize=14)
plt.grid(visible=True)
plt.savefig(stringFileName,dpi=600)
plt.show()
# define a transfer function
s = ct.TransferFunction.s
W=1/(s**2+4)
C=(24/s)+22+9*s
# here we define the transfer function
Wcl=W*C/(1+W*C)
Wcl=Wcl.minreal()
print(Wcl)
###############################################################################
# simulate the step response
###############################################################################
timeVector=np.linspace(0,5,100)
timeReturned, systemOutput = ct.step_response(Wcl,timeVector)
# plot the step response
plottingFunction(timeReturned,
systemOutput,
titleString='Step Response',
stringXaxis='time [s]' ,
stringYaxis='Output',
stringFileName='stepResponse1.png',)
###############################################################################
Wcl.poles()
Wcl.zeros()
# now without zeros
Wcl2= 24*Wcl*1/( 9*s**2 + 22*s + 24)
Wcl2=Wcl2.minreal()
print(Wcl2)
###############################################################################
# simulate the step response
###############################################################################
timeVector=np.linspace(0,5,100)
timeReturned, systemOutput = ct.step_response(Wcl2,timeVector)
# plot the step response
plottingFunction(timeReturned,
systemOutput,
titleString='Step Response',
stringXaxis='time [s]' ,
stringYaxis='Output',
stringFileName='stepResponse2.png',)
###############################################################################