CapnBry / HeaterMeter

HeaterMeter and LinkMeter Arduino BBQ Controller
https://tvwbb.com/forums/heatermeter-diy-bbq-controller.85/
MIT License
507 stars 82 forks source link

Proportional on measurement #42

Open mattncsu opened 6 years ago

mattncsu commented 6 years ago

Have you considered implementing proportional on measurement from the Arduino PID library to reduce or eliminate overshoot since smoker temperature control is an integrating process? It doesn't appear to be too difficult to implement, it just adds one extra term to the PID equation.

bool PID::Compute()
{
   if(!inAuto) return false;
   unsigned long now = millis();
   unsigned long timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {
      /*Compute all the working error variables*/
      double input = *myInput;
      double error = *mySetpoint - 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*/
       double output;
      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;
        *myOutput = output;

      /*Remember some variables for next time*/
      lastInput = input;
      lastTime = now;
        return true;
   }
   else return false;
}
CapnBry commented 6 years ago

Interesting. I'll give it a try, but the major problem I see with it is that there's no (P * Error) contribution. This means if you set the setpoint to 225, and the current temp is say 80, the output will be 0% which means there will be no way to get it to 225F. It is relying on the Isum to build up to drive the output, which means the Isum will ramp up to 100% until the temperature is reached which then leaves us in the same place.

In the short example graphs shown on that webpage, it isn't a problem because the output is still ramping up when it reaches the setpoint. In a longer term test, say a sous video setup, their Isum (labeled I-term there) would reach 100% as well if the heatup time was a sufficient number of PID cycles in the future. This value is cumulatively reduced by the Derivative P, and instantaneously reduced by Derivative D which seems very redundant. I think their "PonM" graph is also wrong because when the control reaches setpoint, there will be dInput = 0 so the "P-Term" will be 0 and the graph should look just like the D-Term graph or merge them into one "sum" stacked graph.

Still, it isn't difficult to make a test to try it out although I feel like there's a reason why you don't see this in commercial PID software-- because the D term is already there for this exact reason.

mattncsu commented 6 years ago

The author goes into a little more detail on this page:

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

TheDogee commented 6 years ago

I was intrigued by this as well, and from the description sounds like it would work well. Implemented a couple variations with little success. Not sure if it was a coding problem or a tuning issue, but I ran out of patience because it takes so long to test changes on the WSM. It seemed to get wound up too easily or was too ineffective. The way it is implemented in the PonM library, the P and I terms get combined which makes it tough to observe.

CapnBry commented 6 years ago

I've implemented it two different ways and had some time to play with it and it is even more bonkers than I simulated in my mind. ponm-wiggy

The maximum output on startup is limited by the D value. outputSum will be saturated at 100% (assuming maxoutput set to 100%), and as the temperature starts to rise, say dT of 4 degrees, the output is going to be outputSum - 5 * D or 100 - 4*5 = 80% for a D of 5. That's all well and good, in that it takes the place of our "max startup fan speed" in limiting the startup deltaT and slowing the ramp up. I feel like that is more of an unintended consequence than it is by design, as this is never mentioned in on that blog or included in their graphs and calculations.

What happens if we're at 300 and want to get down to OFF? outputSum immediately saturates at 0% and the output turns off... so long as the temperature never starts dropping. Once it starts going down, the D term starts getting positive so the output turns on, having no PonE value to cancel it out. The temperature difference to target is so large 300I much much greater than the deltaTP so outputSum will stay 0 until it gets close enough that the I contribution doesn't overpower the P contribution. Both I and P constants need to be in the same ballpark (or within a decade of each other) as they are both integrated so both have the potential to quickly drive the output to 0 or 100.

This whole concept of integrating the deltaT * X is dubious at best, given than it is attempting to address the exact reason there is a D in PID. In practice, this is worse given the control band is now constrained by the changing temperature

I feel like there's just so many things wrong with this concept it isn't worth the 200 bytes of progmem to keep it around especially considering how contrary to expectations the output performs. When the setpoint is massively lower than current temp, it doesn't output 0%. When the setpoint is massively higher than current temp, it can take a longish time to ramp up and doesn't max out.

That said, it does only take 200 bytes of progmem and I can just cut it out if I need space at some point, so I've put it in the latest snapshot in the online repository so you can give it a try. To activate it, just set your P constant to a negative value.

mattncsu commented 6 years ago

Thanks for testing it out. I actually found a textbook reference to Proportional on Measurement that seems to confirm your findings and offers a fix for the "degraded response following a setpoint change" image1 image2

