Recently I saw a series of articles about PID published by Brett Beauregard. I feel it is very helpful to understand the PID algorithm. So I translated the series of articles! In the process of self-improvement, I also hope to be helpful to my colleagues. Author Brett Beauregard's original website: http: //brettbeauregard.com/blog/2011/04/improving-the-beginner's-pid-initialization/
1. What's the problem?
In the previous section, we realized the function of turning off and turning on the PID. We'll close it, but now let's see what happens when we reopen it:
Ah! The PID jumps back to the last output it sent, and then adjusts from there. This will lead to input bumps that we don't want to see.
2. Solutions
This is easy to solve. Because we now know when to turn it on (from manual to automatic), we just need to do some initialization for a smooth transition. This means that the storage of two working variables (integral and final input) is processed to prevent output jumps.
3. 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 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) { double SampleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; } 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; }
We modified SetMode (...) to detect manual to automatic conversion and added initialization. It deals with the integral term by setting "integral term = output" and "final input = input" to prevent the proliferation of differential. The scale item does not depend on any previous information, so no initialization is required.
4. Final results
As we can see from the chart above, correct initialization leads to manual to automatic undisturbed switching, which is exactly what we are pursuing.
5. Update: Why not ITerm=0?
I recently received a lot of questions about why I didn't set ITerm=0 to initialize. As an answer, I would like you to consider the following scenario: the PID is manual and the user has set the output to 50. After a period of time, the process stabilizes to 75.2 inputs. The user will set point 75.2 to turn on the PID. What will happen?
In my opinion, after switching to automation, the output value should be kept at 50. Since P and D items will be zero, the only way to do this is to initialize the ITerm item to an "output" value.
If you are in a situation where you need to initialize the output to zero, you do not need to change the above code. Before converting PID from "manual" to "automatic", just set Output=0 in the calling routine.