Proportional on Measurement – The Code

In the previous post I spent all my time explaining the benefits of Proportional on Measurement. In this post I’ll explain the code. People seemed to appreciate the step-by-step way I explained things last time, so that’s what I’ll do here. The 3 passes below detail how I went about adding PonM to the PID library.

First Pass – Initial Input and Proportional-Mode selection

/*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;
 
#define P_ON_M 0
#define P_ON_E 1
bool pOnE = true;
double initInput;

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 P-Term*/ 
      if(pOnE) Output = kp * error;
      else Output = -kp * (Input-initInput); 

      /*Compute Rest of PID Output*/ 
      Output += 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, int pOn)
{
   if (Kp<0 || Ki<0|| Kd<0) return;
 
   pOnE = pOn == P_ON_E;
  
   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;
   initInput = Input;
   ITerm = Output;
   if(ITerm > outMax) ITerm= outMax;
   else if(ITerm < outMin) ITerm= outMin;
}
 
void SetControllerDirection(int Direction)
{
   controllerDirection = Direction;
}

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

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 last post it might seem like PonE is useless, but it’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&55) and then act accordingly in the calculation (lines 37&38).

Second Pass – On-the-fly tuning changes

While the code above does indeed work, it has a problem that we’ve seen before. When tuning parameters are changed at runtime, we get an undesirable blip.

PonM-blip

Why is this happening?

PonM-blip-math

The last time we saw this, it was that the integral was being rescaled by a new Ki. This time, it’s that (Input – initInput) is being rescaled by Kp. the solution I chose is similar to what I did for Ki: instead of treating the Input – initInput as a monolithic unit multiplied by the current Kp, I broke it into individual steps multiplied by the Kp at that time:

PonM expansion

/*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;
 
#define P_ON_M 0
#define P_ON_E 1
bool pOnE = true;
double PTerm;

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 P-Term*/ 
   	  if(pOnE) Output = kp * error; 
   	  else 
   	  { 
   	     PTerm -= kp * dInput; 
   	     Output = PTerm; 
   	  } 
   	  
   	  /*Compute Rest of PID Output*/ 
   	  Output += 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, int pOn)
{
   if (Kp<0 || Ki<0|| Kd<0) return;
 
   pOnE = pOn == P_ON_E;
  
   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;
   PTerm = 0;
   ITerm = Output;
   if(ITerm > outMax) ITerm= outMax;
   else if(ITerm < outMin) ITerm= outMin;
}
 
void SetControllerDirection(int Direction)
{
   controllerDirection = Direction;
}

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:

PonM-no-blip

PonM-no-blip-math

Because the old Kps are “in the bank”, the change in tuning parameters only affects us moving forward

Final Pass – Sum problems.

I won’t go into complete detail (fancy trends etc) as to what wrong with the above code. It’s pretty good, but there are still major issues with it. For example:

  1. Windup, sort of: While the final output is limited between outMin and outMax, it’s possible for PTerm to grow when it shouldn’t. It wouldn’t be as bad as integral windup, but it still wouldn’t be acceptable
  2. On-the-fly changes: If the user were to change from P_ON_M to P_ON_E while running, then after some time return back, PTerm wouldn’t be property initialized and it would cause an output bump

There are more, but just these are enough to see what the real issue is. We’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.

By merging PTerm and ITerm into a single variable called “outputSum”, the P_ON_M code then benefits from the all the ITerm fixes that are already in place, and because there aren’t two sums in the code there isn’t needless redundancy.

/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double outputSum, 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;
 
#define P_ON_M 0
#define P_ON_E 1
bool pOnE = true;


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;	 
      double dInput = (Input - lastInput);
      outputSum+= (ki * error);	 
	  
	  /*Add Proportional on Measurement, if P_ON_M is specified*/
	  if(!pOnE) outputSum-= kp * dInput
	  
      if(outputSum > outMax) outputSum= outMax;	 	
      else if(outputSum < outMin) outputSum= outMin;  
   	
   	  /*Add Proportional on Error, if P_ON_E is specified*/ 
	  if(pOnE) Output = kp * error; 
	  else Output = 0;
   	  
   	  /*Compute Rest of PID Output*/ 
   	  Output += outputSum - 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, int pOn)
{
   if (Kp<0 || Ki<0|| Kd<0) return;
 
   pOnE = pOn == P_ON_E;
  
   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(outputSum > outMax) outputSum= outMax;
   else if(outputSum < outMin) outputSum= 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;
   
   outputSum = Output;
   if(outputSum > outMax) outputSum= outMax;
   else if(outputSum < outMin) outputSum= outMin;
}
 
void SetControllerDirection(int Direction)
{
   controllerDirection = Direction;
}

And there you have it. The above functionality is what is now present in v1.2.0 of the Arduino PID.

