详解PID控制器原理

一、P - Proportional 比例

想象一下一个全速行进的机器人,假设传感器上的值为1000。 现在,由于它的速度和惯性,它可能会超过一点, 当编写程序时,这可能是一个大麻烦,你想尽可能的准确。这个问题如图所示(x轴上的绿色标记代表理想距离):
在理想世界中,您告诉机器人在哪里停止,它就停止在哪里

但是,我们不是理想世界,如果我们突然告诉它停止,我们会有超调的问题,结果可能是这样的:

现在这个超调不会是一个问题,如果它的距离总是相同的。然而,有很多变量可以改变它超出的距离。 例如:

  • 电池电压。 如果电池电量不足,则电机不能快速运行,会有较少的惯性, 在这种情况下,机器人超调变小
  • 如果机器人碰到一些东西,那么超调会变小
  • 如果某些东西将机器人朝向想要行进的方向推动,则超调将会 变得更大

所以你可以看到,超调不好。 所以P控制器控制速度, 所以P控制器控制速度平稳地,让它在接近目标时减速,以缩小超调。 这就是为什么它被称为比例控制器 – 输出速度与要更改的值成比例,我们称之为误差(error)。

它是怎么做的?

你有一个很好的变量,叫做误差(error)。这将是根据这个值对应的传感器的读书,它还在变化,就像前文提到的。例如,误差可以是剩下要走的距离,剩下的要提升的高度,需要继续加热的温度,等等。
为了计算误差,我们只需减去传感器给我们的读数,这个读书我们希望传感器在完成后立即告诉我们。

误差 =(目标值) - (传感器读数)

error = (target value) – (sensor reading)

因此,通过给出一些示例值来说明你想要的距离和它实际走过的距离,你会看到当它接近目标时,误差会越来越小。

下面是几个示例值:

目标值 当前传感器读数 误差
1000 200 800
1000 400 600
1000 600 400
1000 800 200
1000 1000 0

我们可以用这个来控制应用程序的速度,如果你想用一个简单的P控制器,没有I和D项。为此,我们可以写:

error = (target value) – (sensor reading);
speed = error;

另外,误差值可能不太像我们想要的那样。 这个值可能太高了,所以它超标了很多。 如果过调,它会尝试和纠正过调(误差将变成负数),所以你可以在在调试器或窗口观察值和发生的情况,你将看到误差在振荡,超调然后过度校正。

或者,误差值太小,但通常不会遇到这个问题。

对于任一问题,您可以更改误差,以保持其比例因子,但你可以将错误乘以(或除)另一个数字,一个常数。 你可以称之为任何你喜欢的名字,但它通常被称为“Kp” – 比例组件的常数。

Kp = 0.5;
while (condition)
{
    error = (target value) – (sensor reading);
    speed = Kp * error;
}

二、I - Integral 积分

所以代码的比例部分已经得到了,所以剩下的误差是相当小的。比例太小,不能产生很大的差异。这就是积分。积分是之前的误差的总和。所以当你的误差非常小,积分起到作用,但它实际上如何工作的呢?

积分想要得到它,使其行进足够快以缩小误差,但不要太快,因为那样可能会有超调的风险.它通过慢慢加速的方式去决定走多快,积分可以想这样计算

积分=积分+误差* 时间增量

integral = integral + error*dT

以上的设置是这样的,所以“积分”的新值等于(等号左侧)
先前的“积分”值,加上(误差*时间增量)。忽略时间增量部分,我稍后再来讨论这个问题。
积分增加的方式可以如下表中所示(使用误差为2 做例子):

Cycle # Previous value for integral Error New value for integral
0 0 2 2
1 2 2 4
2 4 2 6
3 6 2 8
4 8 2 10

粗体的数字(New value for integral积分的新值)在不断增加.

那么我们如何将它添加到现有代码中呢? 我们把它加起来。所以现在的速度是:

speed = (Kp * error) + integral

那么现在加了积分伪代码就变成这样:

Kp = 0.5;
while (condition)
{
    error = (target value) – (sensor reading);
    integral = integral + error;
    speed = Kp*error + integral;
}

