最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助。作者Brett Beauregard的原文网址:http://brettbeauregard.com/blog/2017/06/proportional-on-measurement-the-code/

  在上一篇文章中,我把所有的时间都花在解释了比例测量的好处上。在这篇文章中,我将解释代码。人们似乎很欣赏我上次一步一步地解释事情的方式,所以在此我也将采取这样的方式。下面的3个步骤详细介绍了我是如何将 PonM 添加到 PID 库的。

第一阶段–初始输入和比例模式选择

 /*working variables*/
unsigned long lastTime;
double Input,Output,Setpoint;
double ITerm,lastInput;
double kp,ki,kd;
int SampleTime = ; //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< || Ki<|| Kd<) return; PonE = pOn == P_ON_E; double SampleTimeInSec = ((double)SampleTime)/;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE)
{
kp = ( - kp);
ki = ( - ki);
kd = ( - kd);
}
} void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > )
{
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 是无用的,但重要的是要记住,对于许多回路,它工作的很好。因此,我们需要让用户选择他们想要的模式,然后在计算中相应地操作。

第二阶段–动态更改整定参数

  虽然上面的代码确实有效,但它有一个我们以前看到的问题。当整定参数在运行时发生更改,我们会得到一个不希望出现的信号。

  为什么会这样?

  上次我们看到这一点时,是积分项被一个新的Ki 重新调整。而这一次,是比例项(输入-初始输入) 被新的Kp所更改。我选择的解决方案类似于我们为 Ki 所做的:我们不再是将(输入-初始输入)作为一个整体乘以当前 Kp,而是我把它分解成单独的步骤乘以当时的Kp:

 /*working variables*/
unsigned long lastTime;
double Input,Output,Setpoint;
double ITerm,lastInput;
double kp,ki,kd;
int SampleTime = ; //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< || Ki<|| Kd<) return; PonE = pOn == P_ON_E; double SampleTimeInSec = ((double)SampleTime)/;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE)
{
kp = ( - kp);
ki = ( - ki);
kd = ( - kd);
}
} void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > )
{
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 = ;
ITerm = Output;
if(ITerm > outMax) ITerm= outMax;
else if(ITerm < outMin) ITerm= outMin;
} void SetControllerDirection(int Direction)
{
controllerDirection = Direction;
}

  我们现在保留一个有效的和组成的P项,而不是将输入-初始输入作为一个整体乘以Kp。在每一步中,我们只需将当前输入变化乘以当时的Kp,并从P项(第41行) 中减去它。在这里,我们可以看到变化的影响:

  因为旧的Kp是已经存储,调整参数的变化只会影响我们后续的过程。

最终阶段–求和问题。

  我不会进入完整的细节 (花哨的趋势等) 以及上述代码有什么问题。这相当不错,但仍有重大问题。例如:

  类式积分饱和:虽然最终的输出限制在OUTmin和OUTmax之间。当PTerm不应该增长时,它有可能增长。它不会像积分饱和那样糟糕,但仍然是不可接受的。

  动态更改:在运行时,如果用户想从P _ On _ M 更改为P_ ON _E,并在一段时间后返回,那么P项将不会被初始化,这会导致输出振荡。

  还有更多,但仅仅这些就足以让人看到真正的问题是什么。早在我们创建I项的时候,我们已经处理过所有这些问题。我没有对P项重新执行相同的解决方案,而是选择了一个更美观的解决方案。

  通过将P项和I项合并到一个名为“outputSum”的变量中,P _ ON _ M 代码将受益于已存在的所有上下文修补程序,并且由于代码中没有两个总和,因此不会出现不必要的冗余。

 /*working variables*/
unsigned long lastTime;
double Input,Output,Setpoint;
double outputSum,lastInput;
double kp,ki,kd;
int SampleTime = ; //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 = ; /*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< || Ki<|| Kd<) return; PonE = pOn == P_ON_E; double SampleTimeInSec = ((double)SampleTime)/;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE)
{
kp = ( - kp);
ki = ( - ki);
kd = ( - kd);
}
} void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > )
{
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;
}

  现在你可以获得它。因为上述功能现在已存在于 V1.2.0版的Arduino PID库中。

但等等,还有更多:设定点加权。

  我没有将下面的代码添加到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 = ; //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 = ; /*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< || Ki<|| Kd< || pOn< || pOn>) return; PonE = pOn>; //some p on error is desired;
