diff --git a/improving-the-beginners-pid-introduction/1-sample-time.md b/improving-the-beginners-pid-introduction/1-sample-time.md new file mode 100644 index 0000000..df19b3a --- /dev/null +++ b/improving-the-beginners-pid-introduction/1-sample-time.md @@ -0,0 +1,41 @@ +# 采样时间 + +### 问题 + +初学者的PID被设计为不定期调用。这导致2个问题: + +- 您无法从PID中获得一致的行为,因为有时会频繁调用它,有时却不会。 +- 您需要做额外的数学计算导数和积分,因为它们都取决于时间的变化。 + +### 解决方案 + +确保定期调用PID。我决定执行此操作的方法是指定每个周期调用一次计算函数。根据预定的采样时间,PID决定是应该立即计算还是返回。 + +一旦知道PID是以固定间隔进行评估的,则导数和积分计算也可以简化。奖金! + +### 代码 + +现在在第10和11行,该算法自行决定是否该进行计算。另外,因为我们现在知道采样之间的时间是相同的,所以我们不需要不断地乘以时间变化。我们只能适当地调整Ki和Kd(第31和32行),结果在数学上是等效的,但效率更高。 + +虽然这样做有点皱纹。如果用户决定在操作期间更改采样时间,则需要重新调整Ki和Kd以反映此新更改。这就是第39-42行的全部内容。 + +还要注意,我在第29行将采样时间转换为秒。严格来说,这不是必需的,但是允许用户以1 / sec和s的单位而不是1 / mS和mS的形式输入Ki和Kd。 + +### 结果 + +上面的更改为我们做了三件事 + +1. 无论调用Compute()的频率如何,PID算法都会以固定的间隔进行评估[第11行] +2. 由于时间相减[第10行],当millis()换回0时不会有任何问题。这只会每55天发生一次,但是我们要防弹吗? +3. 我们不再需要乘以和除以时间变化。由于它是一个常数,因此我们可以将其从计算代码中移出[15 + 16行],并与调整常数[31 + 32行]融合在一起。从数学上讲,它的计算结果相同,但是每次对PID求值时都保存了乘法和除法运算 + +### 关于中断的旁注 + +如果此PID进入微控制器,则可以使用中断作为一个很好的论据。SetSampleTime设置中断频率,然后在时间到时调用Compute。在这种情况下,不需要9-12、23和24行。如果您打算通过PID强制执行此操作,那就去吧!不过请继续阅读本系列。希望您将从后续的修改中受益。 +我不使用中断的三个原因 + +1. 就本系列而言,并不是每个人都可以使用中断。 +2. 如果您希望它同时实现许多PID控制器,事情将会变得棘手。 +3. 老实说,这对我来说没有发生。 [吉米·罗杰斯](http://JimmiePRodgers.com)([Jimmie Rodgers)](http://JimmiePRodgers.com)在为我校对该系列作品时提出了建议。我可能决定在PID库的将来版本中使用中断。 + +[下一个>>](2-derivative-kick.md) diff --git a/improving-the-beginners-pid-introduction/2-derivative-kick.md b/improving-the-beginners-pid-introduction/2-derivative-kick.md new file mode 100644 index 0000000..e2fa485 --- /dev/null +++ b/improving-the-beginners-pid-introduction/2-derivative-kick.md @@ -0,0 +1,38 @@ +# 微分踢 + +### 问题 + +此修改将稍微调整派生项。目的是消除被称为“衍生踢”的现象。 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonE.png "微分踢")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonE.png) + +上图显示了该问题。由于error = Setpoint-Input,因此,Setpoint中的任何更改都会导致瞬时错误。这种变化的导数是无穷大(在实践中,由于dt不为0,所以它实际上是一个非常大的数字。)该数字被输入pid方程,这会导致输出出现不希望的峰值。幸运的是,有一种简单的方法可以摆脱这种情况。 + +### 解决方案 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonMExplain.png "DonM解释")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonMExplain.png) +结果表明,当设定值改变时,误差的导数等于输入的负导数,EXCEPT。最后这是一个完美的解决方案。代替加(Kd * Error的导数),我们减去(Kd * Input的导数)。这称为使用“测量导数” + +### 代码 + + +这里的修改非常简单。我们用-dInput替换+ dError。现在我们不用记住lastError了,而是记住了lastInput + +### 结果 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonM.png "唐")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/DonM.png) + +这些修改为我们带来了这些。请注意,输入看起来仍然相同。因此,我们获得了相同的性能,但是每次设定值更改时,我们都不会发出巨大的输出峰值。 + +这可能不重要。这完全取决于您的应用程序对输出峰值的敏感程度。从我的角度来看,不费吹灰之力就不需要做更多的工作,那为什么不正确呢? +[下一个>>](improving-the-beginner’s-pid-tuning-changes) + +[![知识共享许可](http://i.creativecommons.org/l/by-sa/3.0/80x15.png)](http://creativecommons.org/licenses/by-sa/3.0/) + +[![这个!](http://brettbeauregard.com/blog/wp-content/plugins/flattr/img/flattr-badge-large.png)](http://brettbeauregard.com/blog/?flattrss_redirect&id=765&md5=60afbb931f0038052433ba10a872d183 "平板") + +标签:[Arduino](http://brettbeauregard.com/blog/tag/arduino/),[初学者的PID](http://brettbeauregard.com/blog/tag/beginners-pid/),[PID](http://brettbeauregard.com/blog/tag/pid/) + +此项目被张贴上周五,2011年4月15日在下午3点02分,并提交下[编码](http://brettbeauregard.com/blog/category/pid/coding/),[PID](http://brettbeauregard.com/blog/category/pid/)。您可以通过[RSS 2.0](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-derivative-kick/feed/) feed 跟踪对此条目的任何响应。您可以在自己的网站上[留下回应](#respond)或[引用](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-derivative-kick/trackback/)。 + + diff --git a/improving-the-beginners-pid-introduction/3-tuning-changes.md b/improving-the-beginners-pid-introduction/3-tuning-changes.md new file mode 100644 index 0000000..6208d74 --- /dev/null +++ b/improving-the-beginners-pid-introduction/3-tuning-changes.md @@ -0,0 +1,40 @@ +# 调整更改 + +### 问题 + +对于任何受人尊敬的PID算法,必须具有在系统运行时更改调节参数的功能。 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegral.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegral.png) + +如果您尝试在运行时更改调整,则初学者的PID会有点疯狂。让我们看看为什么。这是上述参数更改前后初学者PID的状态: + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegralCode.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegralCode.png) + +因此,我们可以立即将此冲撞归咎于积分项(或“ I项”)。当参数更改时,这是唯一发生剧烈变化的事情。为什么会这样呢?它与初学者对积分的解释有关: + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegralEqn1.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadIntegralEqn1.png) + +直到更改Ki为止,这种解释才能正常工作。然后,您突然将这个新Ki乘以您累积的整个误差总和。那不是我们想要的!我们只想影响前进的脚步! + +### 解决方案 + +我知道有几种方法可以解决此问题。我在上一个库中使用的方法是重新缩放errSum。淇加倍?将errSum减半。这样可以防止I Term发生碰撞,并且可以正常工作。不过这有点笨拙,我想出了一些更优雅的方法。(我不可能是第一个想到这一点的人,但是我确实是一个人想到了。这很可恶!) + +该解决方案需要一些基本的代数(或者是微积分?) + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegralEqn.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegralEqn.png) + +我们没有将Ki置于积分之外,而是将其引入了积分。看起来我们什么都没做,但是我们会发现实际上这有很大的不同。 + +现在,我们将误差乘以当时的Ki值。然后,我们存储THAT的总和。当Ki发生变化时,就不会有颠簸了,因为可以说所有旧Ki都已经在“银行”中了。我们可以顺利进行转学,而无需进行其他数学运算。这可能使我成为一个极客,但我认为这很性感。 + +### 代码 + +因此,我们将errSum变量替换为复合ITerm变量[第4行]。它总结了Ki * error,而不仅仅是错误[第15行]。另外,由于Ki现在被埋在ITerm中,因此已从主PID计算中删除了该行[第19行]。 + +### 结果 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegral.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegral.png) +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegralCode.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/GoodIntegralCode.png) + 那么这如何解决问题。在更改ki之前,它会重新调整整个错误的总和。我们看到的每个错误值。使用此代码,以前的错误将保持不变,新的ki仅影响前进的事物,而这正是我们想要的。 +[下一个>>](improving-the-beginner’s-pid-reset-windup) diff --git a/improving-the-beginners-pid-introduction/4-reset-windup.md b/improving-the-beginners-pid-introduction/4-reset-windup.md new file mode 100644 index 0000000..347b38d --- /dev/null +++ b/improving-the-beginners-pid-introduction/4-reset-windup.md @@ -0,0 +1,33 @@ +# 重置结束 + +### 问题 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/Windup.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/Windup.png) +重置结束是一个陷阱,它可能比其他任何人都吸引更多的初学者。当PID认为自己可以做一些自己做不到的事情时,就会发生这种情况。例如,Arduino上的PWM输出接受0-255之间的值。默认情况下,PID不知道这一点。如果它认为300-400-500可以工作,它将尝试使用那些期望得到所需值的值。由于实际上该值固定在255,因此它将继续尝试越来越多的数字而无所适从。 + +问题以怪异的滞后的形式显现出来。在上方,我们可以看到输出“超出”外部极限。降低设定点后,输出必须先减小,然后才能降至255行以下。 + +### 解决方案–步骤1 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/No-Windup.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/No-Windup.png) +有几种方法可以减轻饱和,但是我选择的方法如下:告诉PID输出限制是什么。在下面的代码中,您将看到现在有一​​个SetOuputLimits函数。一旦达到任一限制,PID便停止求和(积分)。它知道无事可做。由于输出不会结束,因此当设定值下降到可以执行某项操作的范围时,我们会立即得到响应。 + +### 解决方案–步骤2 + +但是请注意,在上图中,尽管我们消除了饱和滞后,但并没有完全解决。pid认为正在发送的内容与正在发送的内容之间仍然存在差异。为什么?比例项和(在较小程度上)微分项。 + +即使已对积分项进行了安全钳位,P和D仍加了2美分,其结果高于输出限制。我认为这是不可接受的。如果用户调用了一个名为“ SetOutputLimits”的函数,他们必须假定这意味着“输出将保持在这些值之内”。因此,对于第2步,我们做出一个有效的假设。除了限制I项之外,我们还限制输出值,使其保持在期望的位置。 + +(请注意:您可能会问为什么我们需要同时钳制两个。如果我们无论如何都要进行输出,为什么还要分别钳制积分呢?如果我们所做的只是钳制输出,则积分项将回到增长的状态。尽管在升压过程中输出看起来不错,但在降压过程中我们会看到明显的滞后。) + +### 代码 + + +添加了一个新功能,允许用户指定输出限制[第52-63行]。这些限制用于钳制I项[17-18]和输出[23-24] + +### 结果 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/No-Winup-Clamped.png)](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/No-Winup-Clamped.png) + 如我们所见,消除了缠绕。另外,输出保持在我们想要的位置。这意味着无需外部钳位输出。如果您希望它的范围是23到167,则可以将其设置为“输出限制”。 +[下一个>>](improving-the-beginner’s-pid-onoff) + diff --git a/improving-the-beginners-pid-introduction/5-onoff.md b/improving-the-beginners-pid-introduction/5-onoff.md new file mode 100644 index 0000000..1725d66 --- /dev/null +++ b/improving-the-beginners-pid-introduction/5-onoff.md @@ -0,0 +1,43 @@ +# 开/关 + +### 问题 + +拥有PID控制器就好了,有时您根本不在乎它在说什么。 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadForcedOutput.png "BadForcedOutput")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BadForcedOutput.png) +假设您要在程序中的某个时刻将输出强制为某个值(例如0),当然可以在调用例程中执行此操作: + +```c +void loop() +{ + Compute(); + Output=0; +} +``` + +这样,无论PID怎么说,都可以覆盖它的值。但是,这在实践中是一个可怕的想法。PID将变得非常混乱:“我一直在移动输出,什么也没发生!是什么赋予了?!让我再移动一点。” 结果,当您停止覆盖输出并切换回PID时,输出值可能会立即发生巨大变化。 + +### 解决方案 + +解决此问题的方法是有一种方法可以打开和关闭PID。这些状态的常用术语是“手动”(我将手动调节值)和“自动”(PID将自动调节输出)。让我们看看如何在代码中完成此操作: + +### 代码 + + +一个相当简单的解决方案。如果您不在自动模式下,请立即退出Compute函数,而无需调整输出或任何内部变量。 + +### 结果 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BetterForcedOutput.png "更好的强制输出")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/BetterForcedOutput.png) + 的确,您可以通过不从调用例程中调用Compute来达到类似的效果,但是此解决方案可以保留PID的工作原理,这正是我们所需要的。通过将事物保持在内部,我们可以跟踪所处的模式,更重要的是,它使我们知道何时更改模式。这就引出了下一个问题…… +[下一页>>](improving-the-beginner’s-pid-initialization) + + [![知识共享许可](http://i.creativecommons.org/l/by-sa/3.0/80x15.png)](http://creativecommons.org/licenses/by-sa/3.0/) + + [![这个!](http://brettbeauregard.com/blog/wp-content/plugins/flattr/img/flattr-badge-large.png)](http://brettbeauregard.com/blog/?flattrss_redirect&id=770&md5=c4a77fae8176ec1859b591aaa0f6e32a "平板") + + 标签:[Arduino](http://brettbeauregard.com/blog/tag/arduino/),[初学者的PID](http://brettbeauregard.com/blog/tag/beginners-pid/),[PID](http://brettbeauregard.com/blog/tag/pid/) + + 此项目被张贴上周五,2011年4月15日下午3:05,并提交下[编码](http://brettbeauregard.com/blog/category/pid/coding/),[PID](http://brettbeauregard.com/blog/category/pid/)。您可以通过[RSS 2.0](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-onoff/feed/) feed 跟踪对此条目的任何响应。您可以在自己的网站上[留下回应](#respond)或[引用](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-onoff/trackback/)。 + + diff --git a/improving-the-beginners-pid-introduction/6-initialization.md b/improving-the-beginners-pid-introduction/6-initialization.md new file mode 100644 index 0000000..6fe6228 --- /dev/null +++ b/improving-the-beginners-pid-introduction/6-initialization.md @@ -0,0 +1,32 @@ +# 初始化 + +### 问题 + +在上一节中,我们实现了打开和关闭PID的功能。我们关闭了它,但是现在让我们看看重新打开它会发生什么: +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/NoInitialization.png "没有初始化")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/NoInitialization.png) + +kes!PID跳回到它发送的最后一个输出值,然后从那里开始调整。这会导致我们不希望有的输入冲击。 + +### 解决方案 + +这个很容易修复。因为我们现在知道打开电源的时间(从手动到自动),所以我们只需要初始化即可平滑过渡。这意味着要对2个存储的工作变量(ITerm和lastInput)进行按摩,以防止输出跳跃。 + +### 代码 + +我们修改了SetMode(…)以检测从手动到自动的过渡,并添加了初始化功能。它将ITerm = Output设置为整数项,并设置lastInput = Input以防止派生峰值。比例项不依赖于过去的任何信息,因此不需要任何初始化。 + +### 结果 + +[![](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/Initialization.png "初始化")](http://brettbeauregard.com/blog/wp-content/uploads/2011/03/Initialization.png) + +从上图可以看出,正确的初始化会导致从手动到自动的无扰转移:这正是我们所追求的。 +[下一个>>](improving-the-beginners-pid-direction) + +### 更新:为什么ITerm = 0? + +最近,我收到很多问题,问为什么初始化时不设置ITerm = 0。作为回答,我请您考虑以下情形:pid是手动的,并且用户已将输出设置为50。一段时间后,该过程稳定为输入75.2。用户设定设定值75.2并打开pid。应该怎么办? + +我认为在切换到自动后,输出值应保持在50。由于P和D项将为零,因此发生这种情况的唯一方法是将ITerm初始化为Output的值。 + +如果您需要将输出初始化为零,则无需更改上面的代码。只需在调用例程中设置Output = 0,然后将PID从“手动”转换为“自动”即可。 + diff --git a/improving-the-beginners-pid-introduction/7-direction.md b/improving-the-beginners-pid-introduction/7-direction.md new file mode 100644 index 0000000..e69de29 diff --git a/improving-the-beginners-pid-introduction/8-proportional-on-measurement-the-code.md b/improving-the-beginners-pid-introduction/8-proportional-on-measurement-the-code.md new file mode 100644 index 0000000..a8dd092 --- /dev/null +++ b/improving-the-beginners-pid-introduction/8-proportional-on-measurement-the-code.md @@ -0,0 +1,54 @@ +# 测量比例-守则 + +在上[一篇文章中,](http://brettbeauregard.com/blog/2017/06/introducing-proportional-on-measurement/)我将所有时间都花在解释“比例测量”的好处上。在这篇文章中,我将解释代码。人们似乎很欣赏我[上次](http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/)解释事情的逐步方式[,](http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/)所以我将在这里做。下面的3条详细说明了我如何将PonM添加到PID库中。 + +### 首过–初始输入和比例模式选择 + +随着输入的变化,“测量比例”会提供越来越大的电阻,但是如果没有参考系,我们的性能将有些不稳定。如果首次打开控制器时PID输入为10000,我们真的要开始以Kp * 10000进行抵抗吗?否。我们希望将初始输入用作参考点(第108行),随着输入从此处变化(第38行),增加或减少阻力。 + +我们需要做的另一件事是允许用户选择是否要按比例进行误差或测量。在[上](http://brettbeauregard.com/blog/2017/06/introducing-proportional-on-measurement/)一篇[文章](http://brettbeauregard.com/blog/2017/06/introducing-proportional-on-measurement/)之后,PonE似乎毫无用处,但重要的是要记住,对于许多循环而言,它运行良好。这样,我们需要让用户选择他们想要的模式(第51和55行),然后在计算中采取相应的行动(第37和38行)​​。 + +### 第二遍–动态调整 + +尽管上面的代码确实可以工作,但是它有一个我们之前已经看到的问题。在运行时更改调整参数时,会出现不希望出现的现象。 + +[![庞贝](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-blip.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-blip.png) + +为什么会这样呢? + +[![PonM斑点数学](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-blip-math-1.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-blip-math-1.png) + +[我们上次看到此](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-tuning-changes/)结果是因为积分是通过新的Ki重新缩放。这次是(Input – initInput)由Kp重新缩放。我选择的解决方案类似于我对Ki所做的解决方案:我没有将Input – initInput视为一个整体单元乘以当前Kp的方法,而是将其分解为各个步骤,然后分别乘以Kp: + +[![PonM扩展](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-expansion.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-expansion.png) + + +现在,我们保留一个有效的总和PTerm,而不是将Input-initInput的整体乘以Kp。在每一步中,我们仅将当前输入更改乘以Kp并从PTerm中减去(第41行)。在这里,我们可以看到更改的影响: + +[![PonM-no-blip](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-no-blip.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-no-blip.png) + +[![庞氏无斑点数学](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-no-blip-math.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-no-blip-math.png) + +由于旧的Kps位于“银行”中,因此调整参数的变化只会影响我们向前迈进 + +### 最终通行证–总和。 + +对于上面的代码有什么问题,我不会详细介绍(流行趋势等)。很好,但是仍然存在重大问题。例如: + +1. **终结,排序的**:虽然最后输出OUTMIN和OUTMAX之间的限制,有可能为PTerm当它不应该增长。它不会像[积分缠绕](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-reset-windup/)那样糟糕,但是仍然不能接受 +2. **即时更改**:如果用户在运行时从P_ON_M更改为P_ON_E,则一段时间后返回,PTerm将不会被属性初始化,并且会导致输出颠簸 + +还有更多,但仅这些就足以了解真正的问题是什么。在创建ITerm之前,我们已经处理了所有这些问题。我决定不为PTerm重新执行相同的解决方案,而是决定使用一种更美观的解决方案。 + +通过将PTerm和ITerm合并到一个称为“ outputSum”的变量中,P_ON_M代码将从已存在的所有ITerm修复中受益,并且由于代码中没有两个和,因此没有不必要的冗余。 + + +那里有。以上功能是Arduino PID v1.2.0中提供的功能。 + +### 但是,等等,还有更多:设定值加权。 + +我没有在Arduino库代码中添加以下内容,但是如果您想自己动手,此功能可能会引起您的兴趣。设定点加权是其核心,它是同时具有PonE和PonM的一种方法。通过在0和1之间指定一个比率,您可以分别具有100%PonM,100%PonE或两者之间的某个比率。如果您的过程集成度不高(例如回流炉),并且需要考虑这一点,这将很有帮助。 + +最终,我决定此时不将其添加到库中,因为它最终成为要调整/解释的另一个参数,并且我认为由此带来的好处并不值得。无论如何,这是代码,如果您想修改代码以具有设定值权重,而不仅仅是选择PonM / PonE: + +现在,pOn不再是将pOn设置为整数的方式,而是提供了一个允许比率的双精度值(第58行)。除了一些标志(第62和63行)以外,还在第77-78行计算了加权Kp项。然后,在第37和43行上,将加权的PonM和PonE贡献添加到总体PID输出中。 diff --git a/improving-the-beginners-pid-introduction/README.md b/improving-the-beginners-pid-introduction/README.md new file mode 100644 index 0000000..9a9e73b --- /dev/null +++ b/improving-the-beginners-pid-introduction/README.md @@ -0,0 +1,69 @@ +# 改善初学者的`PID` + +## 简介 + +随着新版[`Arduino PID`库](http://www.arduino.cc/playground/Code/PIDLibrary)的发布,我决定写一个系列文章。上个版本的库虽然可靠,但并没有任何配套的代码说明。这次的目标是详细介绍代码为什么是这样实现。希望能对两类人有用: + +- 对于`Arduino PID`库内部运作感兴趣的人能获得详尽的解释。 +- 对于要自己编写`PID`算法的人可以了解我的实现方式,并能从中借用任何喜欢的部分。 + +这个目标口号会很艰难,但是我觉得找到了一种不太痛苦的方式来解释我的代码。我将从所谓的『初学者的`PID`』开始。然后逐步进行改进它,直到成为一个高效,强大的`PID`算法。 + +## 初学者的`PID` + +下面是每个人一开始就学习的`PID`方程: + +![](pid-algorithm.png) + +根据这个方程,我们几乎都会编写出像下面的`PID`控制器实现: + +```c +/*working variables*/ +unsigned long lastTime; +double Input, Output, Setpoint; +double errSum, lastErr; +double kp, ki, kd; +void Compute() +{ + /*How long since we last calculated*/ + unsigned long now = millis(); + double timeChange = (double)(now - lastTime); + + /*Compute all the working error variables*/ + double error = Setpoint - Input; + errSum += (error * timeChange); + double dErr = (error - lastErr) / timeChange; + + /*Compute PID Output*/ + Output = kp * error + ki * errSum + kd * dErr; + + /*Remember some variables for next time*/ + lastErr = error; + lastTime = now; +} + +void SetTunings(double Kp, double Ki, double Kd) +{ + kp = Kp; + ki = Ki; + kd = Kd; +} +``` + +`Compute()`被定期或不定期调用,并且运转得还行。不过,本系列文章目标可不是『运转得还行』。如果想要这段代码成为与工业级`PID`控制器相提并论的实现,还必须处理好一些关键问题: + +1. **[采样时间](sample-time)**:当`PID`算法按固定间隔定期调用执行时,算法的效果是最佳的。如果算法能知道采样间隔,还可以简化一些内部数学运算。 +2. **[微分冲击(`Derivative Kick`)](derivative-kick)**:并不是个最大的问题,但因为容易处理,所以我们先从这个问题入手。 +3. **[动态调整更改(`On-The-Fly Tuning Changes`)](tuning-changes)**:实现良好的`PID`算法可以更改调整参数且不会引发内部运行的颠簸震荡。 +4. **[减轻重置浪费](reset-windup)**:我们将研究什么是重置浪费,并实施具有附带利益的解决方案 +5. **[开/关(自动/手动)](onoff)**:在大多数应用中,有时需要关闭`PID`控制器并手动调整输出,而不会干扰控制器 +6. **[初始化](initialization)**:控制器首次打开时,我们需要“无扰动传输”。也就是说,我们不希望输出突然变得有些新的价值 +7. **[控制器指导](improving-the-beginners-pid-direction)**:这最后一个并没有改变鲁棒性本身。它旨在确保用户输入带有正确符号的调整参数。 +8. **[新增:测量比例](http://brettbeauregard.com/blog/2017/06/proportional-on-measurement-the-code/)**:添加此功能可以更轻松地控制某些类型的过程。 + +解决了所有这些问题后,我们将获得可靠的`PID`算法。我们也会(并非偶然)拥有Arduino `PID`库的最新版本中使用的代码。因此,无论您是尝试编写自己的算法,还是试图了解`PID`库中正在发生的事情,我都希望这对您有所帮助。让我们开始吧。 + +更新:在所有代码示例中,我都使用了双精度。在`Arduino`上,双精度与浮点数相同(单精度)。真正的双精度对于`PID`而言是过大的杀伤力。如果您使用的语言是真正的双精度,我建议将所有双精度更改为浮点型。 + +[下一个>>](1-sample-time.md) + diff --git a/improving-the-beginners-pid-introduction/introducing-proportional-on-measurement.md b/improving-the-beginners-pid-introduction/introducing-proportional-on-measurement.md new file mode 100644 index 0000000..87b9ecb --- /dev/null +++ b/improving-the-beginners-pid-introduction/introducing-proportional-on-measurement.md @@ -0,0 +1,67 @@ +# 介绍按比例测量 + +已经有一段时间了,但是我终于更新了[Arduino PID Library](https://github.com/br3ttb/Arduino-PID-Library)。我添加的功能几乎是一个未知的功能,但我认为这将是业余爱好的福音。它称为“测量比例”(简称PonM)。 + +### 为什么你应该关心 + +[![流程类型](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/process-types.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/process-types.png) + +那里有一些称为“集成过程”的过程。这些是pid输出控制输入变化率的过程。在工业中,这些仅占所有过程的一小部分,但在业余爱好中,这些人**无处不在**:真空,线性滑模和3D打印机挤出机温度都是此类过程的示例。 + +这些过程令人沮丧的是,使用传统的PI或PID控制时,它们会超出设定值。并非有时,但总是: + +[![PonE-9](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-9.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-9.png) + +如果您不知道,这可能会令人发疯。您可以永久调整调整参数,并且过冲仍然存在。基础数学使之成为现实。测量比例会改变基础数学。结果,有可能在不发生超调的情况下找到调整参数集: 仍然可以肯定会发生超调,但这不是不可避免的。借助PonM和正确的调整参数,可以轻松地将视频或线性滑行滑入设定点,而无需继续操作。 +[![庞9号](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-9.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-9.png) + +### 那么什么是测量比例? + +与[测量导数](http://brettbeauregard.com/blog/2011/04/improving-the-beginner%e2%80%99s-pid-derivative-kick/)类似,PonM更改比例项的内容。P项将输入PID输入的当前值,而不是错误。 + +错误比例: [![庞氏当量](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-eqn.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-eqn.png) + +测量比例: [![庞克](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-eqn.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-eqn.png) + +与“测量导数”不同,对性能的影响是巨大的。对于DonM,导数项仍然具有相同的作用:抵抗陡峭的变化,从而抑制P和I驱动的振荡。另一方面,“测量比例”从根本上改变了比例项的作用。它不是像I那样的驱动力,而是像D那样的阻力。这意味着对于PonM,更大的Kp将使您的控制器更加保守。 + +### 大。但这如何消除过冲呢? + +为了理解问题并进行修复,它有助于查看不同的术语以及它们对总体PID输出的贡献。这是使用传统PID对积分过程中设定值更改的响应(sous-vide): + +[![PonE组件](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-Components.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonE-Components.png) + +需要注意的两件事是: + +* 当我们处于设定点时,I项是整体输出的唯一贡献者。 +* 即使起点和终点的设定值不同,输出也会返回相同的值。该值通常称为“平衡点”:输出为0的输入斜率。对于副食品,这将仅对应于足够的热量以补偿对环境的热损失。 + +在这里,我们可以看到为什么会发生过冲,并且总是会发生。首次更改设定值时,出现的错误会导致I项增大。为了使过程稳定在新的设定点,输出将需要返回到平衡点。做到这一点的唯一方法是缩小I-Term。发生这种情况的唯一方法是产生负误差,仅当您高于设定点时才会发生。 + +### PonM改变了游戏 + +这是使用“按比例测量”控制的相同视频(以及相同的调整参数): + +[![PonM组件](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-Components.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/PonM-Components.png) +在这里您应该注意: + +* P术语现在提供抵抗力。输入越高,输入越负。 +* 在P项在新设定点变为零之前,它现在继续具有值。 + +P项不返回0的事实是关键。这意味着I项不必自己返回平衡点。P和I可以一起将输出返回到平衡点,而无需缩小I项。由于不需要缩小,因此不需要超调。 + +### 如何在新的PID库中使用它 + +如果您准备尝试“按比例测量”,并且已经安装了最新版本的PID库,则设置起来非常简单。使用PonM的主要方法是在重载的构造函数中指定它: + +[![建设者](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/Constructor.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/Constructor.png) + +如果您想在运行时在PonM和PonE之间切换,则SetTunings函数也将过载: + +[![SetTunings](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/SetTunings.png)](http://brettbeauregard.com/blog/wp-content/uploads/2017/06/SetTunings.png) + +您只需要在要切换时调用重载方法即可。否则,您可以使用常规的SetTunings函数,它将记住您的选择。 + + + + diff --git a/improving-the-beginners-pid-introduction/pid-algorithm.png b/improving-the-beginners-pid-introduction/pid-algorithm.png new file mode 100644 index 0000000..0d58df2 Binary files /dev/null and b/improving-the-beginners-pid-introduction/pid-algorithm.png differ diff --git a/improving-the-beginners-pid-introduction/s29837220.jpg b/improving-the-beginners-pid-introduction/s29837220.jpg new file mode 100644 index 0000000..e02fb6b Binary files /dev/null and b/improving-the-beginners-pid-introduction/s29837220.jpg differ