PID算法的C语言实现一 PID算法原理
直接上图:
、
PID的算法流程其实很简单!利用测得的真实输出变量与输入变量的误差来控制输出变量!而这个误差的控制就是比例、积分、微分这三个控制的相加!
这里我们假设某个采样点时间为t :
设1、输入变量(预期输出的变量大小)为In(t);
2、实际输出变量(传感器测得的真实输出的变量)为Out(t);
3、偏差值 err(t) = In(t)-Out(t);
取得上面的值,再通过pid的控制算法得出误差的补偿值:
理解上面的公式主要几个方面:
1、这个u(x)是对每一次误差的pid控制,相当于我利用这个误差得出一个更精确的误差
2、第一项是对误差做比例的控制,第二项是对以前误差做积分的控制,第三项是对误差做微分的控制
3、最后把这个经过控制的误差加在期望输入变量上来重新改变控制输出变量
这篇先写到这里,因为上面是对于连续的,但我们在计算机控制的时候其实是离散的采样点,下一篇我会说明系统离散化的问题,并根据离散化的特点讲述位置型PID和增量型PID的用法和C语言的实现。
PID算法的C语言实现二 PID算法的离散化
上面一篇已经讲述了PID的流程!这里对上面做一些补充说明:
1、由上一篇原理图框架我们知道PID控制其实是对偏差的控制;
2、如果偏差为0,则比列控制就不起作用;
3、积分环节主要用来消除静差,就是系统稳定后实际输出变量与输入变量的差值,利用偏差不断的叠加来作为输入变量的补偿;
4、微分环节其实是对偏差趋向的一个规律提前调节,这样可以增加系统稳定的快速性;
补充好了!下面还是贴出这个公式:
、
假设采样间隔为T,则第K个采样的时间为KT:
偏差err(KT)=In(K)-Out(K);
积分环节 即 err(K)+err(K+1)+。。。。
微分环节 即(err(K)-err((K-1)))/T
则u(x)的离散公式为:
则u(K)可表示成;
这里的Kp Kd Ki相信推回去就是可以知道具体表达式了!上面是位置型的,下面来个增量型的:
由上面表达式可得:
那么可得;
增量调节值
这就是增量型PID公式,我们发现他跟近3次的偏差有关,这就大大提高了系统稳定性!其实增量型就是算的一个增量误差的调节至就是这次偏差减去上一次偏差的公式!
最后注意增量型PID的输出变量值
U=u(k)+增量调节值
上面已经对PID原理做了分析,下面的篇文将用C语言来实现PID的调节!
*PID算法的C语言实现三 位置型PID的C语言实现*
前面我们已经知道位置型PID和增量型PID的数学表达式,我们根据这些表达式就可以实现基本的PID的C语言编程了!下面我们开始C语言的编写。
第一步:创建一个PID各项参数的变量结构体。
第二步:PID参数的初始化函数。
这里以后我们主要就是对Kp、Ki、Kd的不断调节来优化控制效果!
第三步:PID算法的计算函数。
注意:这里没有很严格的保证PID的算法精确,只是对公式的一种直接实现,以后我们当然要慢慢的优化!
上面就已经结束了对PID算法的设计,下面就开始输出测试了:
代码如下:
下面是经过1000次PID控制之后的结果:因为数据很多,我只给出了一部分
PID算法的C语言实现四 增量型PID的C语言实现
有位置型编程可以知道这个编程也很简单,戒指接贴出代码了:
#include “stdio.h”
struct _pid{
float SetSpeed;
float ActualSpeed;
float Err;
float Err_Last;
float Err_Next;
float Kp,Ki,Kd;
}pid;
void PID_Init(void)
{
printf(“PID_Init begin! \n”);
pid.SetSpeed = 0;
pid.ActualSpeed = 0;
pid.Err = 0;
pid.Err_Last = 0;
pid.Err_Next = 0;
pid.Kp = 0.2;
pid.Ki = 0.015;
pid.Kd = 0.2;
printf(“PID_Init end! \n”);
}
float PID_Cal(float Speed)
{
float incrementSpeed;
pid.SetSpeed = Speed;
pid.Err = pid.SetSpeed - pid.ActualSpeed;
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(void)
{
int count = 0 ;
printf(“SYSTEM BEGIN! \n”);
PID_Init();
while(count<1000)
{
float speed = PID_Cal(200.0);
printf(“-%d-%f-“,count,speed);
count++;
}
return 0;
}
运行结果:
PID算法的C语言实现五 积分分离的PID优化
在上面三、四我们知道积分环节是为了消除静差,但有时候机器启动、结束或出现大幅的偏差时,这样在短时间内会累积大量的偏差,这样就导致积分项很大,以至于出现超调甚至震荡,这样是不允许的!
为了克服这个问题,我们对积分偏差的做一个选择,即通过控制积分偏差的大小来决定是否增加积分项的调节!
在第三篇的代码中做如下更改:
将
float PID_Cal(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;
} 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。代码段一
改成
float PID_Cal(float Speed){
unsigned char index;
pid.SetSpeed = Speed;
pid.Err = pid.SetSpeed - pid.ActualSpeed;
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.Err_Last = pid.Err;
pid.ActualSpeed = pid.Voltage*1.0;
return pid.ActualSpeed;
}。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。代码段二
我们用199这个值来判断他们优化时间的快慢!
代码段一:
运行代码可以看到一下从198到199的越变的次数为 406次,即没积分环节的优化,达到199需要406次的PID控制,时间为406个单位!
代码段二:
运行代码可以看到一下从198到199的越变的次数为 406次,即没积分环节的优化,达到199需要219次的PID控制,时间为219个单位!
从上面我们知道优化后的PID比没优化的快了将近1倍的时间单位!
PID算法的C语言实现六 抗积分饱和的PID优化
为了解决这个问题,我们采用抗积分饱和算法,其思路就是:如果上一次的输出控制量超过了饱和值,饱和值为正,则这一次只积分负的偏差,饱和值为负,则这一次只积分正的偏差,从而避免系统长期留在饱和区!
下面我以 位置型+抗积分饱和+积分分离的PID控制算法C语言来观察调节结果:(相对应的代码可以参考以往的文章)
//位置型+抗积分饱和+积分分离 PID控制算法
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(void)
{
printf(“PID_Init begin! \n”);
pid.SetSpeed = 0;
pid.ActualSpeed = 0;
pid.Err = 0;
pid.Err_Last = 0;
pid.Kp = 0.2;
pid.Ki = 0.1; //增大了积分环节的值
pid.Kd = 0.2;
pid.Voltage = 0;
pid.Integral = 0;
pid.Umax = 400; //正饱和值为400
pid.Umin = -200; //负饱和值为-200
printf(“PID_Init end! \n”);
}
float PID_Cal(float Speed)
{
unsigned char index;
pid.SetSpeed = Speed;
pid.Err = pid.SetSpeed - pid.ActualSpeed;
if(pid.ActualSpeed>pid.Umax) //如果上一次输出变量出现正向的饱和
{
if(abs(pid.Err)>200)
{
index = 0;
}
else
{
index = 1;
if(pid.Err<0)
{
pid.Integral += pid.Err; //正饱和只积分负偏差
}
}
}
else if(pid.ActualSpeed<pid.Umin) //如果上一次输出变量出现负向的饱和
{
if(abs(pid.Err)>200)
{
index = 0;
}
else
{
index = 1;
if(pid.Err>0)
{
pid.Integral += pid.Err; //负饱和只积分正偏差
}
}
}
else
{
if(abs(pid.Err)>200) //积分分离的PID优化,可参考以往的文章
{
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;
}
int main(void)
{
int count = 0 ;
printf(“SYSTEM BEGIN! \n”);
PID_Init();
while(count<1000)
{
float speed = PID_Cal(200.0);
printf(“-%d-%f-“,count,speed);
count++;
}
return 0;
}
最后运行结果:
我们发现,相对以往的算法,还算法大大提高了调节的速度和稳定!
PID算法的C语言实现七 梯形积分的PID优化
先上梯形积分的算法公式;
对于积分环节的PID控制,其实就是为了消除系统的静差,为了减少这个,我们必须提高积分环节的精度也就是每一次偏差的调节变小,因为在系统稳定的状态下,偏差调节越小,就是精度越高,为此我们用梯形的积分代替第六章中的矩形积分来提高积分调节精度!
代码请参考第六篇中的代码,只需讲第六篇中的这段代码:
pid.Voltage = pid.Kp*pid.Err +index*pid.Ki*pid.Integral + pid.Kd*(pid.Err - pid.Err_Last);
改成
pid.Voltage = pid.Kp*pid.Err +index*pid.Ki*pid.Integral/2
+ pid.Kd*(pid.Err - pid.Err_Last);
最后运行的稳定结果为199.999878,相对第六篇的运行结果199.999939而言,精度提高了!
PID算法的C语言实现八 变积分的PID优化
其思想就是:改变积分的累加速度,使其与偏差大小相对应;偏差越大,积分越慢(可以理解为积分少),偏差越小,积分越快!
程序上我们要做的就是在积分系数上加一个速度比例值index:
当 偏差值>200 index=0;
当 偏差值<180 index=1;
当 200>偏差值>180 index= (200-偏差值)/20
具体代码如下:
struct _pid{
float SetSpeed;
float ActualSpeed;
float Err;
float Err_Last;
float Kp,Ki,Kd;
float Voltage;
float Integral;
}pid;
void PID_Init(void)
{
printf(“PID_Init begin! \n”);
pid.SetSpeed = 0;
pid.ActualSpeed = 0;
pid.Err = 0;
pid.Err_Last = 0;
pid.Kp = 0.2;
pid.Ki = 0.2; //增大了积分系数
pid.Kd = 0.2;
pid.Voltage = 0;
pid.Integral = 0;
printf(“PID_Init end! \n”);
}
float PID_Cal(float Speed)
{
unsigned char index;
pid.SetSpeed = Speed;
pid.Err = pid.SetSpeed - pid.ActualSpeed;
if(abs(pid.Err)>200) //变积分处理控制
{
index = 0;
}
else if(abs(pid.Err)<180)
{
index =1;
pid.Integral += pid.Err;
}
else
{
index = (200-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;
}
int main(void)
{
int count = 0 ;
printf(“SYSTEM BEGIN! \n”);
PID_Init();
while(count<1000)
{
float speed = PID_Cal(200.0);
printf(“-%d-%f-“,count,speed);
count++;
}
return 0;
}
运行结果:
我们发现系统的稳定速度非常快,在94次PID控制后就趋于稳定了!