Arduino PID 库讲解
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;
}
随着输入的变化,“测量比例”会提供越来越大的阻碍,但是如果没有参考系,我们的性能会有些不稳定。 如果第一次打开控制器时PID输入为10000,我们真的要开始以Kp * 10000进行抵抗吗? 否。我们希望将初始输入用作参考点(第108行),随着输入从此处更改(第38行),增加或减少阻力。
我们需要做的另一件事是允许用户选择是否要按比例进行误差或测量。 在上一篇文章之后,PonE似乎毫无用处,但重要的是要记住,对于许多循环来说,它运作良好。 因此,我们需要让用户选择他们想要的模式(第51和55行),然后在计算中采取相应的行动(第37和38行)。
Second Pass – On-the-fly tuning changes动态调整
尽管上面的代码确实可以工作,但是它有一个我们之前已经看到的问题。 在运行时更改调整参数时,会出现不希望出现的现象
为什么会这样呢?
我们上次看到此结果的原因是,积分是通过新的Ki重新缩放的。 这次是(Input – initInput)由Kp重新缩放。 我选择的解决方案类似于我对Ki所做的解决方案:我没有将Input – initInput视为一个整体单元乘以当前Kp的方法,而是将其分解为各个步骤,然后分别乘以Kp:
/*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;
}
注:20,39-43,114为改动行
现在,我们保留一个有效的总和PTerm,而不是将Input-initInput的全部乘以Kp。 在每一步中,我们仅将当前输入更改乘以Kp并从PTerm中减去(第41行。)在这里,我们可以看到更改的影响:
由于旧的Kps位于“银行”中,因此调整参数的变化只会影响我们向前迈进
Final Pass – Sum problems.
关于上述代码的错误,我不会详细介绍(流行趋势等)。很好,但是仍然存在重大问题。例如:
- 结束时间:虽然最终输出限制在outMin和outMax之间,但PTerm可能会在不希望的时候增加。它不会像完整的缠绕那样糟糕,但是仍然不能被接受
- 即时更改:如果用户在运行时从P_ON_M更改为P_ON_E,则一段时间后返回,则不会对PTerm进行属性初始化,这会导致输出颠簸
还有更多,但仅这些就足以了解真正的问题是什么。在创建ITerm之前,我们已经处理了所有这些问题。我决定不为PTerm重新执行相同的解决方案,而是决定使用一种更美观的解决方案。
通过将PTerm和ITerm合并到一个称为“ outputSum”的变量中,P_ON_M代码将从已存在的所有ITerm修复中受益,并且由于代码中没有两个和,因此没有不必要的冗余。
/*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 * dInputif(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;
}
那里有。 以上功能是Arduino PID v1.2.0中提供的功能。
But wait, there’s more: Setpoint Weighting.
我没有在Arduino库代码中添加以下内容,但这是一项功能,如果您想自己动手,可能会感兴趣。 设定点加权是其核心,是同时拥有PonE和PonM的一种方法。 通过指定0到1之间的比率,您可以分别具有100%PonM,100%PonE或两者之间的某个比率。 如果您的流程集成度不理想(例如回流炉),并且对此有所考虑,这可能会有所帮助。
最终,我决定此时不将其添加到库中,因为它最终成为要调整/解释的另一个参数,而且我认为由此带来的好处是不值得的。 无论如何,这是代码,如果您想修改代码以具有设定值权重,而不仅仅是选择PonM / PonE:
/*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 * dInputif(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;
}
现在,pOn不再是将pOn设置为整数,而是以允许允许比率的双精度形式出现(第58行)。除了一些标志(第62和63行)以外,还在第77-78行计算了加权Kp项。 然后,在第37和43行上,将加权的PonM和PonE贡献添加到总体PID输出中。