就像比例部分的代码一样,我们需要对积分加上一个常数项。我将使用0.2作为Ki的一个例子值,尽管和K一样,它只是一个数字我随便举例的。

Kp = 0.5;
Ki = 0.2;
while (condition)
{
    error = (target value) – (sensor reading);
    integral = integral + error;
    speed = Kp*error + Ki*integral;
}      

前文告诉过你忽略时间增量,但我现在就解释一下

Integral = integral + error*dT

增量时间是必须的,因为循环不会总是花费同样的时间完成每个周期,但是当每个周期花费同样的时间时, dT可以合并到Ki中,如果每个周期不花费同样的时间,只需要将代码用于每个周期,这样你可以使用这个周期的时间作为你的增量时间

在这个教程里,我们将假设周期之间的时间总是相同的,因此dT将被并入Ki。

这里是关于积分的几个问题:

问题1:

当你的误差几近于0时,你的积分可能任然是一个可以保持速度足够高保持错误的变化的值,方程只会自己达到0,如果它超过一个等于0 的误差,那么负误差就会减去现有的积分.所以,如果速度任然很高来保持误差,我们将面临一个问题,是吧?

对于这个题,有一个非常简单的解决方案,那就是在误差达到0时重新设置积分,如下所示

Kp = 0.5;
Ki = 0.2;
while (condition)
{
    error = (target value) – (sensor reading);
    integral = integral + error;
    if (error is 0)
    {
        integral = 0;
    }
    speed = Kp*error + Ki*integral;
}

问题2:

它被称为integral wind-up.它可以从一个大误差开始,一旦循环开始运行,积分将开始构建。所以,在这个积分需要的时候

使用时,它的值已经远远超过可用的值。有一些简单的解决办法, 我列举三个解决方案:

解决方案#1 –限制积分所能达到的值。如果太高,为什么不给它加个限度?一个限制可以写成如下:

if (integral is greater than or equal to the maximum value)
{
    integral = maximum value;
}

但是,如果积分太大,但它是负的形式(即,使速度相反)快速地,你需要重写和上面一样的,但是为负的版本的积分。

解决方案#2 - 限制积分允许建立的范围。因此,如果错误对于积分来说太大了,我们可以禁用该范围的积分。

if ( error is greater than useful for the integral )
{
    disable the integral (set the integral to 0);
}

但同样,就像在解决方案1中一样,你需要重写相同的但要是积分的负值。或者,如果您的编程语言支持使用一个绝对值的工具,您可以使用它使代码更短,也许更简单。
如何在代码中实现绝对值的工具:

if ( abs(error) is greater than useful for the integral)
{
    disable the integral (set the integral to 0);
}

解决方案#3–限制积分允许积累的时间。这样做有点复杂,但仍然可行。

对于本教程,我们将使用解决方案#2,因为它是最简短的.
适合计算积分的范围将为+/- 40,但只是一个随机数。 下面代码的完整版(如果剩下的话会被称为“PI控制“[比例积分控制])将是这样的:

Kp = 0.5;
Ki = 0.2;
while (condition)
{
    error = (target value) – (sensor reading);
    integral = integral + error;
    if (error = 0)
    {
        integral = 0;
    }
    if ( abs(error) > 40)
    {
        integral = 0;
    }
    speed = Kp*error + Ki*integral;
}

三、D - Derivative 导数

PID代码的最后一点——导数!导数的工作就是预测未来的误差价值,然后进行相应的速度行为。例如,如果它认为它会过调,会使它慢下来。

为了能够预测下一个误差,我们需要知道先前的误差,然后找到这两者的区别。
derivative = ( (current error) – (previous error) ) / dT

这个公式将发现当前误差和之前的误差之间的变化,然后我们可以通过将其添加到当前误差中来预测下一个误差。就像积分一样,导数是由dT影响的,但是一个循环的周期话费的时间总是相同的,dT可以合并到Kd。

这里有一个表,展示了未来可能发生的误差的例子,通过导数计算的:

Current error Previous error Next error (error + derivative)
50 55 45
20 30 10
2 3 1
5 15 -5

