{"id":1187,"date":"2011-04-15T15:07:12","date_gmt":"2011-04-15T19:07:12","guid":{"rendered":"http:\/\/brettbeauregard.com\/blog\/?p=1187"},"modified":"2025-03-22T06:53:55","modified_gmt":"2025-03-22T10:53:55","slug":"improving-the-beginners-pid-direction","status":"publish","type":"post","link":"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginners-pid-direction\/","title":{"rendered":"Improving the Beginner&#8217;s PID: Direction"},"content":{"rendered":"<p><small>(This is the last modification in a <a href=\"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginners-pid-introduction\">larger series<\/a> on writing a solid PID algorithm)<\/small><\/p>\n<h3>The Problem<\/h3>\n<p>The processes the PID will be connected to fall into two groups: direct acting and reverse acting.  All the examples I&#8217;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.<\/p>\n<p>This isn&#8217;t a problem per se, but the user must choose the correct sign, and make sure that all the parameters have the same sign.<\/p>\n<h3>The Solution<\/h3>\n<p>To make the process a little simpler, I require that kp, ki, and kd all be &gt;=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.<\/p>\n<h3>The Code<\/h3>\n<pre class=\"brush: css; gutter: true; highlight: [13,14,15,44,51,52,53,54,55,56,102,103,104,105]; title: ; notranslate\" title=\"\">\n\/*working variables*\/\nunsigned long lastTime;\ndouble Input, Output, Setpoint;\ndouble ITerm, lastInput;\ndouble kp, ki, kd;\nint SampleTime = 1000; \/\/1 sec\ndouble outMin, outMax;\nbool inAuto = false;\n\n#define MANUAL 0\n#define AUTOMATIC 1\n\n#define DIRECT 0\n#define REVERSE 1\nint controllerDirection = DIRECT;\n\nvoid Compute()\n{\n   if(!inAuto) return;\n   unsigned long now = millis();\n   int timeChange = (now - lastTime);\n   if(timeChange&gt;=SampleTime)\n   {\n      \/*Compute all the working error variables*\/\n      double error = Setpoint - Input;\n      ITerm+= (ki * error);\n      if(ITerm &gt; outMax) ITerm= outMax;\n      else if(ITerm &lt; outMin) ITerm= outMin;\n      double dInput = (Input - lastInput);\n\n      \/*Compute PID Output*\/\n      Output = kp * error + ITerm- kd * dInput;\n      if(Output &gt; outMax) Output = outMax;\n      else if(Output &lt; outMin) Output = outMin;\n\n      \/*Remember some variables for next time*\/\n      lastInput = Input;\n      lastTime = now;\n   }\n}\n\nvoid SetTunings(double Kp, double Ki, double Kd)\n{\n   if (Kp&lt;0 || Ki&lt;0|| Kd&lt;0) return;\n\n  double SampleTimeInSec = ((double)SampleTime)\/1000;\n   kp = Kp;\n   ki = Ki * SampleTimeInSec;\n   kd = Kd \/ SampleTimeInSec;\n\n  if(controllerDirection ==REVERSE)\n   {\n      kp = (0 - kp);\n      ki = (0 - ki);\n      kd = (0 - kd);\n   }\n}\n\nvoid SetSampleTime(int NewSampleTime)\n{\n   if (NewSampleTime &gt; 0)\n   {\n      double ratio  = (double)NewSampleTime\n                      \/ (double)SampleTime;\n      ki *= ratio;\n      kd \/= ratio;\n      SampleTime = (unsigned long)NewSampleTime;\n   }\n}\n\nvoid SetOutputLimits(double Min, double Max)\n{\n   if(Min &gt; Max) return;\n   outMin = Min;\n   outMax = Max;\n\n   if(Output &gt; outMax) Output = outMax;\n   else if(Output &lt; outMin) Output = outMin;\n\n   if(ITerm &gt; outMax) ITerm= outMax;\n   else if(ITerm &lt; outMin) ITerm= outMin;\n}\n\nvoid SetMode(int Mode)\n{\n\tbool newAuto = (Mode == AUTOMATIC);\n\tif(newAuto == !inAuto)\n\t{  \/*we just went from manual to auto*\/\n\t\tInitialize();\n\t}\n\tinAuto = newAuto;\n}\n\nvoid Initialize()\n{\n   lastInput = Input;\n   ITerm = Output;\n   if(ITerm &gt; outMax) ITerm= outMax;\n   else if(ITerm &lt; outMin) ITerm= outMin;\n}\n\nvoid SetControllerDirection(int Direction)\n{\n   controllerDirection = Direction;\n}\n<\/pre>\n<h2>PID COMPLETE<\/h2>\n<p>And that about wraps it up.  We&#8217;ve turned &#8220;The Beginner&#8217;s PID&#8221; 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.<\/p>\n<p>Two Final Notes:<\/p>\n<ol>\n<li>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&#8217;d like to know.<\/li>\n<li>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&#8217;s interest in having me explore these topics please let me know.<\/li>\n<\/ol>\n<p><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\"><img decoding=\"async\" style=\"border-width: 0\" src=\"http:\/\/i.creativecommons.org\/l\/by-sa\/3.0\/80x15.png\" alt=\"Creative Commons License\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>(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&#8217;ve shown so &hellip; <a href=\"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginners-pid-direction\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[31,7],"tags":[6,30,41],"class_list":["post-1187","post","type-post","status-publish","format-standard","hentry","category-coding","category-pid","tag-arduino","tag-beginners-pid","tag-pid"],"_links":{"self":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/1187","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/comments?post=1187"}],"version-history":[{"count":42,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/1187\/revisions"}],"predecessor-version":[{"id":7253,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/1187\/revisions\/7253"}],"wp:attachment":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/media?parent=1187"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/categories?post=1187"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/tags?post=1187"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}