PID算法详解及实例分析

1.PID算法入门

PID算法算是控制领域最经典,最重要,也是最实用的算法了。所谓的PID,指的是proportion,integration,differentiation,比例,积分,微分。
因此,PID是结合了比例积分微分三个模块于一身的控制算法。

先看公式:
u ( t ) = K p ( e ( t ) + 1 T i ∫ 0 t e ( t ) d t + T d d e ( t ) d t ) u(t) = K_p\left(e(t) + \frac{1}{T_i} \int_{0}^{t}e(t)dt + T_d \frac{de(t)}{dt}\right) u(t)=Kp(e(t)+Ti10te(t)dt+Tddtde(t))
如果公式看不懂,没关系,我们先往后面走,回头再分析公式。

2.通过实例对PID进行理解

为了更好了解PID算法,我们选取一个例子进行分析,这个例子在很多地方被使用,我们也选过来作为例子进行分析。

小明现在有一个任务:有个水桶,水桶的水位高度需要时刻保持1m,目前水桶的水是0.2m,小命采用比例的方式加水(即P),即每次测量与1m 的误差,并加入与误差成比例的水量。

K p = 0.5 K_p = 0.5 Kp=0.5
第一次的误差:1-0.2=0.8,于是加入的水为0.50.8=0.4,此时桶内水0.6。
第二次的误差:1-0.6=0.4,于是加入的水为0.5
0.6=0.3,此时桶内水0.9。
第三次的误差:1-0.9=0.1,于是加入的水为0.5*0.1=0.05,此时桶内水0.95。
以此类推,不断加下去,通过P控制就可以将水加满到1,完美!

3.积分环节

上面的比例环节,貌似就可以解决问题。但是实际中没有这么理想的情况,比如水桶有个洞,每次加水都会流出0.1m。这个时候就比较接近真实情况了,比如系统的各种摩擦力,阻力什么的。

如果我们还是用上面的比例控制(P环节)
第一次的误差:1-0.2=0.8,于是加入的水为0.50.8=0.4,此时桶内水0.6-0.1=0.5。
第二次的误差:1-0.5=0.5,于是加入的水为0.5
0.5=0.25,此时桶内水0。5+0.25-0.1=0.65。
第三次的误差:1-0.65=0.35,于是加入的水为0.5*0.35=0.175,此时桶内水0.65+0.175-0.1=0.725。

我们多算几次以后发现,水位最终会在0.8m处稳定。如果反推一下,我们也很好理解:当误差为0.2m时,每次加水量为0.1,漏掉的也是0.1,正好加的等于漏掉的。

这就是系统稳定误差的概念。

关于系统稳态误差,引用胡寿松老爷子自动控制原理一书中的描述:

控制系统的稳态误差,是系统控制准确度的一种度量,通常称为稳态性能。在控制系统设计中,稳态误差是一项重要的技术指标,对于一个实际的控制系统,由于系统结构,输入作用的类型(控制量或扰动量) ,输入函数的形式(阶跃,斜坡或加速度)不同,控制系统的稳态输出不可能在任何情况下与输入量一致,也不可能在任何形式的扰动作用下都能准确地恢复到平衡位置。此外,控制系统中不可避免的存在摩擦,间隙,不灵敏区,零位输出等非线性因素,都会造成附加稳态误差。因此,控制系统的稳态误差是不可避免的,控制系统设计的任务之一,就是尽量减小系统的稳态误差,或者使稳态误差小于某一容许值。显然,只有当系统稳定时,研究稳态误差才有意义。对于不稳定系统而言,根本不存在研究稳态误差的可能性。

为了消除稳态误差,我们的做法就是引入积分项,就是PID中的I,积分控制就是将历史误差累加起来再乘以积分常数,即
1 T i ∫ 0 t e ( t ) d t \frac{1}{T_i} \int_{0}^{t}e(t)dt Ti10te(t)dt

前面的例子,我们还是设置为Kp=0.5,Ki= 0.3。
第一次,误差为0.8, 比例部分 Kp0.8=0.4, 积分部分 Ki(e(1))= 0.24,加入水量u为0.4+0.24=0.64. 最终水位0.2+0.64-0.1= 0.74m
第二次,误差为0.26,比例部分Kp0.26=0.13,积分部分Kp*(e(1)+e(2))= 0.318,加入水量u为 0.13+0.318=0.448.最终水位 0.74+0.448-0.1=1.088m。

如果这样一直下去,最终会到达一个稳定值。

4.微分环节

前面我们已经分析了,积分环节能消除稳态误差,但是积分环境又会带来另外一个问题:积分环节会带来超调量。而且随着Ki值的变大,超调量也会边大。
所谓的超调量,是指峰值超过终止的比例。从直观上来说,超调量对应的对就是波峰位置。波峰越高,超调量越大。

为了消减超调,我们引入微分运算,也就PID中的D。
上面的例子,我们假设Kp=0.5,Ki= 0.5,Kd=0.3。

第一次: 误差为0.8, 比例部分 Kp0.8=0.4, 积分部分 Ki(e(1))= 0.24,微分部分 =0 (因为没加水前水位差就是0.8) 加入水量u为0.4+0.4=0.8. 最终水位0.2+0.8-0.1= 0.9m

第二次: 误差为0.1,比例部分Kp0.1=0.5,积分部分Kp(e(1)+e(2))= 0.45,微分部分为Kd*(e(2)-e(1))加入水量u为 0.5+0.45-0.21=0.29.最终水位 0.9+0.29-0.1=1.09m

最后我们计算发现,引入微分运算以后,超调量比之前有减小。

5.PID各模块小结

