We continue with the series of posts dedicated to the theory of controllers, seeing how to implement a PID control in a microprocessor like Arduino.
In previous posts we have seen an introduction to control theory, presented the on/off controller with hysteresis, the powerful PID controller, and seen how to adjust the parameters of a PID controller.
Now it’s time to leave the theory, dust off the keyboard, see how a PID controller is implemented in a microprocessor like Arduino. Fortunately, making a basic PID is quite simple and, as we will see in the second part, we have a very complete library available to make it even easier.
A PID is an essential element when addressing a large number of interesting projects, such as automatically regulating the level of light, maintaining temperature, ensuring that a motor rotates at a constant speed, stabilizing platforms, making a robot move straight, or even robots that remain balanced on two wheels.
Manual PID controller
First let’s see how to implement a simple controller ourselves. In fact, the code is not overly complex. Let’s assume our controller takes the input of the controlled variable at A0, and outputs it using a PWM signal on pin 3.
The code for a basic PID could be as follows
// Pin assignments
const int PIN_INPUT = A0;
const int PIN_OUTPUT = 3;
// Controller constants
double Kp=2, Ki=5, Kd=1;
// External controller variables
double Input, Output, Setpoint;
// Internal controller variables
unsigned long currentTime, previousTime;
double elapsedTime;
double error, lastError, cumError, rateError;
void setup()
{
Input = analogRead(PIN_INPUT);
Setpoint = 100;
}
void loop(){
pidController.Compute();
Input = analogRead(PIN_INPUT); // read a controller input
Output = computePID(Input); // calculate the controller
delay(100);
analogWrite(PIN_OUTPUT, Output); // write the controller output
}
double computePID(double inp){
currentTime = millis(); // get current time
elapsedTime = (double)(currentTime - previousTime); // calculate elapsed time
error = Setpoint - Input; // determine the error between setpoint and measurement
cumError += error * elapsedTime; // calculate the integral of the error
rateError = (error - lastError) / elapsedTime; // calculate the derivative of the error
double output = kp*error + ki*cumError + kd*rateError; // calculate the PID output
lastError = error; // store previous error
previousTime = currentTime; // store previous time
return output;
}
As we can see it is not too difficult. We have a ‘computePID()’ function that does all the work. In this function we calculate the time elapsed between calls, which we need to calculate both the derivative and the integral of the error.
Next, we compare the controller input with the setpoint to determine the error and perform the ‘pseudo’ integrals and derivatives (their discrete equivalent). Finally, we calculate the system response using the PID formula, and save the values for the next cycle.
It wasn’t that hard, right? However, although it is completely functional, our PID controller is quite simple. Therefore, it has certain deficiencies in situations that frequently occur in reality.
We could improve our code, but it was not going to be so simple or fast to do it. And the best part is, it’s not necessary! To our delight, someone has done all the work for us, as we will see in the next section.
PID controller with library
Fortunately we have available the PIDController library, based on the ArduinoPID library developed by Brett Beauregard.
ArduinoPID is a little wonder that contains many improvements over a basic PID, which are detailed in this link from the author. I recommend that you read the documentation of the library because it is a whole class on PID controllers.
With this, creating a PID controller on Arduino is as simple as the following code.
#include <PIDController.hpp>
const int PIN_INPUT = 0;
const int PIN_OUTPUT = 3;
PID::PIDParameters<double> parameters(4.0, 0.2, 1);
PID::PIDController<double> pidController(parameters);
void setup()
{
pidController.Input = analogRead(PIN_INPUT);
pidController.Setpoint = 100;
pidController.TurnOn();
}
void loop()
{
pidController.Input = analogRead(PIN_INPUT);
pidController.Update();
analogWrite(PIN_OUTPUT, pidController.Output);
}
We have seen that it is quite easy to implement a PID controller on a microprocessor like Arduino. And it is even easier with the little gem of an Arduino PID library. So there is no reason to have excuses or fears when implementing a PID controller in our projects.
In the next posts of the series, we will start to see practical examples of projects where we can implement a PID controller on Arduino. See you soon!