I can't remember which PID settings I have now, but my heatermeter usually controls my WSM close to +/- 1 degree F for hours at a time (until the coals need stirring-then oscillations start to occur)

CapnBry commented 6 years ago

Ooh that's a good link, I just spent the last hour reading both chapters looking for ideas. The solution presented just adjusts the setpoint further away from the current input to accelerate the integration which I don't think will help because we've already saturated the Isum at 0% (our integral lower limit is 0) and due to the D being in parallel, we can't remove its contribution so there's no way to fully turn off the output. One could write a special case where D is only allowed to contribute when the error is 100/D (inside the derivative band?) but that then messes with startup because the controller won't start backing down until it gets within that range which could cause further overshoot.

Also reading that paper, I'm not sure what the heck the Arduino PID code is doing. P on M isn't integrating! PonM should be instantaneous, He mentions it in his second article, in the second pass section but I can't figure out how it can achieve the proper output unless the input is already changing in the proper direction. P on M is supposed to guesstimate the proper output for a given setpoint so if we know ambient is 80 and our target is 225, PonM is supposed to set the output to P (225-80). If we know it generally takes 30% power to maintain 225, we'd set P constant to 30 / (225-80)= 0.207. A 300 setpoint would then get 0.207 (300-80)=45.54% output and then rely on the Isum to ramp up. It doesn't produce the same response to do PonM of 0.207*(set-start) vs 0.207+0.207+...+0.207 until you reach the same output value. For one, the integration relies on the temperature changing which it will never do if there's no change in the output. The faster the temperature changes, the faster the output will increase too. That's just nutters and contrary to what he was initially trying to achieve, which is a resistive force.

Now that I've read all this, I'm definitely going to back out the changes. While PonM is a thing, having it slowly ramp to that value relying on an external contribution to actually initiate the change is not sound and then applying it in reverse to the Isum is just baffling.

mattncsu commented 6 years ago

Sorry it didn't work out. It seemed like a good idea reading those posts. At least we all know a little more about PID theory now...

CapnBry commented 6 years ago

Too right we do! It was educational in that I've learned some new terms, like I didn't know what we had was an "independent gains (parallel form) proportional-on-error derivative-on-measurement" PID controller.

I also was able to find sort of a bug in the code in that the Isum could theoretically jump to a very large value of the temperature changes very rapidly but the controller isn't saturated yet. I'll keep that change. I'll also probably re-add PonM as an option in the more traditional sense, where it contributes just a straight P*setpoint so people can play with that if they like. I might also change the antiwindup code to operate always in the proportional band rather than on the last output? Not sure if that would make a mess of anything.

CapnBry commented 6 years ago

Actually, reading over it all again, equation 2.3(24) is for PonM and DonM and it uses -C for the proportional input so I guess the Arduino PID library is correct in that it is a resisting force? I'm very confused about how that's supposed to work. If it is 80 and we're shooting for 225:

m= P * (-80 + 1/I*(225-80) + D*deltaT)
Assuming 0 deltaT and that our I already includes P*1/I
m = -80*P + 145*I

If Isum is constrained to 0-100%, the controller will only be able to run at 100-(P*temp) so the higher the temperature, the lower the max output would be. That doesn't make any sense at all. How is this simple few lines of code taking several days to consider! It even says the proportional contribution to the output is -Kc*C, is all of this for a reverse-acting controller?

mattncsu commented 6 years ago

@br3ttb Any thoughts?

CapnBry commented 6 years ago

Well a couple dozen hours of reading and testing and yet another implementation. I've also read some papers on 2DOF PID controllers and unsurprisingly they all use a negative sign on the control variable. Two-Degree-of-Freedom PID Controllers by Mituhiko Araki and Hidefumi Taguchi Two-Degree-of-Freedom PID Controllers Structures by V.M. Alfaro and R. Vilanova?

I get a bit lost reading these papers when they jump right into using s which I have to assume is a factor of a Laplace Transform and it has been decades since I learned how to use those. What I've got now is from the textbook you linked where they do the "linear combination of PonE and PonM" formula 2.3(27). Right now I have it hard-coded with a lambda of 0.4, but due to how it works, I am going to make it a tunable parameter as well because the same code can work with the our old style PonE (lambda 1.0), support pure PonM (lambda 0.0), or roll your own mix. I can't make it tunable just yet because there would be no UI for it (the webui needs to support it) and I can't make changes there because I'm in the throes of converting it back to OpenWrt from LEDE. So for now, use a negative P value to indicate you want to use this PonMonE code (but the value is inverted by the code so -1 => 1).

