一、PID算法简介
PID 是 Proportional(比例)、Integral(积分)、Differential(微分)的首字母缩写,它是一种结合比例、积分和微分三个
环节于一体的闭环控制算法。
本质:根据输入的偏差值,按照比例、积分、微分的函数关系进行运算,运算结果用以控制输出。
二、PID离散公式
1、位置式
Kp: 比例系数。
Ki: 积分系数。
Kd: 微分系数。
ek : 实际与预期的误差值。
∑_j=0^k▒e_j : 每一次误差值的和。
ek-ek-1 :本次误差值与上一次误差值的插值。
有人喜欢把P认为是过去(及过去的误差)、I认为是现在(及现在所要达到的值)、D认为是将来(及对将来所要达到值的控制)。
代码实现:
#include <stdio.h>
#include <string.h>
#define Kp 0.1f
#define Ki 0.01f
#define Kd 0.0f
typedef struct{
float target_value; //设定的目标值及需要达到的最终值
float current_value; //当前值(可认为外部的反馈值)
float CAL_value; //计算需要输出的值
float sum_error; //累计的偏差值
float error; //误差值
float last_error; //上一次误差值
float pre_error; //上上一次误差值(增量式pid中使用)
} PID;
PID pid;
float control_value = 0;
float Velocity_FeedbackControl(float Targetvalue, float Currentvalue){
pid.error = Targetvalue - Currentvalue;
pid.sum_error += pid.error;
// if(pid.sum_error>500){
// pid.sum_error = 500;
// }
// if(pid.sum_error < 0){
// pid.sum_error = 0;
// }
pid.CAL_value += (Kp * pid.error + Ki * pid.sum_error + Kd * (pid.error - pid.last_error));
pid.last_error = pid.error;
pid.current_value = pid.CAL_value; //由于没有外部反馈 就默认当值已经到达计算值
return pid.current_value;
}
int main(void){
memset(&pid, 0,sizeof(PID));
int i = 0;
while(i<100){
i++;
pid.current_value = Velocity_FeedbackControl(10, pid.current_value);
printf("%f\n", pid.current_value);
}
return 0;
}
运行结果如下:
从结果看出结果还是理想的,由于Kd设置为0没有消除静态误差,所以出现了超过目标值10.
while循环中仅仅设置了100次而已,在正真的闭环中,次数式不限的。
注意到代码中注释的段落,对pid.sum_error设限制是因为在一个闭环系统中pid会无限制的进行计算,这个pid.sum_error会不断地累计。就电机控制中的速度环而言,若停在一个速度保持很近,突然给定一个减速指令,整个系统的响应会变得很慢所以需要设定限制。
2、增量式
增量式的pid是有位置式推到出来的如下:
第一步:把 k = k −1 代入公式①中,得:
第二步:把公式① − 公式②,得:
可以看出增量式PID计算的是相对上一次输出的增量,即u_k= u_k−1+ ∆u_k 。增量只与近3次的偏差有关,计算出现异常对系统工作影响较小。计算量较小。
代码实现如下:
#include <stdio.h>
#include <string.h>
#define Kp 0.1f
#define Ki 0.01f
#define Kd 0.1f
typedef struct{
float target_value; //设定的目标值及需要达到的最终值
float current_value; //当前值(可认为外部的反馈值)
float CAL_value; //计算需要输出的值
float sum_error; //累计的偏差值
float error; //误差值
float last_error; //上一次误差值
float pre_error; //上上一次误差值(增量式pid中使用)
} PID;
PID pid;
float Velocity_FeedbackControl(float Targetvalue, float Currentvalue){
pid.error = Targetvalue - Currentvalue;
pid.CAL_value +=(Kp * (pid.error - pid.last_error) + Ki * pid.error + Kd * (pid.error - 2* pid.last_error + pid.pre_error));
pid.pre_error = pid.last_error;
pid.last_error = pid.error;
pid.current_value = pid.CAL_value; //由于没有外部反馈 就默认当值已经到达计算值
return pid.current_value;
}
int main(void){
memset(&pid, 0,sizeof(PID));
int i = 0;
while(i<500){
i++;
pid.current_value = Velocity_FeedbackControl(10, pid.current_value);
printf("%f\n", pid.current_value);
}
return 0;
}
运行结果如下:
从上结果可以看出,在同样的系数下位置式的响应要高于增量式。当然这跟pid的系数有直接关系。在实际运用过程中这三个系数是要慢慢调整的。
总结:
以上只是对PID有一个初步的认识。至于更加复杂的算法,有待自己去思考,但是万变不离其宗。文中的公式推导来自原子的电机控制教程ppt。