Improving the Beginner’s PID: Direction

(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:

  1. 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.
  2. 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.

Creative Commons License

flattr this!

Tags: , ,

92 Responses to “Improving the Beginner’s PID: Direction”

  1. Tom L says:

    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!

  2. Brett says:

    Glad it was useful to you. I’ll start making a list of topics to address.

  3. Mike says:

    really in-depth set of program notes
    thanks for taking the time
    and explaining so clearly!

  4. Thanks for such a nice writeup..i really enjoyed every word..you are a good teacher..thanks for sharing…

  5. rom says:

    muy buen trabajo y muy util! gracias por compartirlo con todos

  6. Brett says:

    ¡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.

  7. Ricardo says:

    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

  8. Brett says:

    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.

  9. Ricardo says:

    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

  10. Brett says:

    Nada tan complicado. Solo quise decir que no puedes usar 01001011011… Tendrás que usar la temperatura propia (130, 131, …)

  11. Murl says:

    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&gt;=SampleTime)

  12. Brett says:

    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.

  13. Mark Barnes says:

    Brilliant set of tutorials, I learnt so much. Thanks.

  14. Pablo says:

    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

  15. Brett says:

    Pablo, no me ocurre nada obvio. capas hay algo en el programa que se enciende a la misma vez?

  16. matthew says:

    Thanks so much for this! Just what I needed!

  17. raul says:

    genius!! I read the whole write up and had a great time, I am now tuning the constants.. 🙂 Thanks!!!

  18. kps says:

    Excellent series of articles.
    Most lucid explanation.

    Long Live Brett.

  19. paul says:

    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!

  20. Jorge says:

    Excellent Job!!

    Thank you for the clear concise explanations.

    Best Regards,
    Jorge

  21. noonv says:

    Great work! Thanks!

  22. Sean says:

    Just wanted to chime in with another big Thank You!

    Really appreciate the time you put into sharing this…

  23. Oleksiy says:

    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!

  24. Kees Reuzelaar says:

    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.

  25. Prickle says:

    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?

  26. Brett says:

    @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)

  27. Lance says:

    I just wanted to drop you a note and say this was a great tutorial!

  28. Bert says:

    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 !

  29. Very good Xplanation about pid i ever seen .
    Thank you.

  30. Jacob says:

    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!

  31. James Brown says:

    The most eloquent and understandable explanations of PID I’ve found. Please continue to expand the articles – they are a great resource – thanks!

  32. gyro_john says:

    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.

  33. Brett says:

    @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 🙂

  34. Eric Morrison says:

    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.

  35. Brett says:

    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.

  36. Steve Houtchen says:

    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

  37. Michel says:

    @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!

  38. Brett says:

    @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.

  39. Michel says:

    Thanks Brett, very much appreciated!

  40. Cameron says:

    This is awesome, thanks so much! Really well written article, excellent explanations.

  41. Elwood Downey says:

    Thank you very much: clear, excellent discussion. My vote for next topic is Kvff and Kaff. Thanks again.

  42. Jeramy says:

    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.

  43. Elwood Downey says:

    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.

  44. Andrew G says:

    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?

  45. Andrew G says:

    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!

  46. Cortade says:

    Very good series! I would like to ask you: could you recommend us any books or sources you have used lately?
    Thanks a lot!

  47. Brett says:

    @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.

  48. Robin says:

    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!

  49. Cortade says:

    Thanks a lot. I didn’t know this website. Strongly recommendable.

  50. 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.

Leave a Reply