前言 近日刚接触PID,于是查找了所有资料(PID是经典算法,有很多文章),理解一大堆资料后,整理总结才写下在这篇博客。
自己也用STM32F103写些代码实现验证,先是单纯比例控制,后来PI控制,到最后PID控制,后续慢慢理解PID的改进及专家PID和模糊PID。
PID算法理解
首先,先理解PID指的是什么,PID即比例Proportion、积分Integral和微分Derivative的英文缩写。
这是算法的流程图。
接下来参考下面的博客,讲的已经很详细了,举了个例子方便理解PID。(比例部分的例子挺好的,讲的挺精彩的)
一文读懂PID控制算法(抛弃公式,从原理上真正理解PID控制)
(初学建议先看上面推文再继续阅读)
连续PID算法
先摆公式。
上面博客讲到了,不再赘述了。
PID算法离散化
在实际上用的最多的是,离散化的PID,也叫数字化PID。
将PID算法离散化,
离散化中,用K代替次数,表示第1,2,3…采样
偏差err(K)=设定值-实际值;
积分环节,连续用积分,离散用求和。即err(K)+err(K+1)+……;
积分时间,连续用周期T,离散用K表示(K是正整数);
微分环节,连续用微分,离散用差分。即[err(K)-err(K-1)];
离散化公式就变成:
Kp,Ki,Kd只是个系数,表现为比例、积分、微分环节的力度。
位置式PID
上面的式子就叫做位置式PID,它是根据你预期的设定位置,如速度在某个位置。
公式再贴一遍
增量式PID
增量式PID其实是位置式PID公式的变型
公式1:
在这里建议初学者,自己动手算一下,印象才会深刻。
按照上式后计算到下一步是,合并同类项,将所有的e(k)和e(k-1),e(k-2)合并起来,再将前面的系数修改为Kp,Ki,Kd得:
公式2:
注意:
1)公式1和2都是增量式PID,用哪个都行。
2)Kp,Ki,Kd只是系数,根据不同的控制系统设置不同的参数,公式照套,代码照搬,要变的是这些系数,PID的难点也在这些系数的设置,但是要先理解好PID的每个环节。下篇会讲到这些参数的整定。
3)由公式可以看出,增量式的表达结果和最近三次的偏差有关,这样就大大提高了系统的稳定性。需要注意的是最终的输出结果应该为
u(K)+增量调节值;
位置式和增量式的区别
(1)位置式PID控制的输出与整个过去的状态有关,用到了误差的累加值;而增量式PID的输出只与当前拍和前两拍的误差有关,因此位置式PID控制的累积误差相对更大;
(2)增量式PID控制输出的是控制量增量,并无积分作用,因此该方法适用于执行机构带积分部件的对象,如步进电机等,而位置式PID适用于执行机构不带积分部件的对象,如电液伺服阀。
(3)由于增量式PID输出的是控制量增量,如果计算机出现故障,误动作影响较小,而执行机构本身有记忆功能,可仍保持原位,不会严重影响系统的工作,而位置式的输出直接对应对象的输出,因此对系统影响较大。
(4)从嵌入式的角度分析,从C语言内存角度看,增量式不需要累加误差,而位置式累加误差会造成占用内存的增大,不利于程序长期运行,这点增量式更优。
位置式PID代码
第一步:定义PID变量结构体,代码如下
控制算法中所需要用到的参数在一个结构体中统一定义,方便后面的使用。
struct _pid{
float SetSpeed;
//定义设定值
float ActualSpeed; //定义实际值
float err;
//定义偏差值
float err_last;
//定义上一个偏差值
float Kp,Ki,Kd;
//定义比例、积分、微分系数
float voltage;
//定义电压值(控制执行器的变量)
float integral;
//定义积分值
}pid;
第二步:初始化变量,代码如下:
统一初始化变量,尤其是Kp,Ki,Kd三个参数,调试过程当中,对于要求的控制效果,可以通过调节这三个量直接进行调节。
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;
}
增量式PID代码
第一步
struct _pid{
float SetSpeed;
//定义设定值
float ActualSpeed; //定义实际值
float err;
//定义偏差值
float err_next;
//定义上一个偏差值
float err_last;
//定义最上前的偏差值
float Kp,Ki,Kd;
//定义比例、积分、微分系数
}pid;
第二步
void PID_init(){
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.err_next=0.0;
pid.Kp=0.2;
pid.Ki=0.015;
pid.Kd=0.2;
}
第三步
float PID_realize(float speed){
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
incrementSpeed=pid.Kp*(pid.err-pid.err_next)+pid.Ki*pid.err+pid.Kd*(pid.err-2*pid.err_next+pid.err_last);
pid.ActualSpeed+=incrementSpeed;
pid.err_last=pid.err_next;
pid.err_next=pid.err;
return pid.ActualSpeed;
}