But wait, there’s more: Setpoint Weighting.

I didn’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’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’s not perfectly integrating (like a reflow oven) and want to account for this.

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’t think the resulting benefit was worth it. At any rate, here is the code if you’d like to modify the code to have Setpoint Weighting instead of just pure PonM/PonE selection:

/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double outputSum, 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;
 
#define P_ON_M 0
#define P_ON_E 1
bool pOnE = true, pOnM = false;
double pOnEKp, pOnMKp;


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;	 
      double dInput = (Input - lastInput);
      outputSum+= (ki * error);	 
	  
	  /*Add Proportional on Measurement, if P_ON_M is specified*/
	  if(pOnM) outputSum-= pOnMKp * dInput
	  
      if(outputSum > outMax) outputSum= outMax;	 	
      else if(outputSum < outMin) outputSum= outMin;  
   	
   	  /*Add Proportional on Error, if P_ON_E is specified*/ 
	  if(pOnE) Output = pOnEKp * error; 
	  else Output = 0;
   	  
   	  /*Compute Rest of PID Output*/ 
   	  Output += outputSum - 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, double pOn)
{
   if (Kp<0 || Ki<0|| Kd<0 || pOn<0 || pOn>1) return;
 
   pOnE = pOn>0; //some p on error is desired;
   pOnM = pOn<1; //some p on measurement is desired;  
  
   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);
   }
   
   pOnEKp = pOn * kp; 
   pOnMKp = (1 - pOn) * kp;
}
 
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(outputSum > outMax) outputSum= outMax;
   else if(outputSum < outMin) outputSum= 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;
   outputSum = Output;
   if(outputSum > outMax) outputSum= outMax;
   else if(outputSum < outMin) outputSum= outMin;
}
 
void SetControllerDirection(int Direction)
{
   controllerDirection = Direction;
}

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

Posted in Coding, PID | Comments Off on Proportional on Measurement – The Code

Introducing Proportional On Measurement

It’s been quite a while, but I’ve finally updated the Arduino PID Library. What I’ve added is a nearly-unknown feature, but one which I think will be a boon to the hobby community. It’s called “Proportional on Measurement” (PonM for short).

Why you should care

process-types

There are processes out there that are known as “Integrating Processes.” These are processes for which the output from the pid controls the rate of change of the input. In industry these comprise a small percentage of all processes, but in the hobby world these guys are everywhere: Sous-vide, linear slide, and 3D printer extruder temperature are all examples of this type of process.

The frustrating thing about these processes is that with traditional PI or PID control they overshoot setpoint. Not sometimes, but always:

PonE-9

This can be maddening if you don’t know about it. You can adjust the tuning parameters forever and the overshoot will still be there; the underlying math makes it so. Proportional on Measurement changes the underlying math. As a result, it’s possible to find sets of tuning parameters where overshoot doesn’t occur:
PonM-9
Overshoot can still happen to be sure, but it’s not unavoidable. With PonM and the right tuning parameters, that sous vide or linear slide can coast right in to setpoint without going over.

So What is Proportional on Measurement?

Similar to Derivative on Measurement, PonM changes what the proportional term is looking at. Instead of error, the P-Term is fed the current value of the PID input.

Proportional on Error: PonE-eqn

Proportional on Measurement: PonM-eqn

Unlike Derivative on Measurement, the impact on performance is drastic. With DonM, the derivative term still has the same job: resist steep changes and thus dampen oscillations driven by P and I. Proportional on Measurement, on the other hand, fundamentally changes what the proportional term does. Instead of being a driving force like I, it becomes a resistive force like D. This means that with PonM, a bigger Kp will make your controller more conservative.

Great. But how does this eliminate overshoot?

To undestand the problem, and fix, it helps to look at the different terms and what they contribute to the overall PID output. Here’s a response to a setpoint change for an integrating process (a sous-vide) using a traditional PID:

PonE-Components

The two big things to notice are:

  • When we’re at setpoint, the I-Term is the only contributor to the overall output.
  • Even though the setpoint is different at the start and end, the output returns to the same value. This value is generally known as the “Balance Point”: the output which results in 0 Input slope. For a sous-vide, this would correspond to just enough heat to compensate for heat-losses to the surroundings.

Here we can see why overshoot happens, and will always happen. When the setpoint first changes, the error present causes the I-Term to grow. To keep the process stable at the new setpoint, the output will need to return to the balance point. The only way for that to happen is for the I-Term to shrink. The only way for THAT to happen is to have negative error, which only happens if you’re above setpoint.

PonM changes the game

Here is the same sous-vide controlled using Proportional on Measurement (and the same tuning parameters):

PonM-Components
Here you should notice that:

  • The P-Term is now providing a resistive force. The higher the Input goes, the more negative it becomes.
  • Where before the P-Term became zero at the new setpoint, it now continues to have a value.

