Arduino PID库 (5):开启或关闭 PID 控制的影响
Arduino PID库 (4):Reset Windup
问题
尽管拥有一个PID控制器很好,但有时你并不关心它要说什么。
PID On - User Overwritting Output:PID 开启 - 用户覆盖输出
Confused PID keeps asking for more:混乱的 PID 不断要求更多
图中,蓝色的竖虚线表示在时间轴上,PID 控制由开启状态转为关闭的状态。如果我们对于关闭 PID 采取的操作是强制设定输出为 “PID 由开启转关闭对应的输出值或更小或为 0”。由于输出被设为定值,由于输出的 “力度” 不够,那么被控量会缓慢减少。但对于 PID 控制器来说,他不知道 PID 已经关闭了,由于被控量减少了,他就会努力增加输出,以求改变这种态势。但现实是残酷的,为何 “肆意” 增加输出,被控量却没有按照我设定的方向发展。此时,若突然又将 PID 打开了,将会有一个极大的输出,稳定性降低。
假设在程序中的某个时刻,您希望将输出强制为某个值(例如 0),您当然可以在调用例程中执行此操作:
void loop()
{
Compute();
Output=0;
}
这样,无论 PID 说什么,您都只需覆盖其值。然而,这在实践中是一个糟糕的想法。PID会变得非常困惑:“我一直在移动输出,什么也没发生!给什么?!让我再动一下。因此,当您停止覆盖输出并切换回 PID 时,您可能会立即获得输出值的巨大变化。
解决方案
这个问题的解决方案是有一种关闭和打开PID的方法。这些状态的常用术语是“手动”(我将手动调整值)和“自动”(PID 将自动调整输出)。让我们看看这是如何在代码中完成的:
《代码》
/*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; // 这里决定开启/关闭PID
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)
{
inAuto = (Mode == AUTOMATIC);
}
一个相当简单的解决方案。如果未处于自动模式,请立即离开计算函数,而不调整输出或任何内部变量。
结果
确实,您可以通过不从调用例程调用 Compute 来实现类似的效果,但此解决方案保留了 PID 的工作原理,这正是我们所需要的。通过将事情保持在内部,我们可以跟踪处于哪种模式,更重要的是,当我们更改模式时,它可以让我们知道。这就引出了下一个问题…