多种PID算法用C语言来实现
原文链接:https://blog.csdn.net/Nirvana_Tai/article/details/105409311,随后整理验证,再补充
(一) 前言
PID算法在工业应用中随处可见。大学期间,想做各类科创也少不了PID算法的身影。PID除了需要理解原理,用合理的代码实线PID算法也让许多同学们苦恼,我总结了常用的多种PID算法的C语言实现,供大家参考学习。
我只是简单总结了常用的一些PID算法。对于模糊PID和专家PID,因为我也没用到过,就没有具体给出代码,这其中最难的还是参数的整定。当然仿真软件也十分有帮助,大家可以看我另外的Simulink的学习。
整理不易,请留个赞呗、
(二) PID原理简单介绍
PID的流程本质上是通过误差信号控制被控量,而控制器本身就是比例、积分、微分三个环节的加和。
先大致介绍一下PID的控制流程。
首先,我们给出定义的连续的PID公式:
但在工程中,我们想要用处理器去计算,必须将其化为离散化模型。这样处理器才可以进行计算。
偏差 err(K)=rin(K)-rout(K);
积分环节用加和的形式表示 err(K)+err(K+1)+……;
微分环节用斜率的形式表示 [err(K)-err(K-1)]/T;
通过以上公式,可以得到PID离散表示形式:
[1] 位置式PID
下面是位置式PID:
[2] 增量式PID
下面是PID的增量式表示方式:
增量式的结果和近三次的偏差相关,这就很大地提高了系统的稳定性。
注意的是最终的输出结果应该为:
u(K)+调节值;
(三) 位置式PID——C语言
① 定义PID变量结构体:
struct _pid{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
②初始化变量:
void PID_init(){
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.2;
pid.Ki=0.015;
pid.Kd=0.2;
}
Kp,Ki,Kd三个参数,调试过程当中,对于要求的控制效果,可以通过调节这三个量直接进行调节。
当然也可以写函数来直接修改比例系数的值,这很简单我就不写了。
③控制算法:
float PID_realize(float speed){
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.integral+=pid.err;
pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
注:这是最基本的算法实现形式,稳定到设定值的速度慢,且没有考虑死区问题,还没有设定阈值,。这只是一种原理上算法的直接实现,并不能直接用于工程中。
(四) 增量型PID——C语言
①定义并初始化PID
struct _pid{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_next; //定义上一个偏差值
float err_last; //定义最上前的偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
}pid;
void PID_init(){
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.err_next=0.0;
pid.Kp=0.15;
pid.Ki=0.20;
pid.Kd=0.25;
}
②控制算法:
float PID_realize(float speed){
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
float incrementSpeed=pid.Kp*(pid.err-pid.err_next)+pid.Ki*pid.err+pid.Kd*(pid.err-2*pid.err_next+pid.err_last);
pid.ActualSpeed+=incrementSpeed;
pid.err_last=pid.err_next;
pid.err_next=pid.err;
return pid.ActualSpeed;
}
③测试算法
int main(){
PID_init();
int count=0;
while(count<1000)
{
float speed=PID_realize(150.0);
printf("%f\n",speed);
count++;
}
return 0;
}
(五) 积分分离的PID控制算法——C语言
当被控量与设定值偏差较大时,取消积分作用; 当被控量接近给定值时,引入积分控制,以消除静差,提高精度与稳定速度。
部分控制代码
if(abs(pid.err)>100)
{
index=0;
}else{
index=1;
pid.integral+=pid.err;
}
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
这种控制算法的速度很快
(六) 抗积分饱和的PID控制算法——C语言
积分饱和现象是指当系统是一个方向出现的偏差,使PID控制器的输出与积分作用不断积累更多,导致执行机构超出极限位置。
这时如果控制器输出U (k)继续增加,致动器的开放不可能增加,电脑输出控制量超出了正常操作范围,进入饱和区。当系统出现反向偏差时,u(k)逐渐退出饱和区。你进入饱和区越深,你离开的时间就越长。系统就会失控,导致控制性能的恶化。
防止积分饱和的方法之一——抗积分饱和法,该方法的思路是在计算u(k)时,首先判断上一时刻的控制量u(k-1)是否已经超出了极限范围: 若u(k-1)>umax,则只累加负偏差; 若u(k-1)<umin,则只累加正偏差。避免了控制量长时间在饱和区。
struct _pid{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义控制执行器的变量
float integral; //定义积分值
float umax;
float umin;
}pid;
void PID_init(){
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.2;
pid.Ki=0.1; //这里加大了积分环节的值
pid.Kd=0.2;
pid.umax=100;
pid.umin=-100;
}
float PID_realize(float speed){
int index;
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
if(pid.ActualSpeed>pid.umax)
{
if(abs(pid.err)>100)
{
index=0;
}else{
index=1;
if(pid.err<0)
{
pid.integral+=pid.err;
}
}
}else if(pid.ActualSpeed<pid.umin){
if(abs(pid.err)>100) //积分分离过程
{
index=0;
}else{
index=1;
if(pid.err>0)
{
pid.integral+=pid.err;
}
}
}else{
if(abs(pid.err)>200) //积分分离过程
{
index=0;
}else{
index=1;
pid.integral+=pid.err;
}
}
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
(七) 变积分的PID控制算法——C语言
根据系统的偏差大小改变积分速度。
变积分PID的基本思想是设法改变积分项的累加速度,使其与偏差大小相对应:偏差越大,积分越慢; 偏差越小,积分越快。
具体实现思路为:
给积分系数前加上一个比例值index:
当abs(err)<150时, index=1;
当100<abs(err)<150时,index=(150-abs(err)/20;
当abs(err)>150时, index=0;
最终的比例环节的比例系数值为ki*index;
控制函数为:
float PID_realize(float speed){
float index;
pid.SetSpeed=speed;
pid.err=pid.SetSpeed-pid.ActualSpeed;
//变积分过程
if(abs(pid.err)>150)
{
index=0.0;
}else if(abs(pid.err)<100){
index=1.0;
pid.integral+=pid.err;
}else{
index=(150-abs(pid.err))/20;
pid.integral+=pid.err;
}
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
这种控制方式会使系统的稳定速度非常快
这就是一些简单的PID实现的大体思路。在实际工程中需要结合实际再加调整,选择合适项目的PID算法。在有些时候,也许只用到PI或PD即可。
(八) 附言
附送一个参数整定口诀
参数整定找最佳, 从小到大顺序查。
先是比例后积分, 最后再把微分加。
曲线振荡很频繁, 比例度盘要放大。
曲线漂浮绕大弯, 比例度盘往小扳。
曲线偏离回复慢, 积分时间往下降。
曲线波动周期长, 积分时间再加长。
曲线振荡频率快, 先把微分降下来。
动差大来波动慢, 微分时间应加长。
理想曲线两个波, 前高后低四比一。
一看二调多分析, 调节质量不会低。
这只是PID纯算法部分,如何将PID算出的数值合理地反馈到输入上,如何定时去捕获执行PID,可以在我的他博文中查看。
(二) PID原理简单介绍 PID的流程本质上是通过误差信号控制被控量,而控制器本身就是比例、积分、微分三个环节的加和。 先大致介绍一下PID的控制流程。
首先,我们给出定义的连续的PID公式:
但在工程中,我们想要用处理器去计算,必须将其化为离散化模型。这样处理器才可以进行计算。
偏差 err(K)=rin(K)-rout(K); 积分环节用加和的形式表示 err(K)+err(K+1)+……; 微分环节用斜率的形式表示 [err(K)-err(K-1)]/T;
通过以上公式,可以得到PID离散表示形式:
[1] 位置式PID下面是位置式PID:
[2] 增量式PID下面是PID的增量式表示方式:
增量式的结果和近三次的偏差相关,这就很大地提高了系统的稳定性。 注意的是最终的输出结果应该为: u(K)+调节值;
(三) 位置式PID——C语言① 定义PID变量结构体:
struct _pid{ float SetSpeed; //定义设定值 float ActualSpeed; //定义实际值 float err; //定义偏差值 float err_last; //定义上一个偏差值 float Kp,Ki,Kd; //定义比例、积分、微分系数 float voltage; //定义电压值(控制执行器的变量) float integral; //定义积分值}pid;123456789②初始化变量:
void PID_init(){ pid.SetSpeed=0.0; pid.ActualSpeed=0.0; pid.err=0.0; pid.err_last=0.0; pid.voltage=0.0; pid.integral=0.0; pid.Kp=0.2; pid.Ki=0.015; pid.Kd=0.2;}1234567891011 Kp,Ki,Kd三个参数,调试过程当中,对于要求的控制效果,可以通过调节这三个量直接进行调节。 当然也可以写函数来直接修改比例系数的值,这很简单我就不写了。③控制算法:
float PID_realize(float speed){ pid.SetSpeed=speed; pid.err=pid.SetSpeed-pid.ActualSpeed; pid.integral+=pid.err; pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last); pid.err_last=pid.err; pid.ActualSpeed=pid.voltage*1.0; return pid.ActualSpeed;}123456789 注:这是最基本的算法实现形式,稳定到设定值的速度慢,且没有考虑死区问题,还没有设定阈值,。这只是一种原理上算法的直接实现,并不能直接用于工程中。
(四) 增量型PID——C语言①定义并初始化PID
struct _pid{ float SetSpeed; //定义设定值 float ActualSpeed; //定义实际值 float err; //定义偏差值 float err_next; //定义上一个偏差值 float err_last; //定义最上前的偏差值 float Kp,Ki,Kd; //定义比例、积分、微分系数}pid;
void PID_init(){ pid.SetSpeed=0.0; pid.ActualSpeed=0.0; pid.err=0.0; pid.err_last=0.0; pid.err_next=0.0; pid.Kp=0.15; pid.Ki=0.20; pid.Kd=0.25;}
12345678910111213141516171819②控制算法:
float PID_realize(float speed){ pid.SetSpeed=speed; pid.err=pid.SetSpeed-pid.ActualSpeed; float incrementSpeed=pid.Kp*(pid.err-pid.err_next)+pid.Ki*pid.err+pid.Kd*(pid.err-2*pid.err_next+pid.err_last); pid.ActualSpeed+=incrementSpeed; pid.err_last=pid.err_next; pid.err_next=pid.err; return pid.ActualSpeed;}123456789③测试算法
int main(){ PID_init(); int count=0; while(count<1000) { float speed=PID_realize(150.0); printf("%f\n",speed); count++; } return 0;}1234567891011(五) 积分分离的PID控制算法——C语言 当被控量与设定值偏差较大时,取消积分作用; 当被控量接近给定值时,引入积分控制,以消除静差,提高精度与稳定速度。 部分控制代码
if(abs(pid.err)>100) { index=0; }else{ index=1; pid.integral+=pid.err; } pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last); 12345678 这种控制算法的速度很快
(六) 抗积分饱和的PID控制算法——C语言 积分饱和现象是指当系统是一个方向出现的偏差,使PID控制器的输出与积分作用不断积累更多,导致执行机构超出极限位置。 这时如果控制器输出U (k)继续增加,致动器的开放不可能增加,电脑输出控制量超出了正常操作范围,进入饱和区。当系统出现反向偏差时,u(k)逐渐退出饱和区。你进入饱和区越深,你离开的时间就越长。系统就会失控,导致控制性能的恶化。 防止积分饱和的方法之一——抗积分饱和法,该方法的思路是在计算u(k)时,首先判断上一时刻的控制量u(k-1)是否已经超出了极限范围: 若u(k-1)>umax,则只累加负偏差; 若u(k-1)<umin,则只累加正偏差。避免了控制量长时间在饱和区。
struct _pid{ float SetSpeed; //定义设定值 float ActualSpeed; //定义实际值 float err; //定义偏差值 float err_last; //定义上一个偏差值 float Kp,Ki,Kd; //定义比例、积分、微分系数 float voltage; //定义控制执行器的变量 float integral; //定义积分值 float umax; float umin;}pid;
void PID_init(){ pid.SetSpeed=0.0; pid.ActualSpeed=0.0; pid.err=0.0; pid.err_last=0.0; pid.voltage=0.0; pid.integral=0.0; pid.Kp=0.2; pid.Ki=0.1; //这里加大了积分环节的值 pid.Kd=0.2; pid.umax=100; pid.umin=-100;}float PID_realize(float speed){ int index; pid.SetSpeed=speed; pid.err=pid.SetSpeed-pid.ActualSpeed;
if(pid.ActualSpeed>pid.umax) { if(abs(pid.err)>100) { index=0; }else{ index=1; if(pid.err<0) { pid.integral+=pid.err; } } }else if(pid.ActualSpeed<pid.umin){ if(abs(pid.err)>100) //积分分离过程 { index=0; }else{ index=1; if(pid.err>0) { pid.integral+=pid.err; } } }else{ if(abs(pid.err)>200) //积分分离过程 { index=0; }else{ index=1; pid.integral+=pid.err; } }
pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last); pid.err_last=pid.err; pid.ActualSpeed=pid.voltage*1.0; return pid.ActualSpeed;}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768(七) 变积分的PID控制算法——C语言 根据系统的偏差大小改变积分速度。 变积分PID的基本思想是设法改变积分项的累加速度,使其与偏差大小相对应:偏差越大,积分越慢; 偏差越小,积分越快。 具体实现思路为: 给积分系数前加上一个比例值index: 当abs(err)<150时, index=1; 当100<abs(err)<150时,index=(150-abs(err)/20; 当abs(err)>150时, index=0; 最终的比例环节的比例系数值为ki*index;控制函数为:
float PID_realize(float speed){ float index; pid.SetSpeed=speed; pid.err=pid.SetSpeed-pid.ActualSpeed; //变积分过程 if(abs(pid.err)>150) { index=0.0; }else if(abs(pid.err)<100){ index=1.0; pid.integral+=pid.err; }else{ index=(150-abs(pid.err))/20; pid.integral+=pid.err; } pid.voltage=pid.Kp*pid.err+index*pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err; pid.ActualSpeed=pid.voltage*1.0; return pid.ActualSpeed;}
12345678910111213141516171819202122 这种控制方式会使系统的稳定速度非常快
这就是一些简单的PID实现的大体思路。在实际工程中需要结合实际再加调整,选择合适项目的PID算法。在有些时候,也许只用到PI或PD即可。
(八) 附言附送一个参数整定口诀
参数整定找最佳, 从小到大顺序查。 先是比例后积分, 最后再把微分加。 曲线振荡很频繁, 比例度盘要放大。 曲线漂浮绕大弯, 比例度盘往小扳。 曲线偏离回复慢, 积分时间往下降。 曲线波动周期长, 积分时间再加长。 曲线振荡频率快, 先把微分降下来。 动差大来波动慢, 微分时间应加长。 理想曲线两个波, 前高后低四比一。 一看二调多分析, 调节质量不会低。12345678910 这只是PID纯算法部分,如何将PID算出的数值合理地反馈到输入上,如何定时去捕获执行PID,可以在我的其他博文中查看。————————————————版权声明:本文为CSDN博主「TaiBai-let」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/Nirvana_Tai/article/details/105409311
人就像是被蒙着眼推磨的驴子,生活就像一条鞭子;当鞭子抽到你背上时,你就只能一直往前走,虽然连你也不知道要走到什么时候为止,便一直这么坚持着。