Because the P contribution is always negative in this configuration, the limit on the integral sum needed to be increased so the output can reach 100%. Without this, the max output would be lower the higher the setpoint is, since I could only contribute 100%, and at steady state, P is -0.2 * setpoint * Pconstant. Max integral sum is therefore 100%+0.6*setpoint*P which should allow 100% output at setpoint if needed. The lowerbound integral sum is still 0.

In bench testing, it had a pretty good setpoint response with little overshoot and a llllooooong oscillation period. The disturbance response however was not great, although it could be because I don't have optimal coefficients. In grill testing, I saw a lot of overshoot when achieving setpoint. With this system the Isum always maxes out on startup, and the P contribution is roughly constant so the controller doesn't back off quickly as it passes the setpoint. This is mitigated by a more managed approach to the setpoint due to being almost entirely controlled and limited by the D contribution retarding the output throughout startup. (graph is of bench test) image

In any case, I've uploaded a snapshot AVR firmware with the current code to the online repository so it can be tried out. I used P,I,D = -0.4, 0.10, 12. I am concerned with the P value being on measurement meaning that, say 400 degrees is -96%, and Isum will range from 0-196% but the D term's range is always fixed due to being on Error.

TheDogee commented 6 years ago

I tried the snapshot for a short cook today of some poppers with my normal tuning parameters P=3,I=0.008,D=5. I have a MicroDamper with fan on WSM 18. I started with a half chimney of lit coals over unlit, started with fan on startup at 50% but change it eventually to 100% because it was not getting there. I negated the first term to -3 to try PonM. It behaved very strangely, seemed to get all wound up getting to setpoint (which took a long time, probably my fault, not enough lit?), but then as soon as it went slightly over the setpoint of 225, it abruptly went to zero and stayed there. Temp fell so much that the lid open detect went off, and I gave up and changed the sign on P back to positive to go back to normal mode (around 7:45 on graph). Any idea what happened here? Is it a bug, or operator error? 20180819_115004

I had the "P" window up at the time where it went to zero and both P and I had not drastically changed, but both were large. P was around -330, I was about 230.

CapnBry commented 6 years ago

Thanks for testing, however, you can't just invert the sign on the P value and switch to PonM. It's a whole different game and "3" is about 10x too high I think. The "I" for my test system was also 5x the PonE version, and the "D" is about 2x my normal PonE value.

It definitely is strange that it cut off roughly as it would have been getting close to starting to control though. There's definitely something weird happening there, I'll have to try your values and see if I can reproduce. Something might be overflowing internally due to the larger P than expected. Maybe try it with PID= -0.3, 0.08, 10 (which would be -1/10, 10x, 2x your PonE values).

TheDogee commented 6 years ago

Thanks for the reply. Makes sense based on the math that my values are off. The Android pid guy had suggested at using the same tuning values in his article, but that does not see like it will work. I will give it another go next time with more reasonable values.

TheDogee commented 6 years ago

Here is take 2 with the parameters you suggested. It went slightly better, but still had problems with the fan rapidly cutting out after overshoot. What basically seems to happen in that the I term hit its positive maximum before hitting setpoint, and then once overshoot occurs, it starts decrementing rapidly. The "D" term seems to make things worse. The P term does almost nothing to correct. Note that in the graph the first "lid open" was real, the next two were not. At 12:45 I gave up and went back to PonE. ponm_090218_0d3_0d08_10d0

pokeymud commented 2 years ago

Whew, I just wanna shoot out a quick thanks to the discussion on this subject. I'm not a heatermeter user, but this is the only place I could find any coherent discussion on the arduino PID's proportional-on-measurement feature, and what it is /supposed/ to be doing.

As a note, the implementation in the OP is from an early version of the library, which is very strange - it seems to want to integrate the differential term to achieve a similar effect to pure P on M (lambda =0), but I'm not sure exactly what all the extra work of integrating a differential accomplishes or if it causes any other possible problems. Additionally, the author of the library calling the effect of P on M "resistive" in either implementation is simply incorrect and a big part of my confusion. Anyway the latest implementation of that library just uses simple pure ponm as described in articles linked above.

Lastly in the spirit of accumulated knowledge, and helping out newbies like me: I found the wikipedia section on proportional and derivative action on measurement demystified the terms in the paper shared a bit because the article as a whole is self-contained and defines all the terms used in the formulae: https://en.wikipedia.org/wiki/PID_controller#Basing_proportional_action_on_PV

Thanks again!