改进初学者的PID-测量的比例编码
最近看到了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-测量的比例编码的更多相关文章
- 改进初学者的PID-测量的比例介绍
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- 改进初学者的PID-介绍
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- PID控制器(比例-积分-微分控制器)- I
形象解释PID算法 小明接到这样一个任务: 有一个水缸点漏水(而且漏水的速度还不一定固定不变),要求水面高度维持在某个位置,一旦发现水面高度低于要求位置,就要往水缸里加水. 小明接到任务后就一直守在水 ...
- 改进初学者的PID-正反作用
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- 改进初学者的PID-修改整定参数
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- 改进初学者的PID-采样时间
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- PID控制器(比例-积分-微分控制器)- IV
调节/测量放大电路电路图:PID控制电路图 如图是PlD控制电路,即比例(P).积分(I).微分(D)控制电路. A1构成的比例电路与环路增益有关,调节RP1,可使反相器的增益在0·5一∞范围内变化; ...
- 改进初学者的PID-积分饱和
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
- 改进初学者的PID-初始化
最近看到了Brett Beauregard发表的有关PID的系列文章,感觉对于理解PID算法很有帮助,于是将系列文章翻译过来!在自我提高的过程中,也希望对同道中人有所帮助.作者Brett Beaure ...
随机推荐
- js Date对象和数字对象
<script type="text/javascript"> alert(new Date.toLocaleString()); </script> 以本 ...
- 在windows下安装cx_Oracle问题
将 instantclient_11_2 所在的目录添加到环境变量,但是环境变量有时没有立即生效,可以复制 oci.dll(版本也要正确)到 \Python36\Lib\site-packages 目 ...
- intellij高亮字体背景颜色
https://blog.csdn.net/aosica321/article/details/52956419 https://blog.csdn.net/lxzpp/article/details ...
- Spring框架:Controller和RestController区别
了解如何利用SpringMVC的注释创建RESTful Web服务. Spring的基于注释的MVC框架简化了创建RESTful Web服务的过程.传统的Spring MVC控制器和RESTful W ...
- BZOJ 3689: 异或之 可持久化trie+堆
和超级钢琴几乎是同一道题吧... code: #include <bits/stdc++.h> #define N 200006 #define ll long long #define ...
- 通过redash query results 数据源实现跨数据库的查询
redash 提供了一个简单的 query results 可以帮助我们进行跨数据源的查询处理 底层数据的存储是基于sqlite的,期望后期有调整(毕竟处理能力有限),同时 query results ...
- vigil rpm 包制作
vigil 可以方便的进行服务的监控,以下尝试制作一个rpm 包,方便使用 安装依赖 ruby yum -y install ruby rubygems ruby-devel 修改gem 源 可选, ...
- 使用chrome浏览器调试移动端网页(非模拟访问)
1. 使用数据线连接手机与电脑 2. 打开手机调试模式 参考:http://jingyan.baidu.com/article/f79b7cb35ada4d9145023e63.html 本人使用的m ...
- 我的Android前生今世之缘-学习经验-安卓入门教程(六)
关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己. 本篇文章同步微信公众号 欢迎大家关注我的微信公众号:「醉翁猫咪」 据我所知,网上教学资料一堆一堆的,那么还有很多人说,如何学习? ...
- GoCN每日新闻(2019-10-12)
GoCN每日新闻(2019-10-12) 1. Go 1.13中sync.Pool是如何优化的 https://colobu.com/2019/10/08/how-is-sync-Pool-impro ...