pOnM = pOn<; //some p on measurement is desired; double SampleTimeInSec = ((double)SampleTime)/;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE)
{
kp = ( - kp);
ki = ( - ki);
kd = ( - kd);
} PonEKp = pOn * kp;
pOnMKp = ( - pOn) * kp;
} void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > )
{
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设置为整数,而是以双精度输入,允许使用一个比率(第58行)。除了一些标记(第62行和第63行)外,第77-78行计算了加权Kp项。然后在第37行和第43行,将加权后的PonM和PonE贡献添加到整个PID输出中。

欢迎关注:

改进初学者的PID-测量的比例编码的更多相关文章

  1. 改进初学者的PID-测量的比例介绍

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  2. 改进初学者的PID-介绍

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  3. PID控制器(比例-积分-微分控制器)- I

    形象解释PID算法 小明接到这样一个任务: 有一个水缸点漏水(而且漏水的速度还不一定固定不变),要求水面高度维持在某个位置,一旦发现水面高度低于要求位置,就要往水缸里加水. 小明接到任务后就一直守在水 ...

  4. 改进初学者的PID-正反作用

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  5. 改进初学者的PID-修改整定参数

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  6. 改进初学者的PID-采样时间

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  7. PID控制器(比例-积分-微分控制器)- IV

    调节/测量放大电路电路图:PID控制电路图 如图是PlD控制电路,即比例(P).积分(I).微分(D)控制电路. A1构成的比例电路与环路增益有关,调节RP1,可使反相器的增益在0·5一∞范围内变化; ...

  8. 改进初学者的PID-积分饱和

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

  9. 改进初学者的PID-初始化

    最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...

随机推荐

  1. jmeter源码环境(IDEA)

    jmeter源码环境(IDEA) jmeter 1. 本地环境 2. 下载源码 3. 下载依赖包 4. 导入IDEA 5. 运行 1. 本地环境 Windows7 java版本:1.8.0_191 a ...

  2. mongodb移除分片和添加分片(转)

    首先我们要移除的分片之后再次添加此分片时会出现添加失败的情况,需要在添加的分片上登录进行删除此分片之前数据库的历史数据比如testdb,删除分片上的数据库之后就可重新添加此分片到mongos中 1.执 ...

  3. UPUPW和ThinkPHP安装配置

    去年弄过,今年弄起来就简单多了. 参考的是<ThinkPHP实战>

  4. es6 字符串模板拼接和传统字符串拼接

    字符串拼接是在日常开发中必不可少的一个环节. 注意:字符串可以用单引号'',或者""双引号,出于方便大家理解,文章以下内容统一使用单引号''! 如果只是一个字符串和一个变量拼接,使 ...

  5. springboot 整合Swagger2的使用

    Swagger2相较于传统Api文档的优点 手写Api文档的几个痛点: 文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时. 接口返回结果不明确 不能直接在线测试接口,通常需要使用工 ...

  6. 关于input标签checkbox属性 和checked

    我们设置了type的属性为checkbox时,记住以下3个关键点 1.点勾选时或者说点击时,checked为选中,在input标签中是checked=“checked”,注意这里面无论checked= ...

  7. HDU - 4059: The Boss on Mars (容斥 拉格朗日 小小的优化搜索)

    pro: T次询问,每次给出N(N<1e8),求所有Σi^4 (i<=N,且gcd(i,N)==1) ; sol:  因为N比较小,我们可以求出素因子,然后容斥.  主要问题就是求1到P的 ...

  8. java技术思维导图(转载)

      在网上看到有个人总结的java技术的东东,觉得很好,就保存下来了,码农还真是累啊,只有不断的学习才能有所提高,才能拿更多的RMB啊. java技术思维导图 服务端思维导图 前端思维导图

  9. java代码实现文件的下载功能

    昨天,根据需求文档的要求,自己要做一个关于文件下载的功能,从学校毕业已经很久了,自己好长时间都没有做过这个了,于是自己上网百度,最终开发出来的代码如下: 哦!对了,我先说一下我的思路,首先需要获取服务 ...

  10. python面试题&练习题之嵌套循环

    1.打印如下结果: 1*5=5 2*10=20 3*15=45 ... 10*50=500 for i in range(1,11): print(str(i)+'x'+str((i*5))+'='+ ...