在我们的代码中,导数是用上面的方程计算出来的,然后加到速度上(在这里也乘以Kd来达到缩放的目的)。

我们需要创建一个新的整数,来命名先前的误差(或者任何一个你喜欢的方式,只要它表示之前的误差值),我们让它更新自己在我们我们计算完导数后。我们可以用简单的方式让它更新,设置它的值为误差值。

一下是完整的控制PID的伪代码:

Kp = 0.5;
Ki = 0.2;
Kd = 0.1;
while (condition)
{
    error = (target value) – (sensor reading);
    integral = integral + error;
    if (error = 0)
    {
        integral = 0;
    }
    if ( abs(error) > 40)
    {
        integral = 0;
    }
    derivative = error – previous_error;
    previous_error = error;
    speed = Kp*error + Ki*integral + Kd*derivative;
}

四、调整常数项

这是费时又费力的工作。 有很多不同的方法来调整Kp,Ki和Kd,我会尽我所能地解释一下他们。 调整PID常数可以通过计算机程序完成,通过数学计算或通过手动调整, 我强烈建议您随时查看误差,速度等等,所以你可以看到距离到达目标还剩下多少需要改变。 使用调试器或类似的监视工具来检查结果。

首先,了解调节PID控制器的规则很重要。 当每个常数增加时有什么改变如下表所示。 常数 术语在左侧的列中,并且它们具有的效果在顶行列出。

效果如下:

  • Rise time - 从起点到目标点所需的时间
  • Overshoot - 改变的量太大了; 值比错误更大
  • Settling time - 遇到变化时需要解决的时间
  • Steady-state error - 均衡时的误差
  • Stability - 速度的“平滑度”

当每个常数增加时会发生什么?

Constant: Rise time: Overshoot: Settling Steady-state Stability: Stability:
Kp decrease increase Small change decrease degrade
Ki decrease increase increase decrease degrade
Kd minor change decrease decrease No effect Improve (if small enough)

手工调优:

手动调优是完全由你自己完成的——没有涉及到数学,但有时也会有些低效。我个人使用手动调优方法,因为我可以温和地增加每一个常量,并且知道什么时候会变得太高,而像ziegler - nichols方法这样的数学方法,你永远不会知道事情会怎样发展,直到你尝试之后。毕竟,在理论上,实践和理论是一样的,但在实践中,它们不是。我调整常量的方式如下:

1.将Kp、Ki和Kd设置为0。这将使他们暂时瘫痪。

2.增加Kp直到误差相当小,但是它仍然从开始到结束足够快。

3.增加Kd,直到任何超过你可能拥有的覆盖。但是小心Kd——太多会使它过度

4.增加Ki,直到任何仍然存在的错误被消除。从一个非常小的数字开始,不要惊讶,如果它小到0.0001甚至更小。

5.使用调整常量的规则(在上一页的表格中),您可以稍微更改一些常量,以使其工作到最佳性能。

数学方式

@todo 试过再补充

工具方式

@todo试过再补充

五、补充

while循环:

您可能已经注意到,在我的所有伪代码示例中,我将主代码放在while循环中。 这是因为误差变化时需要重新计算误差,积分,微分和速度。 例如,如果您将速度计算一次,但不再次计算,则无法重新刷新并相应地更改速度 - 它将以原始速度继续运行!

循环对PID控制器至关重要 - 不要忘记添加一个!

那么,你如何让它最终退出循环,每次当它完成了它的工作?

其中的一个常见的方法是,如果你知道它需要多长时间才能完成,你 可以将循环设置为指定的时间量(但显然比它需要的稍微多一点),以确保它确实完成了循环。

