上面给出的是PID控制算法的整个框图,以及根据框图所给出的公式。下面是结合蒸汽刷的实例子进行说明PID控制。
首先需要说明的是PID是一个闭环控制,也就是说一定要有个反馈通路在那,还有就是对于系统存在滞后性、稳定误差,PID可以达到较好效果,具体原因还要从PID三个参数说起,为了贴近项目。这里先将框图中具体参数具体意义和蒸汽刷实物相对。
蒸汽刷项目中,r(t)为当前设定的温度,c(t)为当前输出温度,e(t)为当前设定温度与当前输出温度的差值,u(t)为温度的控制量,也就是加热的脉冲数,执行机构为可控硅,对象就是蒸汽刷了。好了,有了实际的意义,可以讨论PID三个参数了。
P(Proportion)比列系数:P系数实际就是公式中的那个Kp,纯P作用就是u(t)=Kp*e(t),单纯的就P作用就很好理解了,看设定温度与当前温度差值e(t),不够就加控制量,够了就减小,具体加多大或者减多少,那就要看Kp了,温差e(t)一定时,Kp大了给的控制量就大,试想一下,当Kp无穷大时,给的控制量就无穷大,显然系统是很难稳定的,当Kp无穷小时,系统控制量也无穷小,系统也就没什么变化,升温几乎不可能,这也侧面反应了Kp显然不是越大或越小越好。这里假设我们Kp已经取的合适,这里暂就假设为5,设定温度r(t)为150,为防止控制量过大,这里限定一下上限值为50,刚开始时,e(t1) = (150-0)=150,
u(t1) = 150*50>50 u(t1)控制量就为50,继续加热t2……tn,当温差为13左右时候,控制量就开始逐渐小于50了,这个时候也还很大,为48,…..当温差为1度时候,控制量就一直在5范围内波动了,这就了不得了,温度假设现在是149,误差为+5那下个阶段就要5的控制量,这个时候温度就有可能达到155, 在155时候又发现温度高了,误差为-5那就降下来控制量为0,降到149时候,控制量又为5,温度又会上升到155,如此反复。你会发现温度始终都不能稳定下来,始终在一个范围内波动。要是在考虑这个系统的滞后性,也就是说这个控制量给到了发热体,发热体发热是需要时间的,可能要下两个时刻温度才会升,那可能就会出现,那边温度还没升完,我这边又给控制量,这样一来温度一定过冲更加严重。当然了,看到这里,肯定会想到,前面这个Kp或许太大了,那我减小一点不就行了,是的,减小确实会有效果,至少温度到后面稳定的波动不会太大,但Kp小了,也就意味着控制量小了,对吧,这时候升温时间可就长了,Kp越小,升温时间就越久,可总不能这么一直等下去呀,人们等待时间总是有限的。进一步的,就会发现,那我就刚开始让它控制量大些,等到快稳定时候再让它控制量小下来,不错,这就会引申出我们下面讲到的D参数。
D(Differential)微分系数:微分系数是公式中的那个Kd,微分作用部分也就是Kd(de(t)/dt) ,由于上面讲的那个P一个人不能解决问题,我们就找了一个叫D的人过来帮忙了,两人联手,公式这时也变为了u(t)= Kp*e(t)+ Kd(de(t)/dt),既然D过来帮忙了,那我们先介绍一下D的绝招,D擅长将过去与现在联系一起,预料到后面事态的发展,可以说是半个预言家吧。也就是说它主要是辅助P来解决问题,P负责冲锋,D负责辅助。清楚了各个职责后,让我们接着上面讲的蒸汽刷控温的继续讲下去。这时候我们需要把眼光放的长远一些,e(t)也就是温差,我们上面看的时候都是单个时刻去看的,这时候我们假设把e(t)的变化都绘制出来,也就是将所有时刻都连线连起来看的话,就会发现,e(t)是一个随着时间递减的曲线,后面在0范围内上下波动,刚开始时候e(t1)=150,紧接着e(t2)=130……e(tn) = 50.这个时候一定会想到这个曲线究竟是怎样的,目前看来我们只能靠猜测,但有一点可以肯定的是就是这个曲线一定是一个斜率小于0的,而且斜率有可能在不断变化,看,我们想到了什么,斜率,斜率就是变化,那我们是否可以用它来告诉我们控制量什么时候大些,什么时候小些呢。由于我们在实际过程中,整个温度变化不可能是瞬间产生的,一定是缓慢的,这时候只要采集温度时间足够短的话,那完全可以通过现阶段的斜率预想到下个时刻的发生。D开始出场了,这个时候Kd我们假设取8,刚开始,Kd不起作用因为没法预测都没两个点产生也没有所谓的斜率,u(t1)=5*(150-0),第一个时刻当前温度为0度,u(t1)为50,第二个时刻:Kd开始作用,当前温度为20度, de(t)/dt =(130-150)/(t2-t1),这里我们每1S检测一次,所以de(t)/dt=20 u(t2)=5*(150-20) +0.8*(130-150)>50 = 50, 温度不断上升,当温差当前为10时,前一个时刻为13,也就是当前温度为140度时,u(t2)=5*(150-140) +0.8*(10-13)=50-2.4= 47,本来没有微分作用时候这时候应该为50,现在为47,再下个时刻,温差为5,则u(t2)=5*(150-145)+0.8*(5-10)=21,再下个时刻,当温差为正1时候, u(t2)=5*(150-149)+0.8*(1-5)<0=0,不给控制量,当温差为负1时候,u(t2)=5*(150-151)+0.8*(-1-5)<0=0,不给控制量,可以看出温度有明显的改善,稳定度大大的提高。但也不要过于开心,仔细观察会发现,我们现在的整个升温是理想化的,也就是说并没有考虑任何散热啊,什么的。拿出我们前面升温一个状态看一下,这个时刻我们贴近实际,假设这时候散热是变化的,具体散热的量我们对应到控制量上,当温差当前为10时,前一个时刻为13,也就是当前温度为140度时,u(t2)=5*(150-140) +0.8*(10-13)=50-2.4= 47,这时候散热为15,则实际相当于32的控制量用于升温,下个时刻温度为145,温差为5,则u(t2)=5*(150-145)+0.8*(5-10)=21,这时候散热为16,再下个时刻温度为146,温差为4,则u(t2)=5*(150-146)+0.8*(4-5)=19.2, 这时候散热为19,温度为146,温差为4,那会出现什么情况,很明显温度不会往上升了,这时候给的控制量都用于了散热。这对于温度要求精度不高的话,好像是可以了,但是如果一个应用,比方说开水啥的,就是要达到100度,少1度都不行,那P和D就不够用了,这时候要是再来一个量,在什么时候温度提不上的时候,也就是误差始终不为0,能够再增加一下控制量就好办了。在理一下思路,误差不为0,上面那个加热的误差差不多一直在5左右,究竟什么时候增加一下控制量,那肯定是误差加到什么时候,注意这个加,累加就是积分,那就是积分到多少的问题,这就引申到我们下面最后要讲的积分了。
I(integral)积分系数:积分系数是公式中的那个Ki,积分作用部分为Ki* ,这个公式中是从0到t开始积分的,现在我们将这个三个兄弟集结在一起,公式就变成了这样:
u(t)= Kp*e(t)+ Kd(de(t)/dt)+ Ki* ,这个时候还是接着我们上面的例子开始讲起来,Kp和Kd都不变,Kp为5,Kd为0.8,Ki为2.0,刚开始时候,u(t)=5*(150-0)+0+2*(150-0)>50=50,
下一个时刻温度上升到20度,u(t)=5*(150-0)+0.8(130-150)+2*(150-0+130-0)>50=50,这样温度不断上升,假设当温差当前为10时,前一个时刻为13,也就是当前温度为140度时,u(t2)=5*(150-140) +0.8*(10-13) +2*(150-0+130-0 +….)=50-2.4+ …>50,这时候控制量还是保持50,容易导致超温,而且随着时间拉长,积分量可能只会越来越大,这就容易导致一种称为积分饱和的现象,因此一般在使用积分时候,都会给积分加限制条件,比如最大值,或最小值,这样即使超温,还是能够靠微分给拉回平衡。另外一点也容易想到,就是刚开始温差大,本来P作用就很大了,这时又加了I进来,可能会导致温升过于剧烈,所以一般情况下是在最好快接近目标温度时候,选择加入积分作用,也就是,积分时机很关键,这里我把积分I比作刺客,P是战士,D是辅助,D辅助战士发动进攻,等到敌人与战士僵持不下时候,刺客这时出手,争取一击必杀。
理清了PID系数后,接下来就开始实际操作了,第一步就是对函数进行离散化处理,计算机只能处理数字量,所以为便于计算,下面将整个函数离散化表示如下:
上面的离散表达式中为tn时刻的控制量,在ts时刻加入积分控制。
软件代码:
第一步:定义PID变量结构体
struct _pid{
float Err; //------定义偏差值
float ErrLast; //------定义上一个偏差值
float Kp,Ki,Kd; //------定义比列、积分、微分值
unsigned char ControlDuty; //------定义控制量
float Proportion; //------定义PID比列部分
float Interal; //-------定义PID积分部分
float Differential; //-------定义PID微分部分
}Pid;
#define Integral_max 15 //-----定义积分上限值
#define Integral_min -15 //-----定义积分下限值
#define Integral_time 100 //-----定义积分加入时机
#define ControlDuty_Max 50 //-----定义控制量最大值不超过50
extern float SetTemperature; //------设定温度值
extern float ActualTemperature; //------实际测得温度值
第二步:初始化PID参数
void PID_Init(void)
{
Pid.Err = 0;
Pid.ErrLast = 0;
Pid.Kp = 10;
Pid.Ki = 2;
Pid.Kd = 0.8;
Pid. ControlDuty = 0;
Pid. Proportion = 0;
Pid. Differential = 0;
Pid. Interal = 0;
}
第三步:编写控制算法
void PID_Ctrl(void)
{
Pid.Err = SetTemperature - ActualTemperature;
Pid. Proportion = Err;
If(ActualTemperature !=0)
Pid. Differential = (Err - ErrLast);
If(ActualTemperature >= Integral_time)
Pid .Interal = Pid .Interal + Err;
If(Pid .Interal >15)
Pid .Interal = Integral_max;
If(Pid .Interal <-15)
Pid .Interal = Integral_min;
Pid.ErrLast = Pid.Err;
ControlDuty = (unsigned char)( Pid.Kp * Pid. Proportion + Pid.Ki * Pid .Interal + Pid.Kd* Pid. Differential);
If(ControlDuty > ControlDuty_Max)
ControlDuty = 50;
}