送大家一份小礼物,公众号内回复linux0001即可获得一本Linux电子教程:
我们知道,理论上的数据可以迅速呈现断崖式的线性变化。比如我们想要一个电机迅速从30降到20,理论曲线可以迅速反应到20,但是实际系统并不能呈现这种趋势,一定会是有坡度的曲线或者近似直线,所以要使用算法来进行控制来达到快速变化的标准。PID是一种快速到达预期值的算法,达到实际控制系统调节的快、准、狠。
目录
一、位置式PID精讲
二、增量式PID与位置式PID对比
三、使用C语言设计PID
一、位置式PID精讲
首先我们要了解以下开环控制和闭环控制:
1、开环控制:输入量输入到系统之后,不受反馈量的控制,输入量将一次性输入到被控对象。
2、闭环控制:输入量输入到系统之后,输出量将反馈回系统,下次输入到系统的值将受反馈量影响。
PID也属于闭环系统,他的反馈控制流程如下图:
![](/qrcode.jpg)
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;
}