We take an in-depth look at PID Control and how it can be used in your own projects, including a simple garden watering system that controls water flow from a gravity-fed water reservoir.
BUILD TIME: A WEEKEND
DIFFICULTY: Intermediate
There are many incredibly interesting topics in engineering that get a bad rap because of their perceived complexity. While mathematics and physics underpin all fundamental interactions within our world, not all of us are mathematically minded.
One of such topics is Proportional-Integral-Derivative Control, or PID Control for short. Simply put, it is a method of meeting a specific goal within a system, given that it has a way to control an output. Unfortunately, visit the Wikipedia page on PID controllers and you’ll be quickly met with an overwhelming amount of mathematics! Even if you are handy with equations or a touch of calculus, it may not be intuitively apparent as to why this system is so clever.
PID control is not a weird, obscure piece of engineering theory either. We’re confident in saying that throughout your day, you’re likely to use a huge variety of different appliances and machines that rely on PID to keep them running and reliable. Coffee machines, cruise control, thermostats, solar charge controllers and thousands of industrial machines use PID constantly.
But, of course, you’re not here to read about how to build a coffee machine. Later on, we’re going to teach you how to implement PID control into your own Arduino-based projects. Buckle up!
How It Works
At its core, PID Control is a type of Closed-Loop Control System. Let’s say that we have some sort of quantifiable goal, and some form of output that we can use to reach this goal. We’ll be using the example of a car’s cruise control system throughout this article, as it’s a very good application of a control loop.
Our quantifiable goal is the speed we have set that the car should reach - for instance, 100kph. This is known as a ‘setpoint’. A very simple Open Loop control system in this case would be to push the accelerator in accordance with how fast we are desiring to go. So, we tell the car’s computer to push the accelerator twice as hard when our target is 100kph compared to 50kph.
Immediately, it should be apparent as to why this system would work horribly and be quite dangerous too - we’re not factoring in our current speed, so we have no feedback. The target and actual speed will be wildly different, especially if we’re going up or down a hill.
When we talk about a closed-loop control system, we’re effectively talking about a system with feedback. In other words, the control system is able to compare the setpoint with the current value, or ‘process variable’. A closed-loop control system is a general concept - the diagram for how the system works is fairly universal.
A PID controller is a specific case of a closed-loop control system, and a very widely used one at that. The real beauty of this system is hidden behind some slightly daunting mathematics, which turns some makers off using such a powerful system.
The Maths
To get it out of the way, this is the equation that describes a basic PID control system. But, never fear, we’re going to break it down in a way that makes sense.
You will notice there are three separate terms added together, representing each component we’ll be discussing shortly. In front of each term, there is a ‘K’ multiplier that represents the gain of each. The higher this multiplier, the stronger that term is. Depending on how these values are picked, you may find that your system becomes unstable, doesn’t reach the setpoint responsively (or at all), oscillates too much, or just causes poor behaviour in general.
By calculating these terms and adding all of them together, we end up with the output of our system. It may be your car’s accelerator, your thermostat’s heater element, or any other ‘process’ that takes an input, and can output the current value or state of itself.
Proportional
The first term of PID is Proportional, and is the simplest to understand. All terms of PID operate on the difference between setpoint and the current value, rather than an absolute value, known as the ‘error’. In our cruise control analogy, if our setpoint was 100kph, and our current speed is 90kph, this means that our error is 10kph.
The error function e(t) is defined as below. This error function simply takes the difference between the current value y(t) and the target value r(t), and returns the result. The (t) in brackets represents the current time. In a real-world implementation, we would simply take the last value we read from the sensor of choice and assign it to y(t), and r(t) would be our set target.
Going back to the original proportional equation, we can see it involves two values multiplied. The Kp value is the weight of this term, and the larger this weight is, the stronger the proportional term becomes. We’ll talk more about that as we progress.
The proportional term increases linearly with the size of the error, and is applied to the output. So, if our error doubles, so does the output, assuming none of the other terms are applied yet, of course. If the error is negative, the output becomes negative, and so forth.
However, there are some problems with our single Proportional term. As external influences make getting to the setpoint easier or harder, we may find ourselves being unable to reach the setpoint with the Proportional term alone.
Driving up a hill with cruise control on, using only a simple proportional term, would not be able to press the accelerator harder to maintain a setpoint. Because it only relies on the error, it will respond the same way no matter how long we’re under or overshooting the setpoint. This is where the Integral term comes in!
Integral
The integral term fixes this problem by making the output stronger or weaker depending on how long the error is around for. Driving up a hill will cause the error to grow as the car slows down, and if the error does not subside quickly, the integral term will begin accumulating and pushing the accelerator harder. Let’s break down the equation above a bit more. As usual, we have the Ki term that determines how strong this term is. Once we have a practical implementation of the system, we will need to ‘tune’ this value to ensure the system behaves well.
After that, we have an integral, which essentially ‘adds up’ the area underneath a given function. The error function, in this case. The ‘how’ and ‘why’ of this may seem somewhat confusing to those who have gone a few years without high school calculus. We mentioned before that the integral term, unlike the proportional term, depends on how long the error is present for.
On the graph shown here, we’ve plotted a hypothetical curve that visualises the error as time changes. Just after t=0 (0 seconds), we can see that the error quickly increases from 0 to 10. Maybe our car has just hit a pothole, or lost speed suddenly in some way.
The integral term adds up the area between the error line e(t) and the x-axis up to the current time, shown by the blue shaded area. The total area is then the resulting value of the integral, which in this case linearly increases. As the error sticks around for an increasing amount of time, the area covered by the error line increases, making the integral term stronger and stronger. If this is confusing, don’t worry - just know that the integral term is similar to the proportional term, but it also accounts for how long the error has been around for.
However, the integral term introduces another problem into our situation - overshoot. By the time it’s pushed the accelerator of the car hard enough, it may well have overdone it! We reach the speed, and then we go past it. Now, we’re above the set speed and the integral term tries to then reduce the speed. If this ‘ringing’ behaviour subsides, and we eventually settle on the setpoint, this is known as a ‘critically damped system’.
However, if this behaviour causes the output to oscillate continuously, we end up with unwanted oscillations of pushing the accelerator harder, then softer, then harder again indefinitely. For some applications, this is okay if smoothness is not essential. In the context of a car, we’d rather not involuntarily put our occupants into a kangaroo-themed rollercoaster. Note that even if our system is critically damped, we still would aim to reduce the temporary ringing effect in order to smooth out our system.
So, to calm down the effects of the integral term, we need to introduce the third and final member of PID control.
Derivative
The final term in PID Control is the derivative. Simply put, it has the ability to affect the output depending on how rapidly the error is growing or shrinking. Why would this be useful?
As we mentioned, sometimes the integral term has a few too many energy drinks for breakfast and causes the output to oscillate rapidly. The derivative term is intended to account for these rapidly changing oscillations and apply corrections in the opposite direction to nullify them.
The derivative (de(t)/dt) term produces a value that is equal to the instantaneous rate of change of the error function. In English, that means that if our car accelerates quickly and approaches the setpoint, this term will be negative and quite large - since our error is diminishing quickly. If we’re staying at around the same error, the term is 0, and if we’re getting further away from the setpoint, the term becomes positive. The thing that is most confusing about this for most people is remembering that we aren’t interested in the change of the process variable (the car’s speed in our analogy), we’re interested in our change of error - that’s why it can be negative, even though we may still be beneath the setpoint.
Let’s say we’re stuck in a loop of oscillation thanks to the integral term. Sure, on average we’re at the setpoint, but we’re constantly going above and below it. The accelerator is being pushed quite hard, and shortly after that we cross the setpoint (i.e. we’re no longer going too slowly, we’re going too fast!) The integral term right now has ramped all the way up, and can’t react quickly to crossing the setpoint - it has to cool down before it lifts off the accelerator.
The derivative term notices that the error is diminishing very quickly, and thus it becomes negative. Because we add all P, I and D terms together, the derivative term acts to reduce the effects of everything else, which (hopefully) nullifies the oscillations.
An important point to remember is that the derivative term doesn’t care what size the error is, it’s only interested in the rate at which it’s changing. This means that there is no way for it to bring the error to zero (achieve the setpoint) by itself - it must work in tandem with the other terms.
The Real World
Every engineer wishes that the real world works as well as the equations suggest it does. Air resistance, friction, temperature and manufacturing inaccuracies are just some of the problems we makers have to consider when implementing some form of control system or mathematical model into our projects.
So, how does PID control translate into the real world? There are a couple of important aspects of PID control that make sense in mathematics, but don’t always behave the way we’re expecting once they’re working (or not working) on a workbench.
Since PID uses one input - our process variable - and one output - the control variable - we have to ensure that the information we collect and transmit to these variables are appropriately filtered.
Input signals in particular may be quite noisy. If you were building a thermostat with PID control, and your temperature sensor has poor resolution or is consistently noisy, the error function will rapidly change. This will tell your control system to drive the output to fix the potentially-nonexistent error, which either may make the system inaccurate or unstable. If your control system has a considerable weighting on the derivative term, (Kd) this may cause severe oscillation due to rapidly changing error values.
For this reason, in the real world the derivative term is often used with a digital low pass filter (e.g. averaging the input) to smooth out the noisy input. That is, of course, if the derivative term is used at all. Some resources online referring to the theory of PID control hilariously call the ‘D’ term the ‘Do Not Use Term’ - many industrial control systems have little or no weighting on the derivative because it causes more problems than it fixes. Regardless, it depends on the dynamics of your system. Your project may hugely benefit from having a Derivative term, or it may not.
It’s also important that your output value is appropriately responsive. With our cruise control analogy, if the car takes some time to increase the engine speed, there will be a short time delay before pressing the accelerator and actually accelerating the car. The integral term in particular won’t like this, as it will keep pressing the accelerator harder in the meantime. Again, this could cause oscillation after we pass the setpoint.
Certain outputs in the real world have limitations that need
to be addressed. Since PID control can produce a negative output, and you can’t press the accelerator by a negative amount, we would need to either treat the brake pedal as the negative response, or not have one at all. Engine braking and rolling resistance would slow the car when the pedal is not pressed in any case.
As another example, coffee machines frequently use PID control for regulating their inbuilt heater units to around 92° so as to not burn the ground coffee during extraction. Thinking about how this system would be made, the onboard controller would only have one output - heating the currently circulating water by a variable amount. There is no way to produce a ‘negative’ temperature change solely by means of the heater coils, it just has to wait until the water cools through conduction into nearby materials - unless, for some strange reason, you have a refrigerator built into your coffee machine.
Tuning
We mentioned that each of the PID terms has a ‘weighting’. Increasing or decreasing each of the ‘K’ coefficients in front of each term changes the behaviour of the system, sometimes drastically.
You can see in the diagram shown here, we are demonstrating PID control with varying weightings, where one oscillates indefinitely, one responds quite well, and another takes a long time to get to the setpoint. The correctly damped system is the best, because it quickly approaches the setpoint with little overshoot or undershoot.
There is no magic answer for picking these numbers, because it depends almost entirely on the dynamics of your control system. A set of values that perfectly balances response time and damping in one system may not even reach the setpoint in another. Our answer? Experiment! Use the descriptions we’ve provided above to enhance or reduce the effects of certain behaviours you see in your control system.
Actually, what we just said isn’t entirely true. While it isn’t magic, there is an approach for systematically picking the weightings for each term. The Zeigler-Nichols method, the Tyreus Muyben method or even simulating the behaviour in CAD software are all mathematical approaches to stably tuning your system. However, this is substantially more advanced than just experimenting and finding a nice set of values that work for your specific instance. We’ve left links to some research links in the reading list of this project if that's your thing.
But, there’s no point rambling on about all the nerd stuff without making something cool with it. Let’s build something!
The Build:
Water Flow Controller
To visually demonstrate why PID is so powerful, we opted to build a simple garden watering system that is able to precisely control water flow from a gravity-fed water reservoir. Water flow is important in this situation, as water flow that’s too fast leads to saturating the soil and washing it out, and too little isn’t able to provide enough pressure to water the whole area at once. This system will only act as a working demonstration of PID control, which you could adapt to any project you might have! Projects involving heating/cooling, liquid flow, positional motor control, robots and many more can all massively benefit from PID control.
Let’s draw out the system. We’re going to be having two water buckets, one of which acts as our water tank - perhaps fed from rainwater, and one which will act as our garden. The potential difference between these buckets will cause water to flow down the centre acrylic tube. However, this is not ideal for our garden watering situation, since the water pressure might be too high to begin with, and too low once the water level drops. Even if we have a simple open-close solenoid, we would be able to control whether water flows, but not at what rate it flows at.
Our objective is to have a constant flow of water, no matter the amount or pressure of water already in the reservoir - within reason, of course. This is an ideal candidate for a PID control system, because we can accurately control the flow with a gate valve, and we can accurately measure it with a simple digital water flow sensor. We’ll use an Arduino to calculate the PID response with some code, and then drive a servo motor to move the gate valve up and down.
Hardware Required
1 x Thread Seal Tape (optional)
4 x 15mm Countersunk Wood Screws
2 x Snap-Action Wire Terminals (3-way)
1 x Ball/Gate Valve 15mm
1m 19mm OD Clear Vinyl Piping
1 x Pipe Nipple 1/2" BSP Threaded
4 x Hose Clamp 16-27mm
4 x 13mm Saddle Clamp
1 x 15mm Threaded to 1/2" Plug Adapter
2 x Buckets or Water Tanks (if using as a testbed)
1 x Timber Sheet (Any suitable size for components)
Parts Required: | Jaycar | ||
---|---|---|---|
1 x Arduino Uno or Compatible Board | XC4410 | ||
1 x INA260 Current/Voltage/Power Sensor^ | XC4610 | ||
1 x DF15RSMG 360 Degree Motor # | - | ||
4 x 10mm M3 Screws | HP0404 | ||
4 x M3 Nuts | HP0426 | ||
1 x Length of Double-Sided Tape | NM2823 | ||
1 x Pack of Jumper Wires | WC6027 |
^ Does not need to be this exact sensor. Any Arduino-compatible current sensor module that can read at least 3A (as a ballpark) responsively will work okay.
# When we sourced this motor, we did not realise that it only allows direction control, not speed. We wrote some code that emulates speed control, but if a true continuous speed servo is desired, something like a AR-3606HB may be suitable.
Setting Up
To begin with, we need to secure the gate valve to the servo motor we’re using. After we took off the red handle from the unit, we sandwiched two circular servo plates between the nut on the top of the stem. After checking both plates were aligned, we then screwed two 20mm M3 nuts through the threaded holes. It took a few tries to get everything aligned properly.
It’s a good idea to check if the servo can drive the gate valve well enough, as if your motor does not have sufficient torque, there won’t be much movement!
We’re using a timber sheet to screw everything onto. There isn’t a need for it to be overly durable, as it’s just a platform for us to build the electronics on - it’s more or less a testbed for testing the PID functionality of the system. We designed and printed a small servo bracket specifically for mounting the servo horizontally onto the wood. The bottom holes are countersunk so they can be easily screwed flat with wood screws. The other four holes are used for securing the servo, and need M3 screws and nuts for mounting.
We found that the gate valve doesn’t sit flat when the servo is mounted, so we slipped a few spare perfboards underneath it to make it level. You could always use a 3D print or glue the gate valve in place, but we’re not worrying about that as we are using hose clamps on either side of the valve anyway.
The acrylic tubing can be slid over some hose fittings, which are screwed directly to the gate valve and flow rate sensor. For a bit of added water-tightness, we used some hose clamps around the acrylic. If you end up having water leaking between the hose fittings and valve (Hint: foreshadowing!), some thread seal tape would do wonders for keeping it nice and dry.
To hold everything down, we used saddle clamps and cut off one end of their screw holes to give us enough room to secure them to the wood board.
Arduino
We need to be a bit careful not to get the Arduino drenched during our water flow experiments. As such, we’ll be mounting it horizontally across from the acrylic tubing. If there’s a leak, it shouldn’t spill onto the Arduino.
Mounting the Arduino to the board is quite simple, only needing some double-sided tape and the plastic ‘undertray’ case that the official Arduino Uno includes. We like using this with projects involving liquids since it prevents any seeping water from shorting out the traces underneath the Arduino.
Limits of Output
Just like a car's accelerator has a limited range of movement, our servo motor can’t move indefinitely - or, more precisely, when the gate valve becomes fully open or closed. We need to be conscious about how our system will behave when the servo motor reaches the valves limits. If we let the servo motor continue to apply torque when the valve is fully open or closed, it will try to damage the next weakest component in the system. The connection between the servo and valve may become loose, or the mounting hardware may become loose.
As such, we need to stop the servo from moving when it reaches its limits. Normally, one way of doing this is with limit switches. By placing limit switches at both ends of the servo’s travel, it is possible to automatically stop the servo when it reaches a potentially dangerous location. However, our ball valve doesn’t change at all externally as it is being opened or closed - the gate system is purely internal. The servo motor also does not know its absolute position, it can only go in reverse, forward and stop completely.
The easiest way to detect the gate valve reaching its limits is to use the fact that resistance dramatically increases once it has reached a fully open or fully closed position. The servo motor will then stall, and its current usage will skyrocket significantly. If we continuously monitor this current and stop moving the motor whenever we detect a substantial and persistent rise, we can detect the limits of the motor.
For this, we’re using an INA260 current and voltage sensor. This is a high-precision unit that is easily capable of making fast and reliable readings. We’re not really interested in voltage here, so we’re just using the current measurement capabilities of the chip. We picked up a breakout board that needs a set of screw terminals and headers soldered to it, but besides that, it is good to go. It uses a simple I2C bus and can quickly relay the available power usage information to the Arduino it is attached to. Since we’re measuring current, we can hook this unit in between the Servo power and the 7.2V rail it is powered off. This won’t (and shouldn’t) be used to power the Arduino and thus it will not interfere with any current readings we receive.
The INA260 sensor, the Servo Motor and the Flow Rate sensor is all that needs to be connected for this project. We’ve provided the schematic for connecting these devices. Do NOT connect the power supply for the Servo to the Arduino, as they are different voltages. You will need to connect the grounds together between the Arduino and the servo power though.
Back to the main board. We added more acrylic tubing to keep the water away from the Arduino and its associated electronics.
Great! All of the electronics and tubing are assembled! We laid it out on the bench for some initial tuning so that the servo wouldn’t cause any damage to itself or its connected hardware. We did a quick test by blowing through the tube to ensure the flow meter works and the valve seals properly.
As you would expect from a group of knowledgeable engineers, here at DIYODE we use only the most durable and versatile forms of storage for our liquids - that’s right, a soft drink bottle. That’s not entirely a joke, either! We used them for the water bottle rocket project a month or two back just because they’re so accessible and can function as a simple water container in seconds. We just chopped off the bottom of one and jammed it into the acrylic tube for the inlet.
The purpose of this is to represent your water reservoir. This could be your garden’s rainwater tank, or some form of pressure-fed water system. It’s important that the bottle is physically above the valve and flow meter so that gravity can provide a form of natural pressure. It is also handy for testing how stable our PID control system is, since the water will deplete over time and thus the water pressure will too.
Our flow system is finished! To test it, we taped it to a small step ladder and connected the power to the Servo and Arduino. Doing this inside is slightly frustrating because the project eventually turns into a man-vs-machine water fight, but it’s still convenient because we have immediate access to the Serial console to see how things on the Arduino are running.
Finally, we added a simple 3L collection bucket beneath the output tube. In a real implementation of this system, this would represent the garden or irrigation system. You may also notice that the wood and surrounding floor area is somewhat wet! We, unfortunately, didn’t do the best job of fitting everything together in a totally watertight way, because a couple of slow leaks appeared. Nothing that greatly impacts the performance of the system, but annoying nonetheless. As we mentioned, this could be easily fixed with some thread seal tape.
The Code
As you may expect, the heavy lifting of this project occurs in the code, which can be downloaded from our website.
Before we focus on the mathematics of the PID control, let's work out what we need to make it all work - namely, the process variable and the setpoint. The setpoint is the water flow rate we are targeting, and the process variable is the current flow rate we are at.
To find our process variable, we need to derive a value from the flow rate sensor. We could do it in arbitrary number units, but handily enough the datasheet of the flow rate sensor specifies that 1Hz = 7.7L/Hr. This means that we can convert a frequency reading we get directly to a flow rate measurement.
To actually find the frequency, we can measure the time gap between each pulse we receive. So that we don’t miss any pulses, we can set up an Interrupt Service Routine (ISR).
attachInterrupt(digitalPinToInterrupt(3), flowPulse, RISING);
This flowPulse() function essentially calculates the period of time between the current and last pulse, which we use later in the main code loop to derive the frequency. There are a couple of quirks with this code which is important to address if you’ll be using this code in a practical project.
First of all, any global variables referenced by an ISR must be declared volatile. This means that you’re telling the compiler, “Hey, this variable could change at any time!” - this is vital as an interrupt can fire at any time, even when the code is in the middle of the main loop.
The ‘unsigned long’ data type will overflow (i.e. wrap back to zero) at ~70 minutes, when trying to contain values from the micros() command. This is because the micros() command returns the current time in microseconds (1,000,000 per second!), it will therefore reach the limit of the 32-bit data type quite quickly. If you have some form of project that will be running for an extended period of time, you may wish to implement a workaround for this issue. While we haven’t tested it, it should be possible to use the ‘unsigned long long’ data type to upgrade it to 64 bits - this would increase the lifespan to half a million years at the expense of a significant amount of processing time and memory usage.
volatile unsigned long last_rise = 0;
volatile unsigned long pulse_gap = 0;
void flowPulse() {
pulse_gap = micros() - last_rise;
last_rise = micros();
}
During the loop, we can calculate the actual frequency as follows. It follows the simple formula of f = 1/T with units considered. We should also check that the flow rate meter is still spinning, as otherwise, the frequency will never quite reach zero - there aren’t any additional pulses once it reaches 0 RPM.
void loop() {
freq = 1000000/(double)(pulse_gap);
//If we've exceeded the timeout period,
//approximate the frequency as zero.
//Also approximate the frequency to be zero
//if the above division is infinite/NaN.
if(last_rise/1000 + timeout_period < millis() || isnan(freq) || isinf(freq)) {
freq = 0;
}
Moving on further through the loop functionality, we can see how we’re handling the motor limits. As we mentioned, we’re using the INA260 to detect when we hit a limit on the gate valve. After we detect a high current reading, we can stop the motor and only allow it to move in the other direction.
Note that we’ve left out some of the motor handling code to keep this article short.
int current = ina.readCurrent();
//Average current so we get a stable reading.
avg_current = (current + avg_current * (current_average_samples - 1)) / current_average_samples;
//Calculate the PID response.
float response = calculatePIDResponse(setpoint);
bool stopped = false;
//If we are:
// - moving backward and are not
// stopped by the negative limit,
// - moving forward and are not
// stopped by the positive limit,
//, propogate the response as usual.
//Otherwise, stop the motor
if( (response < 0 && motor_limit != -1) ||
(response > 0 && motor_limit != 1) )
{
outputMotorSpeed(response);
} else{
outputMotorSpeed(0);
stopped = true;
}
The mathematics of the PID control itself is actually much simpler than you may expect. If you read the earlier investigation into the maths of PID control, you will be surprised to see the actual Arduino-based code isn’t super in-depth. We have three variables, K_p, K_i, and K_d for controlling the weights of each term, the error value, and the output. That’s all that is required.
float calculatePIDResponse(float setpoint) {
float error = setpoint - freq;
//Calculate proportional response
Rp = error * K_p;
//Calculate integral response
//We also need to prevent windup here
if(Rp + Ri + Rd < max_response &&
Rp + Ri + Rd > min_response) {
Ri += K_i * error;
}
//Calculate derivative response
Rd = K_d * (error - lastError);
lastPIDCalculation = millis();
lastError = error;
return Rp + Ri + Rd;
}
Calculating the proportional response is just the current error times by the weighting K_p. Easy! Integral isn’t much harder, but note that because this is a time-based signal, we add to it - hence the usage of the “+=” operator.
Because it will continually accumulate, we also need to add an if statement to ensure the entire PID response won’t go beyond the limits we set. Otherwise, the integral will keep getting larger and larger even when we can’t move the motor any more.
The derivative is based on the difference between this error and the last time we measured the error. Just by looking at this statement, one can tell that the derivative is quite noisy. A measly error change of 1 with a K_d gain of 0.1 would lead to an output change of 0.1. This could be fixed by averaging the error changes more over time. We didn’t put a lot of weight on this term, so we aren’t worrying about it too much.
Finally, we return the sum of all three PID terms. Viola!
Testing
Getting our system going after we’ve implemented our code is fairly simple, however, there is still the time-consuming process of tuning the PID control system.
The best way to do this is to observe how the behaviour of our system changes when tuning the three weights. We found that the Proportional term does a very good job of getting the system most of the way there, however, the integral term also helps with the job.
We found using the derivative term gets quite noisy results - the flow rate sometimes varies from reading to reading, and having any substantial weighting on the derivative term leads it to affect the motor output considerably.
Once we did get a nice and stable output going though, it was actually fairly reliable. We couldn’t remove all oscillation, but it was enough that the flow could be considered reasonably constant. Check our social media for videos of the system in action. Currently, changing the target flow rate is only possible in code, but you could implement a user interface to allow the user to change it.
Where To From Here?
This project is a demonstration of PID in a practical context - we wanted to go down this route rather than use the whole time explaining mathematics! We hope that this project inspires you to use PID control in one of your own projects, as it can be used for a variety of control systems. Projects involving temperature, liquids, distances or motors can usually use PID control and work well if tuned correctly!
As cool as PID control is, don’t get entrapped in its implementation just for the sake of doing it. Often, a simpler control system will easily do what you want with less effort. Sometimes, a simple proportional controller or even a ‘bang-bang’ system (i.e. on or off, like how an electric kettle boils water) will do just fine.
In fact, during the testing of this project, we confirmed that a simple proportional controller is only marginally worse than a full PID controller, with average error values around 10% larger than the PID controller - albeit with more oscillation. This will, of course, depend on the dynamics of your particular system. If your system is sensitive to offset, then a PI or PID controller would be essential.
PID really shines in systems that are very responsive. Robotic control systems with beefy stepper motors are one use that comes to mind immediately. We’ve seen many videos on YouTube of makers using PID to balance marbles on platforms, regulating the position of a mechanical component, and spinning an object at a precise rate.
We hoped you learned something from this! If you’re planning on creating something cool with a PID control system, be sure to chuck it on social media and tag us at @diyodemag.
Reading & Resources:
Tuning Papers https://www.ripublication.com/ijaer18/ijaerv13n8_07.pdf