A PID controller example explained in simple words
After 2 pages of theory and mathematical hocus-pocus, there will be people that still are not able to write software for a PID controller. For those people, i have 2 good news: First is that i found the best documentation for making a PID controller with a PIC microcontroller. It is the Application Note 937 from Microchip (who else):
Second good news: A PID controller example explained in simple words
I understand that not all people feel comfortable with mathematics, but many would like to make a PID controller for number of reasons. The integral term is by far the hardest to decipher. Instead of just explaining the integral term, i will explain a complete (and simple) PID controller with an example. I bet that after this, everyone will be able to make one (as long as he know how to program a microcontroller of course).
So, it goes like this: We want to control the temperature of a water heater. The heater resistor is controlled by a TRIAC, and the microcontroller controls the gate of that TRIAC (it could operate as a dimmer for example). A temperature sensor is placed in the water and gives feedback to the microcontroller in degrees Celsius (oC). The system has these parameters which you must define as RAM variables, and also it is good to save them in an EEPROM memory because you do not want to define them every time the system restarts:
SP - The Set-Point of the system, the temperature that we want to achieve
P_Gain - The gain for the Proportional term
I_Gain - The gain for the Integral term
D_Gain - The gain for the Derivative term
I_Time - Reset time, this is the Ti parameter that we saw on previous pages
D_Time - Derivative time, this is the Td parameter that we saw on previous pages
You will also need to define these additional RAM variables:
P_Term - The Proportional term to be added to the output.
I_Term - The Integral term to be added to the output.
D_Term - The Derivative term to be added to the output.
PV - The Process Variable, this is the temperature that the sensor reads.
Err - This will hold the current error (SP-PV).
Acc_Err - This is the accumulated error to help calculate the Integral term
D_Err - This is the derivative error to help calculate the Derivative term
Ti_Counter - A countdown counter for the Integral accumulation routine
Td_Counter - A countdown counter for the Derivative error calculation
P_Out - The output power calculated by the PID
This is the simplest PID controller. The output of this system is described by this function (do not be scared if you know little about mathematics)
The microcontroller will run an infinite loop, during which it will calculate the PID terms and control the output. I call this routine "MainLoop". During start-up, the microcontroller will run the "Initialization" routine. Here are the flow chart of the Initialization and the MainLoop routines: (Click to enlarge)
Initialization (Click to enlarge)
MainLoop (Click to enlarge)
Finally, there are 3 more subroutines, Calculate_P_Term, Calculate_I_Term and Calculate_D_Term. Here are the flow charts for those:
Calculate_P_Term (Click to enlarge)
Calculate_I_Term (Click to enlarge)
Calculate_D_Term (Click to enlarge)
How it works then?
First of all, i want you to agree that, behind the mask of the hard math, there is only a simple program with additions and subtractions (ok, and some multiplications and divisions maybe). I assume that you do know how to read a flowchart and i will not go into details. Regarding the symbols that i use: An ellipse indicates the start and the name of the (sub)routine that follows, a rectangle has the parts of a routine that have to be translated directly into code, a parallelogram defines a call to another sub-routine and a diamond defines an IF case, with a YES or NO branch.
Looking into the MainLoop, you can see that the system reads the temperature and calculates the error in every loop. The error is calculated by subtracting the PV from the SP (Err=SP-PV). If for example we want the water to have 80oC temperature (SP) and the current temperature is 25oC (PV), the error (Err) is 80-25=55oC. Using this error, the program calculates the P_Term (Calculate_P_Term subroutine), simply by multiplying the error by the P_Gain (P_Term=Err*P_Gain). Until now, this is the typical operation of a simple P-controller.
Now to add the I term. Looking again into the MainLoop, we see that the Calculate_I_Term subroutine is NOT called in every loop. Instead, a counter is used (Ti_Counter). The initial value of this counter is taken from the I_Time system parameter (Ti). The counter is decreased on every loop. When the counter becomes zero, the Calculate_I_Term subroutine is called. So, this is where the Ti parameter is used. Unlike the P_Term, the I_Term is calculated only once every Ti loop cycles. To calculate the I_Term the system adds the current error (Err) to a variable named Acc_Err. This variable accumulates all past errors and therefore is called Accumulated Error. The I_Term is then calculated by multiplying this variable by the I_Gain (I_Term=Acc_Err*I_Gain). Now you understand the purpose of the Ti parameter. If the system did not have this parameter, it would accumulate the current errors constantly, and therefore the I_Term would become huge and it would overshadow the P_Term and D_Term.
Finally, the D_Term. Similar to the I_Term, the D_Term has a different countdown timer (Td_Counter), so the Calculate_D_Term subroutine is called once every D_Time (Td) cycles. To calculate the D_Term, the system must know the previous error. The previous error is saved in the D_Err variable. So, the system can calculate the error difference by subtracting the previous error from the current error (Err - D_Err). Finally, the D_Term is calculated by multiplying the error difference by the D_Gain (D_Term = (Err - D_Err) * D_Gain).
The output of the system is calculated in every loop, and it is the sum of the 3 terms:
P_Out = P_Term + I_Term + D_Term
There are some problems that may come out from the following example. First of all, you have to decide if you will use 8-bit registers of longer for the variables. This is something that you need to decide before you start designing the system. You have to take into account the system's range and accuracy. For example, a temperature controller that is used for a system which measures temperatures from 0 to 100 degrees with 1oC accuracy can be implemented with 8-bit registers, But if the system measures from 0 to 600oC then the 8-bits are just not enough.
Moreover, extreme caution must be taken with the P_Out and Acc_Err registers. Both of them are subject to overflow when big numbers are added. If for example an 8-bit registered is selected for the Acc_Err variable, but an error of 100 degrees is added 3 times, then the register will overflow and the result will be wrong. Same can happen to the P_out register if the result of the P and D term addition is larger than the register can handle. Therefore, make sure that you put some limits to these registers and check them every time to see if they exceed these limits.
I'm also trying build PID controller to control temperature of heater. Heater heat the metal plate.
Till this time:
Heater is about 500W, and i trying control temperature by the triac. Heater is less frequently ON, if temperature is closer to the temperature that we want to achieve (set to 100deg.C). In this algorithm heater is on in duty from 10% do 100%. The best result is when:
- duty=100% for T<70deg.C
- duty=80% for T=71 to 80deg.C
- duty=60% for T=81 to 90deg.C
- duty=50% for T=91 to 100deg.C
- when T>=100deg.C then heater is OFF
In algorithm above temperature oscillates in about /- 3deg.C
And that way I would like to implement PID controller.
So for example if sum from calculating PID will be lets say from -50 to 120 then for my duty from 0 to 100% i must convert PID to range 0-100 so:
PID=-50 to 120
DUTY=0 to 170
Hi...I am working with a development of PID controller for a water heater. The problem that I am facing right now in determining the value of Kp,Ki and Kd. If I want to use Ziegler-Nicholas method for tuning PID then I have to determine the critical gain Kc and oscillation period Pc. After I can calculate PID parameters accordingly. But the problem is there is no oscillation in my system. So what should I do now?
@sierra It largely depends on your application. If you have an 8-bit then you have to limit it from 0 to 255, or if you need negative numbers you limit to 127. So you have to do the proper divisions when you read the input to fit these ranges. Which means that you have to know before hands the minimum and maximum values that the sensor will send you
@Pushkin2023 fot Ti and Td i'd say the faster the better, depending on the response of the system (you want very quick response). As for the D term, do not reset it to zero, it will reset itself in a couple of Td times...
Very nice tutorial. I'm trying to make a pid controller for my rotary inverted pendulum. I'm doing the pid computing every 20ms (timer2 interrupt, sets a flag in main loop), how should be Ti and Td?(how many times greater then pid loop) Another thing: the d_term and i_term shouldn't be reseted to 0 when the error is 0 or very very close to 0 ?
@NOAK an 8-bit number can measure from 0 to 255 with integer numbers. So, it fully covers the range 0 to 100. But it cannot measure from 0 to 600 with 1 degree accuracy... Maybe with 3 degrees accuracy (because 3*256=768)
"For example, a temperature controller that is used for a system which measures temperatures from 0 to 100 degrees with 1oC accuracy can be implemented with 8-bit registers, But if the system measures from 0 to 600oC then the 8-bits are just not enough."
could u explain why 8-bit MCU is not enough for 0-600 C application?
for example , i want to control a servo motor to move it to a commanded position and the output of the control system is PWM . so i should select proper P,I & D values so that the summation , i.e PID , is the correct PWM output which makes the motor move to the commanded position regardless to what degree the PWM should be propotional to the PID vlaue .
@akshay That is the idea. You make all these calculations to come up with an output power that is required. This output can b anything, like an analog voltage, an angle of a servomotor, a throttle position of an engine.... anything.
... it is 8 pages long! Filled with in-depth description and pictures the developmental changes to his code. And it is linked to the Arduino Playground code library, along with an Autotune library, at:
in the Input / Output subcategory.
Finally, the author links to the site of his own mentor, at:
I have not yet looked at this myself, but I hope to find it educational.
@Artur the idea of the It and Dt time parameters, is to avoid an overflow. The I parameter for example is used to have usually small changes, so that the operator can balance the immediate changes of the P parameter. If the I builds up on every cycle, then it will become huge very fast, and the system will not become stable. The idea is that the I build up slowly, and the system becomes stable slowly. The delay depends on the system's response. If a systems responses fast (for example hot air blower) then the It is small, otherwise (eg large water tank heater) It may be very large. Same stands for D.
The parameter P follows the changes in output, it reads the error and changes the power accordingly in every cycle. The parameter I builds up slowly. Here is how we use to explain these parameters.
P is the present, it looks at the output and changes with the present error.
I is the past, it looks at the past errors and changes accordingly.
D is the future, every Dt it looks at the error and according to the rate of change it tries to predict the future
I am not sure what the result of the ring buffer would be. It sounds as a good idea, since the errors some 100 cycles before (for example) are not important for the I term. If i make another PID controller, i will experiment with this idea.
I agree with others: best explanation I've seen so far. I'm wondering, would it be a good idea to store error in a ring buffer, e.g. 16 last values and calculate I and D from that on every iteration? If not then why. Other than the obvious added CPU cost.This way I would follow changes in output, not change incrementally every Ti cycles. Also why not calculate D in every loop based on previous loop's error?
@Bill Kostelidis regarding the Integral, if you calculate it in every cycle, the system will never be stable, unless the system is highly reactive. That is exactly the point of the integral term existence after all: to check the progress between time intervals and decide if needs more or less power.
To use the result of the PID, you need first to scale it to an easy-usable number. I use for example -127 to +127 which is one byte. The, you can either translate the number to 0-256 or just use it as is. Finally, you need to have a variable output. If the output is for example PWM, then i would translate the PID result to 0-256. Then, i would use this number as a PID duty cycle (0-0% 256=100%).
I really like your projects. Although I have my disagreements with the way that the Integral is not calculated in every circle, since it is working, you must be correct!
I am working in a PID project myself and I have a question, if you can help, it's great.
I am heating water with immersion heaters, resistors. They are plugged directly in 220 Volts but I will use PWM to control them. The control is going to come from my PID.
After I get the output from my PID controller, how am I going to translate it to PWM? I mean, if the output of my PID indicates 2 Celsius degrees down, how am I going to say to my PWM to adjust to 2 degrees down?
I am going to work experimentally at first, but I am searching to find a more solid solution, any ideas?
I am a Vietnamese. I have read many textbooks about PID control in some subjects in university, but I've never really understood the meaning of this method, I think the theory is explained too complicatedly than what it really means.
This post is a great explanation! I understood it easy.
Thank you very much!
This IS the best explanation of PID I have ever seen.
The historical background adds so much to the explanation,
that it makes so much more sense.
I'd like to know how you created the graphs!
I think a graphical demonstration program of PID would be awesome.
It could allow users to set parameters and watch and compare settings.
It could then show how to tune PID, using either method or both.
I have retuned repaired PID controllers for CNC equipment, and until
now have never understood why the directions for tuning were what they
were. "... then reduce these two levels, and then turn this third one up until the machine oscillates." It all makes sense now.
thank you, I look forward to coding something like the demo program
because I cannot find one.