比例部分P:
比例环节的作用是对偏差瞬间作出反应。偏差一旦产生控制器立即产生控制作用, 使控制量向减少偏差的方向变化。 控制作用的强弱取决于比例系数Kp, 比例系数Kp越大,控制作用越强, 则过渡过程越快, 控制过程的静态偏差也就越小; 但是Kp越大,也越容易产生振荡, 破坏系统的稳定性。 故而, 比例系数Kp选择必须恰当, 才能过渡时间少, 静差小而又稳定的效果。

积分部分I:
从积分部分的数学表达式可以知道, 只要存在偏差, 则它的控制作用就不断的增加; 只有在偏差e(t)=0时, 它的积分才能是一个常数,控制作用才是一个不会增加的常数。 可见,积分部分可以消除系统的偏差。
积分环节的调节作用虽然会消除静态误差,但也会降低系统的响应速度,增加系统的超调量。积分常数Ti越大,积分的积累作用越弱,这时系统在过渡时不会产生振荡; 但是增大积分常数Ti会减慢静态误差的消除过程,消除偏差所需的时间也较长, 但可以减少超调量,提高系统的稳定性。

微分部分D:
实际的控制系统除了希望消除静态误差外,还要求加快调节过程。在偏差出现的瞬间,或在偏差变化的瞬间, 不但要对偏差量做出立即响应(比例环节的作用), 而且要根据偏差的变化趋势预先给出适当的纠正。为了实现这一作用,可在 PI 控制器的基础上加入微分环节,形成 PID 控制器。
微分环节的作用使阻止偏差的变化。它是根据偏差的变化趋势(变化速度)进行控制。偏差变化的越快,微分控制器的输出就越大,并能在偏差值变大之前进行修正。微分作用的引入, 将有助于减小超调量, 克服振荡, 使系统趋于稳定, 特别对髙阶系统非常有利, 它加快了系统的跟踪速度。但微分的作用对输入信号的噪声很敏感,对那些噪声较大的系统一般不用微分, 或在微分起作用之前先对输入信号进行滤波。

6.系统稳定性判断

前面提到了系统稳定性的问题,顺便我们复习一下系统稳定性判据。
同样来自胡寿松老爷子自动控制原理一书

线性系统稳定的充分必要条件是:闭环系统特征方程的所有跟具有负实部,或者说,闭环传递函数的极点均位于s左半平面。

劳斯-赫尔维茨稳定性判据:
根据稳定的充分必要条件判别线性系统的稳定性,假设线性系统的特征方程为
D ( s ) = a 0 s 0 + a 1 s 1 + a 2 s 2 + ⋯ + a n − 1 s n − 1 = 0 , a 0 > 0 D(s) = a_0s^0 + a_1s^1 + a_2s^2 +\cdots + a_{n-1}s^{n-1} = 0, a_0 > 0 D(s)=a0s0+a1s1+a2s2++an1sn1=0,a0>0

即线性系统稳定的必要条件是,在特征方程中,各项系数均为正数。

7.PID的简单实例实现

假设我们的采样间隔为T,那么在第K个T时刻:
偏差err(K) = rin(K) - rout(K)
积分环节用加和的方式表示, err(1) + err(2) + … + err(K)
微分的环节则用斜率的形式表示, [err(K) - err(K-1)] / T

#include 

using namespace std;

struct _pid{
    float SetSpeed; //定义设定值
    float ActualSpeed; //定义实际值
    float err; //定义偏差值
    float err_last; //定义上一个偏差值
    float Kp,Ki,Kd; //定义比例、积分、微分系数
    float voltage; //定义电压值(控制执行器的变量)
    float integral; //定义积分值
}pid;

void PID_init(){
    printf("PID_init begin \n");
    pid.SetSpeed=0.0;
    pid.ActualSpeed=0.0;
    pid.err=0.0;
    pid.err_last=0.0;
    pid.voltage=0.0;
    pid.integral=0.0;
    pid.Kp=0.2;
    pid.Ki=0.015;
    pid.Kd=0.2;
    printf("PID_init end \n");
}

float PID_realize(float speed){
    pid.SetSpeed=speed;
    pid.err=pid.SetSpeed-pid.ActualSpeed;
    pid.integral+=pid.err;
    pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
    pid.err_last=pid.err;
    pid.ActualSpeed=pid.voltage*1.0;
    return pid.ActualSpeed;
}

int run_pid(){
    printf("System begin \n");
    PID_init();
    int count=0;
    while(count<1000) {
        float actual_speed=PID_realize(200.0);
        printf("count is: %d, actual_speed is: %f\n", count, actual_speed);
        count++;
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    run_pid();
    return 0;
}

代码的最终输出为

System begin 
PID_init begin 
PID_init end 
count is: 0, actual_speed is: 83.000000
count is: 1, actual_speed is: 11.554998
count is: 2, actual_speed is: 59.559681
count is: 3, actual_speed is: 28.175407
count is: 4, actual_speed is: 52.907417
count is: 5, actual_speed is: 38.944157
...
count is: 996, actual_speed is: 199.999435
count is: 997, actual_speed is: 199.999451
count is: 998, actual_speed is: 199.999466
count is: 999, actual_speed is: 199.999481

上述代码,模拟的就是通过PID算法,将速度最终控制在200的场景。

参考文献

1.https://zhuanlan.zhihu.com/p/74131690
2.https://www.cxyzjd.com/article/weibo1230123/80812211
3.https://zhuanlan.zhihu.com/p/41425508

你可能感兴趣的:(autodrive,PID算法,比例微分积分,劳斯判据,自动控制原理)