1,PID算法简介
PID算法是当下应用最广泛的控制算法,它具有结构简单,易于理解,控制效果直观,参数较为整定简单,变形多样等优点,更有甚者称之为万能控制算法。
但是,严格意义上讲,PID算法仅仅对线性系统,或近似线性系统,或系统常处于线性段的系统能够取得良好的控制效果。
(线性系统判断依据——满足叠加原理)
那么,对于一个非线性的控制,我们常使用一些PID算法的变形,比如积分分离式PID,微分线性PID,带低通滤波的PID等等。
在这里,提出一种变形——变比例不完全积分式PID。
2,背景
学习PID大多是从电机调速开始的,而电机调速几乎是最简单的控制系统了,因为电压与转速之间是有严格的公式存在,尽管有摩擦或者电网扰动等非线性特性存在,但也是可以进行忽略线性化的。因此,电机调速确实是很典型的学习PID算法的实例,但是对于深度学习尚显不足。
那么,对于一种典型的非线性系统,如流体(气体,液体)控制系统等。我们知道,流体是有别于固态实体的,对于他们的控制已经有了很多的研究。
设想这样一个系统,"风力悬浮球高度控制",顾名思义,我们使用轴流风扇,使用高度反馈来控制管中小球的高度。我们知道,风扇的风力和转速肯定是成某种正比关系的,电机的电压和电机的转速是成某种正比关系的,那么风力和电压之间是否的严格的线性关系呢,这就不得而知了,那么风力和小球的高度是不是严格的线性关系呢,还要考虑到小球的速度,加速度,重力等问题...,以上所谈都是关于系统的建模问题,我们今天不是来讨论模型的,那样就失去了PID算法的无模型控制的简单之美。
实验中发现,小球在最底部到升起的那一瞬间,是需要较大的风力的,而升起之后,风力变化又不需要像升起时候那么大。这必然是一个典型的非线性系统了。
那么,如果我们使用传统的PID算法进行控制,当我们给定一个阶跃信号时,假设说需要小球稳定在50cm处,此时小球在0cm处,让小球升起需要很大的风力。
如果,这时我们给一个较大的比例系数,又由于小球在升起运动时风力不需要很大,在加上积分作用,此时较大的比例就会引起强烈震荡,如果其他参数不合适甚至会导致系统发散。
如果,我们给一个较小的比例,靠积分的作用逐渐使得小球上升呢?这也会有问题,在偏差较大的段使用积分,会导致系统超调量增大,调整时间变长,甚至引起发散震荡。
那么,我们自然会想到一种变形——积分分离式PID。
3,纯积分分离式PID算法的缺陷
积分分离式PID是指,在偏差较大的段不进行积分,只靠比例作用,在偏差较小时进行积分,减小稳态误差,用于减小超调,减小调整时间。但是,如果对于快速变化的流体控制来说,在初始段需要较大的风力,在小球升起的时候只需要很小的风力变化,那么假设说我们在初始段给了一个较大的比例,并且不进行积分,那么小球将会在没有积累到足够的积分项之前产生较大的震荡,而在达到稳态之后,又由于较大的比例,可能会引起系统震荡。这样的系统虽然也能达到稳定,但是有较长的调整时间,这不是我们希望看到的。
那么,综上所诉,我们没有建模,根据实验经验,得到了我们这个系统的特点:在小球升起时需要较大的力,小球运动时风力不需要很大的变化。我们需要减小震荡和调整时间。
4,变比例不完全积分式PID介绍
变比例积分分离式实际上是在积分分离式PID的基础所进行的变形,在小球升起(偏差较大的段)使用一个较大的比例,在接近稳态时使用一个较小的比例;
为防止当积分分离使得系统在初始段震荡,而如果不积分分离又容易出现较大超调的情况,我们在初始段进行不完全积分——即在积分时对偏差乘以系数。
通过这样的一种调整,我们我们在初始段(偏差较大的段)使用了较大的比例和不完全积分,在接近稳态段使用较小的比例和完全积分。既减小了上升时间,又减小超调量提升稳态精度,经过试验验证,这种方式对于此流体系统效果奇佳!
5,C语言代码实现
//系统所有参数结构体
typedef struct{
float Kp; //比例系数
float Ki; //积分时间
float Kd; //微分时间
float ExpValu; //期望值
float NowValu; //当前值
float Diff; //微分
float Inte; //积分
float InteLimitPost;//积分正限幅
float InteLimitNega;//积分负限幅
float NoInteKp; //偏差过大 不完全积分时的Kp
float Err; //本次偏差
float ErrLimit; //积分分离阈值
float IntePar; //不完全积分时积分系数 ,如果是0则完全不积分
float ErrLast; //上次偏差
float OutValu; //输出值
float OutLimitPost; //输出正限幅
float OutLimitNega; //输出负限幅
}PIDTypedef;
//PID算法核心
void PIDCalculate(PIDTypedef *pidset)
{
static float Real_Kp = 0;
//计算偏差
pidset->Err = pidset->ExpValu - pidset->NowValu;
//计算微分
pidset->Diff = pidset->Err - pidset->ErrLast;
//积分分离
//如果偏差过大,则减小积分,P进行调整
if((pidset->Err > pidset->ErrLimit) || (pidset->Err < -pidset->ErrLimit))
{
Real_Kp = pidset->NoInteKp;
//进行不完全积分
pidset->Inte += (pidset->IntePar)*pidset->Err;
}else
{
Real_Kp = pidset->Kp;
//完全积分
pidset->Inte += pidset->Err;
}
//积分限幅
if(pidset->Inte > pidset->InteLimitPost)
{
pidset->Inte = pidset->InteLimitPost;
}else if(pidset->Inte < pidset->InteLimitNega)
{
pidset->Inte = pidset->InteLimitNega;
}
//计算输出值
pidset->OutValu = (Real_Kp * pidset->Err) + \
(pidset->Ki * pidset->Inte) +\
(pidset->Kd * pidset->Diff);
//输出限幅
if(pidset->OutValu > pidset->OutLimitPost)
{
pidset->OutValu = pidset->OutLimitPost;
}else if(pidset->OutValu < pidset->OutLimitNega)
{
pidset->OutValu = pidset->OutLimitNega;
}
//上次偏差
pidset->ErrLast = pidset->Err;
}
//PID控制器
float PIDController(PIDTypedef *pidset,float expvalue,float nowvalue)
{
pidset->ExpValu = expvalue;
pidset->NowValu = nowvalue;
PIDCalculate(pidset);
return (pidset->OutValu);
}
以上代码有足够注释,但是不免有未完善之处,望以批判态度进行移植学习。
6,算法延伸
上述代码中的变比例和不完全积分系数使用了一个固定的值,其实应当也可以使用变量,比如比例可以随着偏差变化,不完全积分系数随着偏差变化等。
可根据需要进行延伸,变形,扩展,这应当是PID算法的一大特性吧。这里,向PID算法致敬,向控制学致敬!
最后贴一下风力悬浮球高度控制系统视频