(This is the last modification in a larger series on writing a solid PID algorithm)
The Problem
The processes the PID will be connected to fall into two groups: direct acting and reverse acting. All the examples I’ve shown so far have been direct acting. That is, an increase in the output causes an increase in the input. For reverse acting processes the opposite is true. In a refrigerator for example, an increase in cooling causes the temperature to go down. To make the beginner PID work with a reverse process, the signs of kp, ki, and kd all must be negative.
This isn’t a problem per se, but the user must choose the correct sign, and make sure that all the parameters have the same sign.
The Solution
To make the process a little simpler, I require that kp, ki, and kd all be >=0. If the user is connected to a reverse process, they specify that separately using the SetControllerDirection function. this ensures that the parameters all have the same sign, and hopefully makes things more intuitive.
The Code
/*working variables*/ unsigned long lastTime; double Input, Output, Setpoint; double ITerm, lastInput; double kp, ki, kd; int SampleTime = 1000; //1 sec double outMin, outMax; bool inAuto = false; #define MANUAL 0 #define AUTOMATIC 1 #define DIRECT 0 #define REVERSE 1 int controllerDirection = DIRECT; void Compute() { if(!inAuto) return; unsigned long now = millis(); int timeChange = (now - lastTime); if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint - Input; ITerm+= (ki * error); if(ITerm > outMax) ITerm= outMax; else if(ITerm < outMin) ITerm= outMin; double dInput = (Input - lastInput); /*Compute PID Output*/ Output = kp * error + ITerm- kd * dInput; if(Output > outMax) Output = outMax; else if(Output < outMin) Output = outMin; /*Remember some variables for next time*/ lastInput = Input; lastTime = now; } } void SetTunings(double Kp, double Ki, double Kd) { if (Kp<0 || Ki<0|| Kd<0) return; double SampleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE) { kp = (0 - kp); ki = (0 - ki); kd = (0 - kd); } } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime; ki *= ratio; kd /= ratio; SampleTime = (unsigned long)NewSampleTime; } } void SetOutputLimits(double Min, double Max) { if(Min > Max) return; outMin = Min; outMax = Max; if(Output > outMax) Output = outMax; else if(Output < outMin) Output = outMin; if(ITerm > outMax) ITerm= outMax; else if(ITerm < outMin) ITerm= outMin; } void SetMode(int Mode) { bool newAuto = (Mode == AUTOMATIC); if(newAuto == !inAuto) { /*we just went from manual to auto*/ Initialize(); } inAuto = newAuto; } void Initialize() { lastInput = Input; ITerm = Output; if(ITerm > outMax) ITerm= outMax; else if(ITerm < outMin) ITerm= outMin; } void SetControllerDirection(int Direction) { controllerDirection = Direction; }
PID COMPLETE
And that about wraps it up. We’ve turned “The Beginner’s PID” into the most robust controller I know how to make at this time. For those readers that were looking for a detailed explanation of the PID Library, I hope you got what you came for. For those of you writing your own PID, I hope you were able to glean a few ideas that save you some cycles down the road.
Two Final Notes:
- If something in this series looks wrong please let me know. I may have missed something, or might just need to be clearer in my explanation. Either way I’d like to know.
- This is just a basic PID. There are many other issues that I intentionally left out in the name of simplicity. Off the top of my head: feed forward, reset tiebacks, integer math, different pid forms, using velocity instead of position. If there’s interest in having me explore these topics please let me know.
These are by far the clearest presentation of entry-level PID issues I’ve encountered: very nice job, and very helpful for newbies trying to get our own code up and making sense.
Yes, more articles would be useful! All the topics you mentioned plus cascaded controllers, and skipping intermediate loops….
Thanks very much for your work!
Glad it was useful to you. I’ll start making a list of topics to address.
really in-depth set of program notes
thanks for taking the time
and explaining so clearly!
Thanks for such a nice writeup..i really enjoyed every word..you are a good teacher..thanks for sharing…
muy buen trabajo y muy util! gracias por compartirlo con todos
¡De nada! Que felicidad tengo sabiendo que esto he sido útil para el mundo español. Puedo hablar español, pero no lo puedo escribir a el nivel requerido por esta serie.
Primero, enhorabuena y muchas gracias por la biblioteca y el blog!! y segundo, me surgen un par de dudas al probar la libreria , en el ejemplo PId_basic por ejemplo , los valores que se utilizan para kp,ki,kd son 2,5,1, no?
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);
entonces cual es el maximo y que se podria utilizar en cada uno? lo poco que vi de pid siempre utilizan hasta 1.( 0,1,0,3…)
con la biblioteca seria igual pero hasta 10? o 100?
y otra pregunta, como input se podria utilizar una variable obtenida desde un sensor digital,habria problemas?
como puedes ver no se mucho de esto, solo intento aprender
muchas gracias !! y un saludo
Gracias Ricardo. Los valores de Kp, Ki, Kd no son porcentajes. Se puede usar cualquier valor de tipo “double”. Normalmente encuentro que valores entre 0.01 y 10 me dan los mejores resultados, pero a veces eh usado 15 o 20. recomiendo empezando con valores pequeños.
para usar un sensor digital tendrás que primero convertir el señal a un valor analógico. Aparate de eso, lo demás sera igual.
ok entendido lo de los valores, la duda de el sensor digital , es por que pretendia experimentar con el pid para un control de temperatura, la temperatura la obtendría en principio con un ds1820, entonces, me recomiendas un convertidor digital/analogico intentarlo con alguno analogico tipo lm35?
muchas gracias por la resupuesta, un saludo
Nada tan complicado. Solo quise decir que no puedes usar 01001011011… Tendrás que usar la temperatura propia (130, 131, …)
Thanks for all of the time and effort you’ve put into this library, not to mention documenting it so well. And – thanks to Control Station for allowing you to do it – it’s another indication of what a stand-up company you work for.
I don’t know if it’s just my browser, but the listing above seems to be corrupted in several places. For example, I think line 22 is supposed to be:
if(timeChange>=SampleTime)
but on this page, it shows up as:
if(timeChange>=SampleTime)
Thanks for the heads up Murl. the plug-in I’m using to display the code can be a little finicky. I’ve fixed the > thing.
As far as Control Station goes, yeah they’ve been great about this. I do my best, however, to limit references to my day job. I need to make sure I can swear if I want to without it reflecting poorly on my employer.
Brilliant set of tutorials, I learnt so much. Thanks.
Hola,
cambiando de Manual a Auto tarda como 10 segundos en empezar a cambiar la salida. Despues funciona perfecto.
Alguna idea?
Gracias por la ayuda,
Pablo
Pablo, no me ocurre nada obvio. capas hay algo en el programa que se enciende a la misma vez?
Thanks so much for this! Just what I needed!
genius!! I read the whole write up and had a great time, I am now tuning the constants.. 🙂 Thanks!!!
Excellent series of articles.
Most lucid explanation.
Long Live Brett.
Excellent series. Great explanation, so easy to follow with the code a graph examples. Very good idea with the code highlighting for the relevant section!
Excellent Job!!
Thank you for the clear concise explanations.
Best Regards,
Jorge
Great work! Thanks!
Just wanted to chime in with another big Thank You!
Really appreciate the time you put into sharing this…
Thank you for the write up. I’m a big fan of control theory, and this is very useful! One other thing that comes up often is choosing how often to call the function, i.e. sampling time vs. the speed of change of the underlying system.
Delightful reading!
Excellent write-up! I love the way you take a ‘too simple’ version step by step to an actual useful version. Very easy to follow.
Oh, and YES, I’d love to see you implement (and document!) more advanced concepts such as feed-forward and whatever else you can think of.
Brilliant introduction to the subject. I am also planning to implement a firmware PID and you have helped me past many pitfalls.
I think describing techniques for tuning the PID controller would complement this series. How does one find good parameters?
@Prickle I would suggest taking a look at the diy-pid-control google group. I personally can’t help, as I have an employment agreement to contend with. (I work for a company that, among other things, does pid tuning)
I just wanted to drop you a note and say this was a great tutorial!
Very, very good story !
Thanks for taking the trouble to put this in the net.
Do you have some ideas or code for self-tuning of the PID ?
I was investigating this for some time now but did not find any good source for it. Please drop me a line if you do !
Very good Xplanation about pid i ever seen .
Thank you.
I’m interested in similar short articles about feed forward and using velocity instead of position. I’ve heard of these concepts in other contexts, but your explanations & graphs are very clear. Thanks!
The most eloquent and understandable explanations of PID I’ve found. Please continue to expand the articles – they are a great resource – thanks!
Re: autotuning. I use PID temperature controllers. Their input is a thermocouple and the output is On/Off control of current to an electric heater. The control algorithm adjusts the on/off time of the output in order to hit and hold temperature.
In my experience, all modern temperature controllers have an Auto Tune function, and it works very well. I haven’t had to manually tune the parameters since ever.
The auto tune algorithm works like this:
– starting from cold, or from below temperature at least,
– the output is turned on full, and stays on until the temperature reaches the set point.
– at that point the output is turned off, and there will be a big temperature overshoot.
– the output will remain off until the temperature drops to the set point.
– then the output will be turned on full again. It will catch the falling temperature (there will be an undershoot) and stay on until the temperature climbs to the set point again.
– at that point the output will turn off again and there will be another temperature overshoot.
– This time, when the temperature drops through the set point, the Auto Tune routine terminates (after having completed two overshoot / undershoot cycles), it puts appropriate values in the P, I and D variables, and PID mode takes over.
Apparently this performance allows the algorithm to put reasonable numbers to the system’s performance, and calculate appropriate values for the parameters.
The auto-tune function only needs to be run once on any given setup. The parameters calculated will continue to work fine until that controller is repurposed for some other system.
Question: Is such an auto-tune procedure easy to implement here, thus getting around the need for trial-and-error parameter adjustment?
Thanks very much for the very informative tutorial.
@John you’re describing the “relay” autotune method. if you had asked me this 2 weeks ago I would have given my much repeated line that I have an employment agreement that precludes me from writing this code. I have recently, however, come across some open-source code, and have received permission from my company to port it to arduino. so.. stay tuned 🙂
You can remove the ‘if’ block (lines 51-56) by using DIRECT = 1 and REVERSE = -1. Now for every pass through “SetTunings” you use:
kp = Kp * controllerDirection
ki = (Ki * SampleTimeInSec) * controllerDirection
kd = (Kd * SampleTimeInSec) * controllerDirection
(lines 47-49)
Not a big code savings but every cycle counts sometimes.
I could be wrong, but it’s my understanding that multiplication is more computationally intensive than subtraction (or an if statement). as written the program is slightly bigger, but I think faster than using 3 multiplications, provided my understanding is correct.
Brett,
I have an PID application using an 8 bit embedded
controller that can use any speed improvement in
the algorithm you can think of..
In particular I am interested in your thoughts about using
gains that are powers of 2 for fast multiplication
, and using integer math…
SteveH
@Brett: I’d be VERY interested in the “relay” autotuning port you’re working on. Can you give us an update of your progres?
And, if possible, do you have a link to that open-source project that you found? I wouldn’t mind having a look at that code too, even if it’s not for the Arduino platform (or in a totally different programming language).
Thanks!
@Michel: I have a rough version of the code working, but it’s not polished. I used this code as the starting point, although I modified the peak identification method to better handle the noise of a real process.
Thanks Brett, very much appreciated!
This is awesome, thanks so much! Really well written article, excellent explanations.
Thank you very much: clear, excellent discussion. My vote for next topic is Kvff and Kaff. Thanks again.
Thanks Brett, this is really nice work. With regard to advanced topics, I would be interested in both feed forward as well as integer math implementations of PID.
I think I have Kvff and Kaff working. Basically I just add them as two more terms to output as in:
output = kp * error + ITerm- kd * dInput + Kvff*Vcmd and Kaff*Acmd;
where Vcmd and Acmd are two new user parameters for the commanded velocity and acceleration.
But now my question is: suppose I want to enforce Vmax and Amax? How does one constrain the PID output to never generate output that moves beyond these limits? Thanks.
This was the best explanation of PID control I’ve ever read. I wrote a PID for a mechatronics project last year, and ran into most of the same problems, and solved them in similar ways (but generally not as elegantly). Definitely would have saved loads of time if I’d had this as a reference then, will be saving this and recommending it to anyone who might need it.
Looking forward to news on the auto-tuning code port. I actually used that matlab toolkit briefly working with my project last year, but ended up hand-tuning, because my code had some confusing integer scaling.
have you considered a version that uses pure integer math to decrease processor load and save space?
I forgot to say what I intended my previous post to be–thank you very much for the time and effort you put into the code and especially this fantastic explanation!
Very good series! I would like to ask you: could you recommend us any books or sources you have used lately?
Thanks a lot!
@Andrew I’m glad this was useful to you. I have considered an integer version, and it’s on my list. the main stumbling block is that I still haven’t decided how I’m going to specify fraction tuning parameters. there are several ways it can be done, and I need to settle on the way think is the most intuitive to the hobbyist.
@Cortade this is largely an original work, based on several years of writing pid algorithms. I don’t know of any books that cover this subject (although there has to be a least SOME, I just have never seen one.) A lot of my understanding over the years has come from reading countless manufacturer specifications. here’s one from rockwell automation: http://samplecode.rockwellautomation.com/idc/groups/literature/documents/wp/logix-wp008_-en-p.pdf
for general PID understanding I would recommend controlguru.com, created by the guy who taught me about pid.
That was a brilliant series! I found your series from the Arduino PIDLibrary page and I’m glad I stumbled across it! I really liked the graphs of the behavior before and after each modification to illustrate the impact. Thanks!
Thanks a lot. I didn’t know this website. Strongly recommendable.
totally kick ass. I can only imagine how much effort this must have taken to simply derive all that let alone write it up for us folks to learn too. My sincerest thank yous. I eagerly await your implementation of an auto-tune loop.