The fact the the P-Term doesn’t return to 0 is the key. This means that the I-Term doesn’t have to return to the balance point on its own. P and I together can return the output to the balance point without the I-Term needing to shrink. Because it doesn’t need to shrink, overshoot isn’t required.

How to use it in the new PID Library

If you’re ready to try Proportional on Measurement, and you’ve installed the latest version of the PID library, setting it up is pretty easy. The primary way to use PonM is to specify it in the overloaded constructor:

Constructor

If you’d like to switch between PonM and PonE at runtime, the SetTunings function is also overloaded:

SetTunings

You only need to call the overloaded method when you want to switch. Otherwise you can use the regular SetTunings function and it will remember your choice.

Posted in PID | Comments Off on Introducing Proportional On Measurement

Priorites


So I’ve been tapering off on my projects recently, and i wanted to quickly post to explain that this trend will continue for the forseeable future. More important things have come up. I’m still checking messages and answering questions, but my pace is slower now.

Posted in Uncategorized | Comments Off on Priorites

My adventures in Silksceening – Part 1

The Motivation

It’s nice to have a souvenir when I come back from vacation: a shot glass, a knick-knack, a T-shirt; a little reminder helps the trip last longer in my mind.

On my last vacation, I realized how much buying souvenirs grates on my diy sensibilites. I wanted a T-Shirt, but I didn’t want what they were selling. I wanted something simple. I wanted the design I had unconsciously completed in my head.

Something clicked. I decided that instead spending $20 on a $5 cookie-cutter T-shirt, I would make my own shirt as soon as I got home.
Continue reading

Posted in Craft | Tagged | Comments Off on My adventures in Silksceening – Part 1

“Beginner’s PID” Series Translated to Spanish


HUGE thanks to Jonathan Moyano who has gone through the trouble of translating the Beginner’s PID series into Spanish! You can see his announcement here and here.

Here’s a direct link to his file. In case that goes down I’ve also set up a local mirror here.

Posted in PID | 1 Comment

PID Library makes it into (another) successful kickstarter [PID Showcase]

You may remember the espresso machine that was on kickstarter earlier this year. You know, where they asked for $20k and got $369k instead?

Well it looks like the PID Library has made it on to another wild Kickstarter ride. This time it’s with the Nomiku: an ingenious sous-vide solution. I’m a sucker for forehead-smack ideas, and this product is a great one. Rather than sell you an entire bulky unit, the Nomiku a small, easy to store, device that clips to a pot you already have.

The icing on the cake is that I know these guys. I met them at makerfaire last year and they’re great; very passionate about this stuff. Actually, it seemed like they were just passionate. about everything. I’m really glad that this is working out for them!

This is what I love about working on base-level components. I only have to do the work once, and then I get to see it crop up in tons of cool stuff. It makes me proud to know I helped those people along a little. I can only imagine how the Arduino team feels.

Posted in PID, Showcase | Comments Off on PID Library makes it into (another) successful kickstarter [PID Showcase]

10-Pin offset header for Arduino

A few years ago, I started making offset headers for the Arduino. These headers allow you to make shields using standard-spaced perfboard, by correcting the shift of that one strangely-placed Arduino header.

With the introduction of the Leonardo, The Arduino team kept the shift of the header, but added two pins to it.

To keep up in this offset header arms race, I ordered some 10-pin headers and modified my fabrication process to accept the two extra pins. I expect these to find their way into the hands of fine retailers over the next couple of months.

Posted in Mechanical | Tagged , | Comments Off on 10-Pin offset header for Arduino

Ladder Golf Construction Tricks

Spring is here, and that means it’s time to prepare for summer parties. If you’re me, that means making a ladder golf set. It seems like every year I’m making another one, the previous-year’s set having been gifted or left somewhere.

Over the years I’ve picked up a few tricks, and this time around I finally remembered to document them.
Continue reading

Posted in Mechanical | 2 Comments

DIY Custom Heating Element


A few years ago I made a BBQ smoke generator. I was fairly happy with the design, but felt that for the “next time” I would make a few changes.

One of my main issues was the size. I really wanted to make it smaller, but I didn’t want to pay for a small cartridge heater.

It occurred to me a couple of days ago that I might be able to modify a stock toaster-oven heating element to make a smaller heater. If I cut out a small section and drove it at a proportionally smaller voltage, I wondered, could I get a small heater with the same temperature as the big boy? It turns out that the answer is yes.
Continue reading

Posted in Mechanical | 3 Comments

Knitting


It’s been a busy few months.  Between the osPID and my day job, it’s been pretty hectic.  Somehow, in the middle of it all, I decided to learn how to knit.
Continue reading

Posted in Craft, Projects | Tagged | 1 Comment