单片机之PID算法
说到PID算法,想必大部人并不陌生,PID算法在很多方面都有重要应用,比如电机的速度控制,恒温槽的温度控制,四轴飞行器的平衡控制等等,作为闭环控制系统中的一种重要算法,其优点和可实现性都成为人们的首选。下面简单来讲解一下PID算法:
首先PID算法是有比例,积分,微分三部分组成,先说下比例部分,所谓比例部分,就是呈线性关系,举个例子,一个电热丝加热水,开始的时候温度很低,离50℃很大,这时应该加大功率,离目标温度越大,其功率应该越大,反之越小,这就是比例部分。
乍一看,既然比例部分已经可以控制温度了为啥还需要积分和微分部分呢,难道是多此一举么?其实不然,在实际中会出现这种情况,当加热到50℃时,系统很难停止下来,而是会持续一段时间,这样就会超过预设值,所以仅有比例控制并不完美,这是就需要积分部分和微分部分。积分部分就是把之前的误差全部累加起来,这样起始时由于误差很大加热功率就大,随着接近预设值后功率开始减少,微分部分就是起始时温度增加很快,表示此时需要很大的功率,随着温度接近预设值,其斜率开始减小最后为零,意味着功率也减少,当然很难为零,一般在一定的范围内波动。
现在开始用C语言来实现PID算法:
位置式:
比例部分:
Kp:比例系数 SetValue:预设值 FactValue:当前实际值 Error_1:当前误差
则比例部分为:
Sp = Kp*(SetValue – FactValue)
或者
Sp = Kp*Error_1
注解:Sp大小反应需要控制的量大小,比如Sp越大,功率越大。当Sp为负值时,表示要超过预设值,如果是电机,则需要反转
积分部分:
Ki:积分系数 Error_1:当前误差 Error_2:上一次误差 Error_3:上上一次误差 ……..Error_n:开始时的误差
则积分部分为:
Si = Ki*(Error_1+Error_2+Error_3+……+Error_n)
注解:因为整个是一个过程,所以上一次误差其实就是上一次的当前误差
微分部分:
Kd:微分系数 Error_1:当前误差 Error_2:上一次误差
则微分部分为:
Sd = Kd*(Error_1-Error_2)
综上部分的PID得:
PID=Sp + Si + Sd = Kp*Error_1 + Ki*(Error_1+Error_2+Error_3+……+Error_n) + Kd*(Error_1-Error_2)
增量式:
将上述推导的PID记作时间为k时刻的PID控制量,则
PID(k) =Sp + Si + Sd = Kp*Error_1(k) + Ki*(Error_1(k)+Error_2(k-1)+Error_3(k-2)+……+Error_n(0)) + Kd*(Error_1(k)-Error_2(k-1)) 1
将上式k=k-1代入得:
PID(k-1) =Sp + Si + Sd = Kp*Error_1(k-1) + Ki*(Error_1(k-1)+Error_2(k-2)+Error_3(k-3)+……+Error_n(0)) + Kd*(Error_1(k-1)-Error_2(k-2)) 2
1-2得:
PID(k) – PID(k-1) = Kp*(Error_1(k)-Error_1(k-1)) + Ki*(Error_1(k)) + Kd*(Error_1(k)-2*Error_2(k-1)+Error_2(k-2))
将PID(k) – PID(k-1)记作detPID
detPID = Kp*(Error_1(k)-Error_1(k-1)) + Ki*(Error_1(k)) + Kd*(Error_1(k)-2*Error_2(k-1)+Error_2(k-2))
这样就得到了增量式的PID算法,其计算的结果为增加的控制量
增量式的PID有个好处就是只与当前三个误差量有关系,与其他无关,这样就简化的处理过程,而且提高了精度,下面是PID源码:
/*文件名:PID.h*/ #ifndef _PID_H_ #define _PID_H_ extern float Kp,Ki,Kd; //系数(全局变量) extern float AclValue; //实际值 extern float SetValue; int PID(void); #endif
/*######################################################################## 文件名:PID.c 时间: 2018.9.7 备注:无 #########################################################################*/ #include "PID.h" float Kp=10,Ki=0.8,Kd=0.5; //系数 float SetValue=2000; //设定值 float AclValue=0; //实际 float Error1=0,Error2=0,Error3=0; //误差 /* 下面为增量式PID算法 */ /********************************************************************************** 函数名:PID 返回值:输出增量 参数:无 备注:当输出大于0表示小于预设值,当输出小于0表示大于预设值 ***********************************************************************************/ int PID(void) { float OutValue =0; Error3 = SetValue - AclValue; OutValue = Kp*(Error3-Error2)+Ki*(Error3)+Kd*(Error3-2*Error2+Error1); Error1=Error2; //这部分是迭代,因为上次的误差就是上次的当前误差 Error2=Error3; if(OutValue>3000) //这部分是规定最大输出增量 OutValue=3000; if(OutValue<-3000) OutValue=-3000; return OutValue; }
下面给出计算机模拟代码;
#include "stdio.h" float Kp=10,Ki=2,Kd=0.5; //系数 float SetValue=1256; //设定值 float AclValue=0; //实际 float Error1=0,Error2=0,Error3=0; //误差 /* 下面为增量式PID算法 */ /********************************************************************************** 函数名:PID 返回值:输出增量 参数:无 备注:当输出大于0表示小于预设值,当输出小于0表示大于预设值 ***********************************************************************************/ int PID(void) { float OutValue =0; Error3 = SetValue - AclValue; OutValue = Kp*(Error3-Error2)+Ki*(Error3)+Kd*(Error3-2*Error2+Error1); Error1=Error2; Error2=Error3; return OutValue; } int main(void) { unsigned int i=1000; while(i) { PID(); //特别注意这里:必须要运行,因为需要执行这一步:Error1=Error2; Error2=Error3;
printf("当前实际值为:%f \n",AclValue); AclValue += PID(); i--);
}
return 0;
}
运行结果: