最近看到了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. 使用Arduino开发板连接干簧管(Reed Switch)的方法

    在现实生活中,干簧管(Reed Switch)有许多重要的应用,如磁性门开关.笔记本电脑.智能手机等.在本篇文章中,我们将了解一些干簧管的知识,并介绍如何使用Arduino开发板连接干簧管. 干簧管( ...

  2. KClass与函数引用详解

    继续学习Kotlin反射相关的东东. KClass: 在上一次是通过类来获取它的KClass对象: 那如果是一个对象呢?与这个对象对应的类的KClass对象又是如何获取的呢?像Java也是一样有相关机 ...

  3. 运行pyqt4生成py文件增加代码

    if __name__ == "__main__": import sys app = QtGui.QApplication(sys.argv) Form = QtGui.QWid ...

  4. php数组打乱顺序

    shuffle() PHP shuffle() 函数随机排列数组单元的顺序(将数组打乱).本函数为数组中的单元赋予新的键名,这将删除原有的键名而不仅是重新排序. 语法: bool shuffle ( ...

  5. docker-compose部署zabbix4.2.5

    桥接模式(mysql和zabbix-server镜像本人修改了一些配置进行重构过) cat zabbix.yml version: '3' services: mysql: image: debian ...

  6. C# 反射(转)

    什么是反射 Reflection,中文翻译为反射.        这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’.‘模块(Module)’.‘类型 ...

  7. navicat 远程连接服务器1130,1045问题报错处理

    本人踩坑多次,一开始网上搜罗,解决办法大同小异,摸索了很久才全部解决完成,小小bug真磨人啊 首先,根据我的踩坑记录,navicat 1045和navicat 1130貌似属于同一种解决方案,都是修改 ...

  8. 纯js房贷计算器开源

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. go 学习 (五):包管理

    一.设置环境变量 二.启用 go modules 功能 并设置代理 https://goproxy.io/zh/ 补充: GO111MODULE  有三个值:on.off.auto GO111MODU ...

  10. JavaScript基础05——严格模式

    严格模式: 除了正常运行模式,ECMAscript5添加了第二种运行模式:“严格模式”(strict mode).顾名思义,这种模式是的Javascript在更严格的条件下运行. 严格模式的作用: 1 ...