In this post, we explain how to discretize and implement a Proportional Integral Derivative (PID), controller. To demonstrate the PID controller implementation, we use a ball beam system and an Arduino microcontroller.
In the s-domain the PID controller has the following form
(1)
where
![Rendered by QuickLaTeX.com U(s)](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-dc3847d3a005c6b567d0df8fb2825a7b_l3.png)
(2)
where
![Rendered by QuickLaTeX.com Y_{r}(s)](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-c54e3064ef93adf4453cf8e9f0f9e0bf_l3.png)
![Rendered by QuickLaTeX.com Y(s)](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-cdbb20e7beb57a0a0307f02671acaef6_l3.png)
![Rendered by QuickLaTeX.com K](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-ea9c87a513e4a72624155d392fae86e2_l3.png)
![Rendered by QuickLaTeX.com T_{i}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-693ae349b5d2e650fecec9835ed7aa4f_l3.png)
![Rendered by QuickLaTeX.com T_{d}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-fbf27974fd99b224f1406cb6b871a84a_l3.png)
In the time domain, the PID controller 1, has the following form
(3)
where
(4)
To obtain 3 from 1, we used the following Laplace transform pairs (by neglecting constants):
(5)
The controller 3 is the continious-time domain. However, to implement the controller using microcrontrollers we need to discretize the controller. There are several ways for discretizing the controller. In this post, we will use the simplest method (by the author’s opinion). To implement the controller, we first differentiate the equation 3:
(6)
Next, using the finite difference method we approximate the first and second derivatives in 6. Due to favorable stability properties, we use backward differences. The first derivative of the control input is approximated as follows
(7)
where
![Rendered by QuickLaTeX.com h](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-14b463d0ecd5b350ced6cf1d6a12eef3_l3.png)
![Rendered by QuickLaTeX.com u_{k}=u(kh)](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-eb1b1e7bdf8800f3339f73d331ca6a75_l3.png)
![Rendered by QuickLaTeX.com k=0,1,2,\ldots](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-e512b4e7d68c80cd7d71da0710b764cd_l3.png)
![Rendered by QuickLaTeX.com k](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png)
![Rendered by QuickLaTeX.com kh](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-86e1d909d42e92be214cb3c007c946c7_l3.png)
![Rendered by QuickLaTeX.com u_{k}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-ef3545520a65ca1f0275df1e96f169a8_l3.png)
![Rendered by QuickLaTeX.com h](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-14b463d0ecd5b350ced6cf1d6a12eef3_l3.png)
Similarly, we approximate the first derivative of the control error
(8)
The second derivative of the control error is approximated as follows
(9)
By substituting \eqref{firstDerivativeApproximationError} for the time indices
![Rendered by QuickLaTeX.com k](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png)
![Rendered by QuickLaTeX.com k-1](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-1a4c925a5ad5141d0726a09015ceadcd_l3.png)
(10)
By substituting the approximations 7, 8, and 10 in 6, we obtain the final expression for the control signal at the time instant
![Rendered by QuickLaTeX.com k](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png)
(11)
where the constants
![Rendered by QuickLaTeX.com K_{0}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-56a996a165d814c57f392ec92b73099f_l3.png)
![Rendered by QuickLaTeX.com K_{1}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3529553514adf14684c8942296f306b5_l3.png)
![Rendered by QuickLaTeX.com K_{2}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-062e0aa37582a4c9259ff174fc58b7a7_l3.png)
(12)
From 11 we see that the control action at the time instant
![Rendered by QuickLaTeX.com k](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png)
![Rendered by QuickLaTeX.com k-1](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-1a4c925a5ad5141d0726a09015ceadcd_l3.png)
![Rendered by QuickLaTeX.com k](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-3422b6bb5c160593658b7c39425d9880_l3.png)
![Rendered by QuickLaTeX.com k-1](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-1a4c925a5ad5141d0726a09015ceadcd_l3.png)
![Rendered by QuickLaTeX.com k-2](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-8955ced0cb9dd14affb6ad51a984c258_l3.png)
for k=0,1,2,3,…
1. Obtain sensor measurements.
2. Compute the control signal
![Rendered by QuickLaTeX.com u_{k}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-ef3545520a65ca1f0275df1e96f169a8_l3.png)
3. Send the control signal
![Rendered by QuickLaTeX.com u_{k}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-ef3545520a65ca1f0275df1e96f169a8_l3.png)
4. Wait for
![Rendered by QuickLaTeX.com h_{1}](https://aleksandarhaber.com/wp-content/ql-cache/quicklatex.com-0ea7d27a092c073002649a51b6bee6e3_l3.png)
where is an additional delay introduced in the loop. The time of executing the steps 1-3 plus the time
should be equal to the discretization step
. There are several methods for choosing the discretization constant. We explain a simple method that does not require additional mathematical explanation.
The discretization step is chosen on the basis of how fast the system response is. Namely, we can apply a step control input and on the basis of the response, we can identify a rise time . Then we can select the discretization constant to be 10-20 smaller than the rise time. However, we should always keep in mind that the discretization constant is large enough such that the steps 1-3 in the control loop can be computed. These steps also contain additional substeps that will be explained in the sequel. In practice, we can measure the time it takes for the steps 1-3 to execute, and then we can select an additional delay time
, such that the total time is equal to the discretization constant.
In the sequel, we present the code that implements the PID controller. The code can be found here. We use the ball-beam system that is used in the following video to demonstrate the PID controller tuning.
//sensor parameters
int distanceSensorPin = A0; // distance sensor pin
float Vr=5.0; // reference voltage for A/D conversion
float sensorValue = 0; // raw sensor reading
float sensorVoltage = 0; // sensor value converted to volts
float k1=16.7647563; // sensor parameter fitted using the least-squares method
float k2=-0.85803107; // sensor parameter fitted using the least-squares method
float distance=0; // distance in cm
int noMeasurements=200; // number of measurements for averaging the distance sensor measurements
float sumSensor; // sum for computing the average raw sensor value
// motor parameters
#include <Servo.h>
Servo servo_motor;
int servoMotorPin = 9; // the servo motor is attached to the 9th Pulse Width Modulation (PWM)Arduino output
// control parameters
float desiredPosition=35; // desired position of the ball
float errorK; // position error at the time instant k
float errorKm1=0; // position error at the time instant k-1
float errorKm2=0; // position error at the time instant k-2
float controlK=0; // control signal at the time instant k
float controlKm1=0; // control signal at the time instant k-1
int delayValue=0; // additional delay in [ms]
float Kp=0.2; // proportional control
float Ki=10; // integral control
float Kd=0.4; // derivative control
float h=(delayValue+32)*0.001; // discretization constant, that is equal to the total control loop delay, the number 32 is obtained by measuring the time it takes to execute a single loop iteration
float keK=Kp*(1+h/Ki+Kd/h); // parameter that multiplies the error at the time instant k
float keKm1=-Kp*(1+2*Kd/h); // parameter that multiplies the error at the time instant k-1
float keKm2=Kp*Kd/h; // parameter that multiplies the error at the time instant k-2
void setup()
{
Serial.begin(9600);
servo_motor.attach(servoMotorPin);
}
void loop()
{
unsigned long startTime = micros(); // this is used to measure the time it takes for the code to execute
// obtain the sensor measurements
sumSensor=0;
// this loop is used to average the measurement noise
for (int i=0; i<noMeasurements; i++)
{
sumSensor=sumSensor+float(analogRead(distanceSensorPin));
}
sensorValue=sumSensor/noMeasurements;
sensorVoltage=sensorValue*Vr/1024;
distance = pow(sensorVoltage*(1/k1), 1/k2); // final value of the distance measurement
errorK=desiredPosition-distance; // error at the time instant k;
// compute the control signal
controlK=controlKm1+keK*errorK+keKm1*errorKm1+keKm2*errorKm2;
// update the values for the next iteration
controlKm1=controlK;
errorKm2=errorKm1;
errorKm1=errorK;
servo_motor.write(94+controlK); // the number 94 is the control action necessary to keep the ball in the horizontal position
// Serial.println((String)"Control:"+controlK+(String)"---Error:"+errorK);
// these three lines are used to plot the data using the Arduino serial plotter
Serial.print(errorK);
Serial.print(" ");
Serial.println(controlK);
unsigned long endTime = micros();
unsigned long deltaTime=endTime-startTime;
// Serial.println(deltaTime);
// delay(delayValue); // uncomment this to introduce an additional delay
}
A few comments are in order. The code lines 3-11 are used to define the sensor parameters. We are using a SHARP infrared distance sensor to measure the ball position on the beam. The procedure for calibrating the distance sensor is given in another post, and explained in the video below.
The code lines 14-16 are used to define the servo-motor parameters. The code lines 20-35 are used to define the controller parameters. The code lines 20-25 define respectively variables for . The code 26 defines the variable
. The code lines 28-31 define respectively
. The code lines 33-35 define respectively
.
The code lines 44 and 77 are used to measure the time it takes for the code to execute. This is necessary for obtaining the exact value of the constant . On the basis of the measurements, we have assigned the value of the constant
in the code line 31 (this required some online code testing). The code lines 47-56 are used to average the raw sensor data and to compute the distance in [cm]. For more details see this post. The code lines 59-67 are used to compute the control errors, control actions, and to update the values of errors and control actions for the next iteration. The code line 69 is used to send the control actions to the actuator. Notice that 94 corresponds to the horizontal beam position. The code lines 73-75 are used to plot the results using the Arduino serial plotter.