另一种方法是检测一旦误差达到零,并且已经完成。如果你选择这种方式,一定要注意确保它已经完全停止。举个例子,如果你告诉它运行循环直到误差达到0,如果有任何过度,没什么能做的,因为它将会停止(过度,错误必须通过一个点(0)。所以,你可以得到循环多长时间内误差保持在0。如果它只是在一个非常短的时间内处于0,那么很有可能它已经被过度并且需要重新调整自己。或者,如果它在一段较长时间内保持在0的值,那么说它已完全停止是安全的。

重新设置积分和之前的错误:

我前面已经讲过了,有时你需要把积分重置为0,但是最后一次需要0的值。当您开始循环时,代码会自动假设这个积分是0,之前的误差是它应该的值。但是,如果循环已经运行,那么这个积分的值和之前的错误仍然是原来的值。

这可以通过在循环开始之前设置积分值和前一个错误的值来确定。

六、总结

Proportional-你的误差,在真实值和预期值之间。

​ error = (target value) – (sensor reading)

Integral – 先前误差的运行和,用于在误差很小的时候进行精细的调整。

​ integral = integral + error*dT

Derivative – 误差的变化,用来预测下一个误差可能是什么。

​ derivative = ( (current error) – (previous error) ) / dT

The loop – 所有的计算都需要在循环中运行-不要忘记包含它!

将三个组件放在一起,再加上一些对Kp、Ki和Kd的精确值,您将拥有一个非常一致和精确的控制器。

七、调试口诀

参数整定找最佳,从小到大顺序查,先是比例后积分,最后再把微分加,
曲线振荡很频繁,比例度盘要放大,曲线漂浮绕大湾,比例度盘往小扳,
曲线偏离回复慢,积分时间往下降,曲线波动周期长,积分时间再加长,
曲线振荡频率快,先把微分降下来,动差大来波动慢,微分时间应加长,
理想曲线两个波,前高后低4比1

八、具体方法

(1)确定比例系数Kp

确定比例系数Kp时,首先去掉PID的积分项和微分项,可以令Ti=0、Td=0,使之成为纯比例调节。输入设定为系统允许输出最大值的60%~70%,比例系数Kp由0开始逐渐增大,直至系统出现振荡;再反过来,从此时的比例系数Kp逐渐减小,直至系统振荡消失。记录此时的比例系数Kp,设定PID的比例系数Kp为当前值的60%~70%。

(2)确定积分时间常数Ti

比例系数Kp确定之后,设定一个较大的积分时间常数Ti,然后逐渐减小Ti,直至系统出现振荡,然后再反过来,逐渐增大Ti,直至系统振荡消失。记录此时的Ti,设定PID的积分时间常数Ti为当前值的150%~180%。

(3) 确定微分时间常数Td

微分时间常数Td一般不用设定,为0即可,此时PID调节转换为PI调节。如果需要设定,则与确定Kp的方法相同,取不振荡时其值的30%。

(4) 系统空载、带载联调

对PID参数进行微调,直到满足性能要求。

以上就是详解PID控制器原理的详细内容,更多关于PID控制器的资料请关注我们其它相关文章!

(0)

相关推荐

  • c# 实现模糊PID控制算法

    跑起来的效果看每个类的test方法,自己调用来测试 目的是看看哪个算法好用,移植的时候比较单纯没有研究懂算法,代码结构也没改动,只是移植到C#方便查看代码和测试,大家要拷贝也很方便,把整个类拷贝到.cs文件即可 这段算法在实际值低于目标值是工作正常,超过后会有问题,不知道如何调教 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Tex

  • python实现PID算法及测试的例子

    PID算法实现 import time class PID: def __init__(self, P=0.2, I=0.0, D=0.0): self.Kp = P self.Ki = I self.Kd = D self.sample_time = 0.00 self.current_time = time.time() self.last_time = self.current_time self.clear() def clear(self): self.SetPoint = 0.0 s

  • 详解PID控制器原理

    一.P - Proportional 比例 想象一下一个全速行进的机器人,假设传感器上的值为1000. 现在,由于它的速度和惯性,它可能会超过一点, 当编写程序时,这可能是一个大麻烦,你想尽可能的准确.这个问题如图所示(x轴上的绿色标记代表理想距离): 在理想世界中,您告诉机器人在哪里停止,它就停止在哪里 但是,我们不是理想世界,如果我们突然告诉它停止,我们会有超调的问题,结果可能是这样的: 现在这个超调不会是一个问题,如果它的距离总是相同的.然而,有很多变量可以改变它超出的距离. 例如: 电池

  • 详解Redis复制原理

    前言 本文主要介绍Redis复制机制 一.配置与实践 配置 Redis实例分为主节点(master)和从节点(slave),默认情况下都是主节点.每一个从节点只能有一个主节点,但是每一个主节点可以有多个从节点(注意数量,多个从节点会导致主节点写命令多次发送从而过度消耗网络带宽,可用树状结构降低主节点负载).复制是单向的,只能从主节点复制到从节点.配置复制的方式由以下3种: 在redis-slave.conf配置文件中加入slaveof {masterHost} {masterPort} 在red

  • 详解编译器编译原理

    详解编译器编译原理 什么是gcc  什么是gcc:gcc是GNU Compiler Collection的缩写.最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C.C++.Java.Pascal.Ada.COBOL语言等. gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的支持 gcc主要特征  1)gcc是一个可移植的编译器,支持多种硬件平台 2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译. 3)gcc

  • 详解Nginx 工作原理

    Nginx工作原理 Nginx由内核和模块组成. Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者.通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块).handler模块负责处理请求,完成响应内容的生成,而filter模块对

  • 详解@ConfigurationProperties实现原理与实战

    在SpringBoot中,当需要获取到配置文件数据时,除了可以用Spring自带的@Value注解外,SpringBoot提供了一种更加方便的方式:@ConfigurationProperties.只要在bean上添加上这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到bean中.举个栗子,现在有如下配置: myconfig.name=test myconfig.age=22 myconfig.desc=这是我的测试描述 添加对应的配置类,并添加上注解@Configuratio

  • 详解Unique SQL原理和应用

    1.什么是Unique SQL 用户执行SQL语句时,每一个SQL语句文本都会进入解析器(Parser),生成"解析树"(parse tree).遍历解析树中各个结点,忽略其中的常数值,以一定的算法结合树中的各结点,计算出来一个整数值,用来唯一标识这一类SQL,这个整数值被称为Unique SQL ID,Unique SQL ID相同的SQL语句属于同一个"Unique SQL". 例如,用户先后输入如下两条SQL语句: select * from t1 where

  • 详解MyBatis工作原理

    一.Mybatis工作原理 Mybatis分层框架图 Mybatis工作原理图 源码分析:一般都是从helloworld入手 1.根据xml配置文件(全局配置文件mybatis-config.xml)创建一个SqlsessionFactory对象,mybatis-config.xml有数据源一些环境信息 2.sql映射文件EmployeeMapper.xml配置了每一个sql,以及sql的封装规则等. 3.将sql映射文件注册在全局配置文件中 4.写代码: 根据全局配置文件得到sqlsessio

  • 详解Vue数据驱动原理

    前言 Vue区别于传统的JS库,例如JQuery,其中一个最大的特点就是不用手动去操作DOM,只需要对数据进行变更之后,视图也会随之更新. 比如你想修改div#app里的内容: /// JQuery <div id="app"></div> <script> $('#app').text('lxb') </script> <template> <div id="app">{{ message }

  • 详解Python字符串原理与使用的深度总结

    目录 什么是 Python 字符串 ASCII 表与 Python 字符串字符 字符串属性 字符串方法 字符串操作 写在最后 今天我们来学习字符串数据类型相关知识,将讨论如何声明字符串数据类型,字符串数据类型与 ASCII 表的关系,字符串数据类型的属性,以及一些重要的字符串方法和操作,超级干货,不容错过! 什么是 Python 字符串 字符串是包含一系列字符的对象.字符是长度为 1 的字符串.在 Python 中,单个字符也是字符串.但是比较有意思的是,Python 编程语言中是没有字符数据类

  • 详解Dijkstra算法原理及其C++实现

    目录 什么是最短路径问题 Dijkstra算法 实现思路 案例分析 代码实现 什么是最短路径问题 如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小. 单源最短路径问题是指对于给定的图G=(V,E),求源点v0到其它顶点vt的最短路径. Dijkstra算法 Dijkstra算法用于计算一个节点到其他节点的最短路径.Dijkstra是一种按路径长度递增的顺序逐步产生最短路径的方法,是一种贪婪算法. Dijkstra算法

随机推荐