{"id":2342,"date":"2017-06-20T10:32:30","date_gmt":"2017-06-20T14:32:30","guid":{"rendered":"http:\/\/brettbeauregard.com\/blog\/?p=2342"},"modified":"2024-06-03T20:58:26","modified_gmt":"2024-06-04T00:58:26","slug":"proportional-on-measurement-the-code","status":"publish","type":"post","link":"http:\/\/brettbeauregard.com\/blog\/2017\/06\/proportional-on-measurement-the-code\/","title":{"rendered":"Proportional on Measurement &#8211; The Code"},"content":{"rendered":"<p>In the <a href=\"http:\/\/brettbeauregard.com\/blog\/2017\/06\/introducing-proportional-on-measurement\/\">previous post<\/a> I spent all my time explaining the benefits of Proportional on Measurement. In this post I&#8217;ll explain the code. People seemed to appreciate the step-by-step way I explained things <a href=\"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginners-pid-introduction\/\">last time,<\/a> so that&#8217;s what I&#8217;ll do here. The 3 passes below detail how I went about adding PonM to the PID library.<\/p>\n<h3>First Pass &#8211; Initial Input and Proportional-Mode selection<\/h3>\n<div style=\"height: 400px; overflow-y: scroll;\">\n<pre class=\"brush: css; gutter: true; highlight: [17,18,19,20,37,38,41,51,55,108]; title: ; notranslate\" title=\"\">\r\n\/*working variables*\/\r\nunsigned long lastTime;\r\ndouble Input, Output, Setpoint;\r\ndouble ITerm, lastInput;\r\ndouble kp, ki, kd;\r\nint SampleTime = 1000; \/\/1 sec\r\ndouble outMin, outMax;\r\nbool inAuto = false;\r\n \r\n#define MANUAL 0\r\n#define AUTOMATIC 1\r\n \r\n#define DIRECT 0\r\n#define REVERSE 1\r\nint controllerDirection = DIRECT;\r\n \r\n#define P_ON_M 0\r\n#define P_ON_E 1\r\nbool pOnE = true;\r\ndouble initInput;\r\n\r\nvoid Compute()\r\n{\r\n   if(!inAuto) return;\r\n   unsigned long now = millis();\r\n   int timeChange = (now - lastTime);\r\n   if(timeChange&gt;=SampleTime)\r\n   {\r\n      \/*Compute all the working error variables*\/\r\n      double error = Setpoint - Input;\r\n      ITerm+= (ki * error);\r\n      if(ITerm &gt; outMax) ITerm= outMax;\r\n      else if(ITerm &lt; outMin) ITerm= outMin;\r\n      double dInput = (Input - lastInput);\r\n \r\n      \/*Compute P-Term*\/ \r\n      if(pOnE) Output = kp * error;\r\n      else Output = -kp * (Input-initInput); \r\n\r\n      \/*Compute Rest of PID Output*\/ \r\n      Output += ITerm - kd * dInput;\r\n      if(Output &gt; outMax) Output = outMax;\r\n      else if(Output &lt; outMin) Output = outMin;\r\n \r\n      \/*Remember some variables for next time*\/\r\n      lastInput = Input;\r\n      lastTime = now;\r\n   }\r\n}\r\n \r\nvoid SetTunings(double Kp, double Ki, double Kd, int pOn)\r\n{\r\n   if (Kp&lt;0 || Ki&lt;0|| Kd&lt;0) return;\r\n \r\n   pOnE = pOn == P_ON_E;\r\n  \r\n   double SampleTimeInSec = ((double)SampleTime)\/1000;\r\n   kp = Kp;\r\n   ki = Ki * SampleTimeInSec;\r\n   kd = Kd \/ SampleTimeInSec;\r\n \r\n  if(controllerDirection ==REVERSE)\r\n   {\r\n      kp = (0 - kp);\r\n      ki = (0 - ki);\r\n      kd = (0 - kd);\r\n   }\r\n}\r\n \r\nvoid SetSampleTime(int NewSampleTime)\r\n{\r\n   if (NewSampleTime &gt; 0)\r\n   {\r\n      double ratio  = (double)NewSampleTime\r\n                      \/ (double)SampleTime;\r\n      ki *= ratio;\r\n      kd \/= ratio;\r\n      SampleTime = (unsigned long)NewSampleTime;\r\n   }\r\n}\r\n \r\nvoid SetOutputLimits(double Min, double Max)\r\n{\r\n   if(Min &gt; Max) return;\r\n   outMin = Min;\r\n   outMax = Max;\r\n \r\n   if(Output &gt; outMax) Output = outMax;\r\n   else if(Output &lt; outMin) Output = outMin;\r\n \r\n   if(ITerm &gt; outMax) ITerm= outMax;\r\n   else if(ITerm &lt; outMin) ITerm= outMin;\r\n}\r\n \r\nvoid SetMode(int Mode)\r\n{\r\n    bool newAuto = (Mode == AUTOMATIC);\r\n    if(newAuto == !inAuto)\r\n    {  \/*we just went from manual to auto*\/\r\n        Initialize();\r\n    }\r\n    inAuto = newAuto;\r\n}\r\n \r\nvoid Initialize()\r\n{\r\n   lastInput = Input;\r\n   initInput = Input;\r\n   ITerm = Output;\r\n   if(ITerm &gt; outMax) ITerm= outMax;\r\n   else if(ITerm &lt; outMin) ITerm= outMin;\r\n}\r\n \r\nvoid SetControllerDirection(int Direction)\r\n{\r\n   controllerDirection = Direction;\r\n}\r\n<\/pre>\n<\/div>\n<p>Proportional on Measurement provides increasing resistance as the input changes, but without a frame of reference our performance would be a little wonky. If the PID Input is 10000 when we first turn on the controller, do we really want to start resisting with Kp*10000? No. We want to use our initial input as a reference point (line 108,) the increase or decrease resistance as the input changes from there (line 38.)<\/p>\n<p>The other thing we need to do is allow the user to select whether they want to do Proportional on Error or Measurement. After the <a href=\"http:\/\/brettbeauregard.com\/blog\/2017\/06\/introducing-proportional-on-measurement\/\">last post<\/a> it might seem like PonE is useless, but it&#8217;s important to remember that for many loops it works well. As such, we need to let the user choose which mode they want (lines 51&amp;55) and then act accordingly in the calculation (lines 37&amp;38).<\/p>\n<h3>Second Pass &#8211; On-the-fly tuning changes<\/h3>\n<p>While the code above does indeed work, it has a problem that we&#8217;ve seen before. When tuning parameters are changed at runtime, we get an undesirable blip.<\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2358\" src=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip.png\" alt=\"PonM-blip\" width=\"417\" height=\"397\" srcset=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip.png 417w, http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip-300x286.png 300w\" sizes=\"auto, (max-width: 417px) 100vw, 417px\" \/><\/a><\/p>\n<p>Why is this happening?<\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip-math-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2357\" src=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-blip-math-1.png\" alt=\"PonM-blip-math\" width=\"450\" height=\"100\" \/><\/a><\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginner%e2%80%99s-pid-tuning-changes\/\">The last time we saw this,<\/a> it was that the integral was being rescaled by a new Ki. This time, it&#8217;s that (Input &#8211; initInput) is being rescaled by Kp. the solution I chose is similar to what I did for Ki: instead of treating the Input &#8211; initInput as a monolithic unit multiplied by the current Kp, I broke it into individual steps multiplied by the Kp at that time:<\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-expansion.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2366\" src=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-expansion.png\" alt=\"PonM expansion\" width=\"450\" height=\"24\" srcset=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-expansion.png 635w, http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-expansion-300x16.png 300w\" sizes=\"auto, (max-width: 450px) 100vw, 450px\" \/><\/a><\/p>\n<div style=\"height: 400px; overflow-y: scroll;\">\n<pre class=\"brush: css; gutter: true; highlight: [20,39,40,41,42,43,114]; title: ; notranslate\" title=\"\">\r\n\/*working variables*\/\r\nunsigned long lastTime;\r\ndouble Input, Output, Setpoint;\r\ndouble ITerm, lastInput;\r\ndouble kp, ki, kd;\r\nint SampleTime = 1000; \/\/1 sec\r\ndouble outMin, outMax;\r\nbool inAuto = false;\r\n \r\n#define MANUAL 0\r\n#define AUTOMATIC 1\r\n \r\n#define DIRECT 0\r\n#define REVERSE 1\r\nint controllerDirection = DIRECT;\r\n \r\n#define P_ON_M 0\r\n#define P_ON_E 1\r\nbool pOnE = true;\r\ndouble PTerm;\r\n\r\nvoid Compute()\r\n{\r\n   if(!inAuto) return;\r\n   unsigned long now = millis();\r\n   int timeChange = (now - lastTime);\r\n   if(timeChange&gt;=SampleTime)\r\n   {\r\n   \r\n      \/*Compute all the working error variables*\/\t \t\r\n      double error = Setpoint - Input;\t \r\n      ITerm+= (ki * error);\t \r\n      if(ITerm &gt; outMax) ITerm= outMax;\t \t\r\n      else if(ITerm &lt; outMin) ITerm= outMin;  \r\n      double dInput = (Input - lastInput);\r\n\r\n   \t  \/*Compute P-Term*\/ \r\n   \t  if(pOnE) Output = kp * error; \r\n   \t  else \r\n   \t  { \r\n   \t     PTerm -= kp * dInput; \r\n   \t     Output = PTerm; \r\n   \t  } \r\n   \t  \r\n   \t  \/*Compute Rest of PID Output*\/ \r\n   \t  Output += ITerm - kd * dInput; \r\n   \r\n      if(Output &gt; outMax) Output = outMax;\r\n      else if(Output &lt; outMin) Output = outMin;\r\n \r\n      \/*Remember some variables for next time*\/\r\n      lastInput = Input;\r\n      lastTime = now;\r\n   }\r\n}\r\n \r\nvoid SetTunings(double Kp, double Ki, double Kd, int pOn)\r\n{\r\n   if (Kp&lt;0 || Ki&lt;0|| Kd&lt;0) return;\r\n \r\n   pOnE = pOn == P_ON_E;\r\n  \r\n   double SampleTimeInSec = ((double)SampleTime)\/1000;\r\n   kp = Kp;\r\n   ki = Ki * SampleTimeInSec;\r\n   kd = Kd \/ SampleTimeInSec;\r\n \r\n  if(controllerDirection ==REVERSE)\r\n   {\r\n      kp = (0 - kp);\r\n      ki = (0 - ki);\r\n      kd = (0 - kd);\r\n   }\r\n}\r\n \r\nvoid SetSampleTime(int NewSampleTime)\r\n{\r\n   if (NewSampleTime &gt; 0)\r\n   {\r\n      double ratio  = (double)NewSampleTime\r\n                      \/ (double)SampleTime;\r\n      ki *= ratio;\r\n      kd \/= ratio;\r\n      SampleTime = (unsigned long)NewSampleTime;\r\n   }\r\n}\r\n \r\nvoid SetOutputLimits(double Min, double Max)\r\n{\r\n   if(Min &gt; Max) return;\r\n   outMin = Min;\r\n   outMax = Max;\r\n \r\n   if(Output &gt; outMax) Output = outMax;\r\n   else if(Output &lt; outMin) Output = outMin;\r\n \r\n   if(ITerm &gt; outMax) ITerm= outMax;\r\n   else if(ITerm &lt; outMin) ITerm= outMin;\r\n}\r\n \r\nvoid SetMode(int Mode)\r\n{\r\n    bool newAuto = (Mode == AUTOMATIC);\r\n    if(newAuto == !inAuto)\r\n    {  \/*we just went from manual to auto*\/\r\n        Initialize();\r\n    }\r\n    inAuto = newAuto;\r\n}\r\n \r\nvoid Initialize()\r\n{\r\n   lastInput = Input;\r\n   PTerm = 0;\r\n   ITerm = Output;\r\n   if(ITerm &gt; outMax) ITerm= outMax;\r\n   else if(ITerm &lt; outMin) ITerm= outMin;\r\n}\r\n \r\nvoid SetControllerDirection(int Direction)\r\n{\r\n   controllerDirection = Direction;\r\n}\r\n<\/pre>\n<\/div>\n<p>Instead of multiplying the entirety of Input-initInput by Kp, we now keep a working sum, PTerm. At each step we multiply just the current input change by Kp and subtract it from PTerm (line 41.) Here we can see the impact of the change:<\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2362\" src=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip.png\" alt=\"PonM-no-blip\" width=\"417\" height=\"398\" srcset=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip.png 417w, http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip-300x286.png 300w\" sizes=\"auto, (max-width: 417px) 100vw, 417px\" \/><\/a><\/p>\n<p><a href=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip-math.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-2363\" src=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip-math.png\" alt=\"PonM-no-blip-math\" width=\"450\" height=\"124\" srcset=\"http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip-math.png 536w, http:\/\/brettbeauregard.com\/blog\/wp-content\/uploads\/2017\/06\/PonM-no-blip-math-300x83.png 300w\" sizes=\"auto, (max-width: 450px) 100vw, 450px\" \/><\/a><\/p>\n<p>Because the old Kps are &#8220;in the bank&#8221;, the change in tuning parameters only affects us moving forward<\/p>\n<h3>Final Pass &#8211; Sum problems.<\/h3>\n<p>I won&#8217;t go into complete detail (fancy trends etc) as to what wrong with the above code. It&#8217;s pretty good, but there are still major issues with it. For example:<\/p>\n<ol>\n<li><strong>Windup, sort of<\/strong>: While the final output is limited between outMin and outMax, it&#8217;s possible for PTerm to grow when it shouldn&#8217;t. It wouldn&#8217;t be as bad as <a href=\"http:\/\/brettbeauregard.com\/blog\/2011\/04\/improving-the-beginner%e2%80%99s-pid-reset-windup\/\">integral windup<\/a>, but it still wouldn&#8217;t be acceptable<\/li>\n<li><strong>On-the-fly changes<\/strong>: If the user were to change from P_ON_M to P_ON_E while running, then after some time return back, PTerm wouldn&#8217;t be property initialized and it would cause an output bump<\/li>\n<\/ol>\n<p>There are more, but just these are enough to see what the real issue is. We&#8217;ve dealt with all of these before, back when we created ITerm. Rather than go through and re-implement the same solutions for PTerm, I decided on a more aesthetically-pleasing solution.<\/p>\n<p>By merging PTerm and ITerm into a single variable called &#8220;outputSum&#8221;, the P_ON_M code then benefits from the all the ITerm fixes that are already in place, and because there aren&#8217;t two sums in the code there isn&#8217;t needless redundancy.<\/p>\n<div style=\"height: 400px; overflow-y: scroll;\">\n<pre class=\"brush: css; gutter: true; highlight: [4,20,32,36,42,43,46,114]; title: ; notranslate\" title=\"\">\r\n\/*working variables*\/\r\nunsigned long lastTime;\r\ndouble Input, Output, Setpoint;\r\ndouble outputSum, lastInput;\r\ndouble kp, ki, kd;\r\nint SampleTime = 1000; \/\/1 sec\r\ndouble outMin, outMax;\r\nbool inAuto = false;\r\n \r\n#define MANUAL 0\r\n#define AUTOMATIC 1\r\n \r\n#define DIRECT 0\r\n#define REVERSE 1\r\nint controllerDirection = DIRECT;\r\n \r\n#define P_ON_M 0\r\n#define P_ON_E 1\r\nbool pOnE = true;\r\n\r\n\r\nvoid Compute()\r\n{\r\n   if(!inAuto) return;\r\n   unsigned long now = millis();\r\n   int timeChange = (now - lastTime);\r\n   if(timeChange&gt;=SampleTime)\r\n   {\r\n   \r\n      \/*Compute all the working error variables*\/\t \t\r\n      double error = Setpoint - Input;\t \r\n      double dInput = (Input - lastInput);\r\n      outputSum+= (ki * error);\t \r\n\t  \r\n\t  \/*Add Proportional on Measurement, if P_ON_M is specified*\/\r\n\t  if(!pOnE) outputSum-= kp * dInput\r\n\t  \r\n      if(outputSum &gt; outMax) outputSum= outMax;\t \t\r\n      else if(outputSum &lt; outMin) outputSum= outMin;  \r\n   \t\r\n   \t  \/*Add Proportional on Error, if P_ON_E is specified*\/ \r\n\t  if(pOnE) Output = kp * error; \r\n\t  else Output = 0;\r\n   \t  \r\n   \t  \/*Compute Rest of PID Output*\/ \r\n   \t  Output += outputSum - kd * dInput; \r\n   \r\n      if(Output &gt; outMax) Output = outMax;\r\n      else if(Output &lt; outMin) Output = outMin;\r\n \r\n      \/*Remember some variables for next time*\/\r\n      lastInput = Input;\r\n      lastTime = now;\r\n   }\r\n}\r\n \r\nvoid SetTunings(double Kp, double Ki, double Kd, int pOn)\r\n{\r\n   if (Kp&lt;0 || Ki&lt;0|| Kd&lt;0) return;\r\n \r\n   pOnE = pOn == P_ON_E;\r\n  \r\n   double SampleTimeInSec = ((double)SampleTime)\/1000;\r\n   kp = Kp;\r\n   ki = Ki * SampleTimeInSec;\r\n   kd = Kd \/ SampleTimeInSec;\r\n \r\n  if(controllerDirection ==REVERSE)\r\n   {\r\n      kp = (0 - kp);\r\n      ki = (0 - ki);\r\n      kd = (0 - kd);\r\n   }\r\n}\r\n \r\nvoid SetSampleTime(int NewSampleTime)\r\n{\r\n   if (NewSampleTime &gt; 0)\r\n   {\r\n      double ratio  = (double)NewSampleTime\r\n                      \/ (double)SampleTime;\r\n      ki *= ratio;\r\n      kd \/= ratio;\r\n      SampleTime = (unsigned long)NewSampleTime;\r\n   }\r\n}\r\n \r\nvoid SetOutputLimits(double Min, double Max)\r\n{\r\n   if(Min &gt; Max) return;\r\n   outMin = Min;\r\n   outMax = Max;\r\n \r\n   if(Output &gt; outMax) Output = outMax;\r\n   else if(Output &lt; outMin) Output = outMin;\r\n \r\n   if(outputSum &gt; outMax) outputSum= outMax;\r\n   else if(outputSum &lt; outMin) outputSum= outMin;\r\n}\r\n \r\nvoid SetMode(int Mode)\r\n{\r\n    bool newAuto = (Mode == AUTOMATIC);\r\n    if(newAuto == !inAuto)\r\n    {  \/*we just went from manual to auto*\/\r\n        Initialize();\r\n    }\r\n    inAuto = newAuto;\r\n}\r\n \r\nvoid Initialize()\r\n{\r\n   lastInput = Input;\r\n   \r\n   outputSum = Output;\r\n   if(outputSum &gt; outMax) outputSum= outMax;\r\n   else if(outputSum &lt; outMin) outputSum= outMin;\r\n}\r\n \r\nvoid SetControllerDirection(int Direction)\r\n{\r\n   controllerDirection = Direction;\r\n}<\/pre>\n<\/div>\n<p>And there you have it. The above functionality is what is now present in v1.2.0 of the Arduino PID.<\/p>\n<h3>But wait, there&#8217;s more: Setpoint Weighting.<\/h3>\n<p>I didn&#8217;t add the following to the Arduino library code, but this is a feature that might be of some interest if you want to roll your own. Setpoint Weighting is, at it&#8217;s core, a way to have both PonE and PonM at the same time. By specifying a ratio between 0 and 1, you can have 100% PonM, 100% PonE (respectively) or some ratio in between. This can be helpful if you have a process that&#8217;s not perfectly integrating (like a reflow oven) and want to account for this.<\/p>\n<p>Ultimately I decided not to add it to the library at this time, as it winds up being ANOTHER parameter to adjust\/explain, and I didn&#8217;t think the resulting benefit was worth it. At any rate, here is the code if you&#8217;d like to modify the code to have Setpoint Weighting instead of just pure PonM\/PonE selection:<\/p>\n<div style=\"height: 400px; overflow-y: scroll;\">\n<pre class=\"brush: css; gutter: true; highlight: [19,20,37,43,58,60,62,63,77,78]; title: ; notranslate\" title=\"\">\r\n\/*working variables*\/\r\nunsigned long lastTime;\r\ndouble Input, Output, Setpoint;\r\ndouble outputSum, lastInput;\r\ndouble kp, ki, kd;\r\nint SampleTime = 1000; \/\/1 sec\r\ndouble outMin, outMax;\r\nbool inAuto = false;\r\n \r\n#define MANUAL 0\r\n#define AUTOMATIC 1\r\n \r\n#define DIRECT 0\r\n#define REVERSE 1\r\nint controllerDirection = DIRECT;\r\n \r\n#define P_ON_M 0\r\n#define P_ON_E 1\r\nbool pOnE = true, pOnM = false;\r\ndouble pOnEKp, pOnMKp;\r\n\r\n\r\nvoid Compute()\r\n{\r\n   if(!inAuto) return;\r\n   unsigned long now = millis();\r\n   int timeChange = (now - lastTime);\r\n   if(timeChange&gt;=SampleTime)\r\n   {\r\n   \r\n      \/*Compute all the working error variables*\/\t \t\r\n      double error = Setpoint - Input;\t \r\n      double dInput = (Input - lastInput);\r\n      outputSum+= (ki * error);\t \r\n\t  \r\n\t  \/*Add Proportional on Measurement, if P_ON_M is specified*\/\r\n\t  if(pOnM) outputSum-= pOnMKp * dInput\r\n\t  \r\n      if(outputSum &gt; outMax) outputSum= outMax;\t \t\r\n      else if(outputSum &lt; outMin) outputSum= outMin;  \r\n   \t\r\n   \t  \/*Add Proportional on Error, if P_ON_E is specified*\/ \r\n\t  if(pOnE) Output = pOnEKp * error; \r\n\t  else Output = 0;\r\n   \t  \r\n   \t  \/*Compute Rest of PID Output*\/ \r\n   \t  Output += outputSum - kd * dInput; \r\n   \r\n      if(Output &gt; outMax) Output = outMax;\r\n      else if(Output &lt; outMin) Output = outMin;\r\n \r\n      \/*Remember some variables for next time*\/\r\n      lastInput = Input;\r\n      lastTime = now;\r\n   }\r\n}\r\n \r\nvoid SetTunings(double Kp, double Ki, double Kd, double pOn)\r\n{\r\n   if (Kp&lt;0 || Ki&lt;0|| Kd&lt;0 || pOn&lt;0 || pOn&gt;1) return;\r\n \r\n   pOnE = pOn&gt;0; \/\/some p on error is desired;\r\n   pOnM = pOn&lt;1; \/\/some p on measurement is desired;  \r\n  \r\n   double SampleTimeInSec = ((double)SampleTime)\/1000;\r\n   kp = Kp;\r\n   ki = Ki * SampleTimeInSec;\r\n   kd = Kd \/ SampleTimeInSec;\r\n \r\n  if(controllerDirection ==REVERSE)\r\n   {\r\n      kp = (0 - kp);\r\n      ki = (0 - ki);\r\n      kd = (0 - kd);\r\n   }\r\n   \r\n   pOnEKp = pOn * kp; \r\n   pOnMKp = (1 - pOn) * kp;\r\n}\r\n \r\nvoid SetSampleTime(int NewSampleTime)\r\n{\r\n   if (NewSampleTime &gt; 0)\r\n   {\r\n      double ratio  = (double)NewSampleTime\r\n                      \/ (double)SampleTime;\r\n      ki *= ratio;\r\n      kd \/= ratio;\r\n      SampleTime = (unsigned long)NewSampleTime;\r\n   }\r\n}\r\n \r\nvoid SetOutputLimits(double Min, double Max)\r\n{\r\n   if(Min &gt; Max) return;\r\n   outMin = Min;\r\n   outMax = Max;\r\n \r\n   if(Output &gt; outMax) Output = outMax;\r\n   else if(Output &lt; outMin) Output = outMin;\r\n \r\n   if(outputSum &gt; outMax) outputSum= outMax;\r\n   else if(outputSum &lt; outMin) outputSum= outMin;\r\n}\r\n \r\nvoid SetMode(int Mode)\r\n{\r\n    bool newAuto = (Mode == AUTOMATIC);\r\n    if(newAuto == !inAuto)\r\n    {  \/*we just went from manual to auto*\/\r\n        Initialize();\r\n    }\r\n    inAuto = newAuto;\r\n}\r\n \r\nvoid Initialize()\r\n{\r\n   lastInput = Input;\r\n   outputSum = Output;\r\n   if(outputSum &gt; outMax) outputSum= outMax;\r\n   else if(outputSum &lt; outMin) outputSum= outMin;\r\n}\r\n \r\nvoid SetControllerDirection(int Direction)\r\n{\r\n   controllerDirection = Direction;\r\n}\r\n<\/pre>\n<\/div>\n<p>Instead of setting pOn as an integer, it now comes in as a double which allows for a ratio (line 58.) In addition to some flags (lines 62&amp;63) weighted Kp terms are computed on lines 77-78. Then on lines 37 and 43 the weighted PonM and PonE contributions are added to the overall PID output.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post I spent all my time explaining the benefits of Proportional on Measurement. In this post I&#8217;ll explain the code. People seemed to appreciate the step-by-step way I explained things last time, so that&#8217;s what I&#8217;ll do &hellip; <a href=\"http:\/\/brettbeauregard.com\/blog\/2017\/06\/proportional-on-measurement-the-code\/\">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":[],"class_list":["post-2342","post","type-post","status-publish","format-standard","hentry","category-coding","category-pid"],"_links":{"self":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/2342","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=2342"}],"version-history":[{"count":84,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/2342\/revisions"}],"predecessor-version":[{"id":2476,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/posts\/2342\/revisions\/2476"}],"wp:attachment":[{"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/media?parent=2342"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/categories?post=2342"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/brettbeauregard.com\/blog\/wp-json\/wp\/v2\/tags?post=2342"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}