经典控制算法PID精讲(附代码)

送大家一份小礼物,公众号内回复linux0001即可获得一本Linux电子教程:

我们知道,理论上的数据可以迅速呈现断崖式的线性变化。比如我们想要一个电机迅速从30降到20,理论曲线可以迅速反应到20,但是实际系统并不能呈现这种趋势,一定会是有坡度的曲线或者近似直线,所以要使用算法来进行控制来达到快速变化的标准。PID是一种快速到达预期值的算法,达到实际控制系统调节的快、准、狠

目录

一、位置式PID精讲

二、增量式PID与位置式PID对比

三、使用C语言设计PID


一、位置式PID精讲

首先我们要了解以下开环控制和闭环控制:

1、开环控制:输入量输入到系统之后,不受反馈量的控制,输入量将一次性输入到被控对象。

2、闭环控制:输入量输入到系统之后,输出量将反馈回系统,下次输入到系统的值将受反馈量影响。

PID也属于闭环系统,他的反馈控制流程如下图:

扫描二维码关注公众号,回复: 9478011 查看本文章

PID是由比例、积分和微分的首字母组成:P“积分”、I“积分”、D“微分”。首先我们给出位置式PID的公式:

1、比例环节

比例的作用是用于快速到达我们所想要的值。比如一个水缸深10米,里面有高两米的水,现在我们想要用控制水位的方法来给他加满水,怎么加才能达到我们的预期值呢?如果我们仅用比例的方法来给他加水:

u=Kp*error(t)

此时我们将比例系数调为0.5。

时间
(t)
比例系数
(Kp)
水位差
(error(t))
加水量
(u)
水深
1 0.5 8 0.5*8 6
2 0.5 4 0.5*4 8
3 0.5 2 0.5*2 9
... ... ... ... ​...

依此类推我们可以发现水高会慢慢无限接近于10米,也就是说可以到达我们的标准。但是我们介绍的只是理想状态,是在没有漏水的情况下得出的结论,现实生活中我们的私家车轮胎和电机都会有不同程度的损耗,所以这种理想状态在现实生活中并不适用。如果我们的水缸在加到水深为8米的时候底部出现一个小洞,以每秒1米的速度漏水,那么接下来的情况会是

时间
(t)
比例系数
(Kp)
水位差
(error(t))
加水量
(u)
水深
1 0.5 8 0.5*8 6
2 0.5 4 0.5*4 8
3 0.5 2 0.5*2-1 8
4 0.5 2 0.5*2-1 8
... ... ... ... ...

可以发现,我们会永远停留在8米,所以单纯的比例控制并不能满足我们正常的需求

2、积分环节

现在我们将积分引入,使实际控制变为比例+积分的PI控制

u=kp*error(t)+ki∗∫error

积分在离散状态下的作用是累加,知道了这个那么接下来的工作会变得很直观。∫error就是将之前的若干次误差累加,比如说第二秒的时候,我们将误差8米和误差4米累加,这样∫error=8+4=12,再加上合适的积分系数,那么我们就可以消除之前的静态误差,目标逐渐达到10米。Ki如果过大会降低系统的反应速度,增大超调量(也就是说超过我们的预期值);过小当然会展现出平稳过渡曲线,系统震荡也会很小,但是静态误差问题也就会显现出来

3、微分环节

在实际控制系统,我们不仅要快速达到预期值,还要纠正积分所带来的偏差,加入微分控制可以调节后者。所以这就形成了闭环控制算法PID。回到水缸的例子上就是我们的水位要到达10米时加入微分调节,减少水位的超调量,并且使系统的震荡尽量小。

大多数时候,我们在写程序时使用下面整合的公式就好了:

u=kp*error(t)+ki∗∫error+∫(error(t)-error(t-1))

二、增量式PID与位置式PID对比

我们首先给出增量式PID的公式:

我们可以看出,增量式PID实际上是位置式PID的本次值与上次值的差值。这样的话我们就要分析连续三次系统的误差情况,而得出的Δu(k)不是本次我们需要位置实际增加的量,而是进几次位置误差的增量,这么做的话实际上就消除了累积的误差。如果我们根据实际情况加权处理,那么在实际系统控制的时候会呈现很稳定的控制。

三、使用C语言设计PID

1、位置式PID:

typedef struct
{
  float Kp;                       //比例系数
  float Ki;                       //积分系数
  float Kd;                       //微分系数
 
  float currErr;                  //当前误差
  float lastErr;                  //前一次误差
  float sumErr;                   //累计误差 
}LocPIDStr;
 
/****************************
返回值:pid位置
参  数:
    setValue:期望值
    realValue:当前值 
****************************/
float PID_Loc(float setValue, float realValue, LocPIDStr *PID)
{
  float LocPIDValue;                  //位置
  PID->currErr = setValue - realValue;//本次误差 
  PID->sumErr += PID->currErr;        //累计误差
  LocPIDValue = PID->Kp * PID->currErr + (PID->Ki * PID->sumErr) + PID->Kd * (PID->lastErr - PID->currErr);
  PID->lastErr = PID->currErr;//保存本次误差为上一次误差 
  return LocPIDValue;
}

2、增量式PID:

typedef struct
{
  float Kp;                       //比例系数
  float Ki;                       //积分系数
  float Kd;                       //微分系数
 
  float currErr;                  //当前误差
  float lastErr;                  //前一次误差
  float preErr;                   //前前次误差 
}IncPIDStr;
 
/****************************
返回值:pid增量
参  数:
    setValue:期望值
    realValue:当前值 
****************************/
float PID_Inc(float setValue, float realValue, IncPIDStr *PID)
{
  float incPIDValue;                  //位置
  PID->currErr = setValue - realValue;//本次误差 
  incPIDValue = (PID->Kp * PID->currErr) - (PID->Ki * PID->lastErr) + (PID->Kd * PID->preErr);
  PID->preErr = PID->lastErr; //保存上次误差为前前次误差 
  PID->lastErr = PID->currErr;//保存本次误差为上一次误差 
  return incPIDValue;
}
发布了24 篇原创文章 · 获赞 35 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Groot_Lee/article/details/104486605