PID的各种算法优缺点

  1. 数字式位置式PID
  2. 数字式增量式PID
  3. 积分分离式PID
  4. 变速积分PID
  5. 不完全微分PID
  6. 微分先行PID
  7. 死区控制PID
  8. 其他

位置式PID

离散化后的公式:

在这里插入图片描述

优点:静态误差小,溢出的影响小。
缺点:计算量很大,累积误差相对大,在系统出现错误的情况下,容易使系统失控,积分饱和。

使用:一般需要结合输出限幅积分限幅使用。积分限幅是避免积分失控的一种手段,也是为了加快调节时间,减少积分饱和的影响,输出限幅是为了使系统输出不过度,也能够减少系统的调节时间,减少超调量。

位置式PID适用于执行没有积分部件的对象。

增量式PID

离散化后的公式:

在这里插入图片描述

优点:溢出的影响小,在系统出现错误的情况下,影响相对较小(因为只与过去的三次误差有关),运算量也相对较小。

缺点:有静态误差(因为没有累积误差)。

使用:位置式PID适用于执行有积分部件的对象。

位置式PID和增量式PID
C语言实现:

//积分限幅
#define INERGRAL_MAX 200
#define INERGRAL_MAX -200
//输出限幅
#define OUT_MIN        -1000
#define OUT_MAX        1000

// 滤波系数a(0-1)  
#define PARAMETER   0.01                
//PID结构体
typedef struct
{
   volatile float    Proportion;             // 比例常数 Proportional Const
   volatile float    Integral;               // 积分常数 Integral Const
   volatile float    Derivative;             // 微分常数 Derivative Const
   volatile int      Error1;                 // Error[n-1]
   volatile int      Error2;                 // Error[n-2]
   volatile int      iError;                 // Error[n]
   volatile    int         Error_sum;
} PID

/****************************************************************************************/                                                                                                                //                                位置式PID
//                                        //pwm=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]    
/****************************************************************************************/
float PID_Postion (int iError,PID* sptr) 
{
    float    iIncpid=0;
  sptr->iError=iError;                                    // 计算当前误差
  
       //sptr->iError=filter(sptr->iError,sptr->Error1); 一阶滤波器
    sptr->Error_sum+=sptr->iError;//积分项    
        ///当输出限幅的时候,积分累加部分也应同时进行限幅,以防输出不变而积分项继续累加,也即所谓的积分饱和过深。
    //积分量限幅
    if(sptr->Error_sum >INERGRAL_MAX)
    {
        sptr->Error_sum = PID_InitStruct->INERGRAL_MAX ;
    }
    if(sptr->Error_sum < INERGRAL_MIN)
    {
        sptr->Error_sum = PID_InitStruct->INERGRAL_MIN ;
    }
        
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D
  sptr->Error1=sptr->iError;                    // 存储误差,用于下次计算        
    iIncpid=PID_OutputLimit(sptr,iIncpid);//PID输出限幅                
  return(iIncpid);          // 返回计算值
    
}

/****************************************************************************************/                                                                        //增量式PID                                                                                        
//                pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]                                                                            //
/****************************************************************************************/
float PID_increase(int iError,PID* sptr) 
{
    
  float    iIncpid=0;
    sptr->iError=iError;//直接检测当前偏差
  iIncpid=sptr->Proportion * (sptr->iError-sptr->Error1)                  // P
         +sptr->Integral * sptr->iError                                   // I
         +sptr->Derivative * (sptr->iError-2*sptr->Error1+sptr->Error2);  // D             
  sptr->Error2=sptr->Error1;                          // 存储误差,用于下次计算
  sptr->Error1=sptr->iError;
    iIncpid=PID_OutputLimit(sptr,iIncpid);//输出限幅处理            
  return(iIncpid);     // 返回增量值    
}

//PID初始化
void PID_Init(PID *sptr)
{
        sptr->Derivative=0;//Kd
        sptr->Proportion=0;//Kp
        sptr->Integral=0;//Ki
        sptr->Error2=0;//第二次误差
        sptr->Error1=0;
        sptr->iError=0;
        sptr->Error_sum=0;//第一次误差
}


//PID输出限制,根据PWM的输出值进行增量式PID输出限制
float PID_OutputLimit(PID *this, double output)  
{
        
    if ((int)output < OUT_MIN)
        {
        output = OUT_MIN;
    }        
        else if ((int)output > OUT_MAX)
        
        {
        output = OUT_MAX;
    }
    return output;
}
//一阶低通滤波器
//减少毛刺,
//滤波系数。取值范围为0~1, 值越小越稳定,越大越灵敏。使用使需要根据实际情况调整滤波系数
//输入:新的采样值
//输出:滤波后的值
float filter(float value,float new_value)   
{     
    return (1-PARAMETER)*value +PARAMETER*new_value;  
}  

两种针对积分的PID:

主要目的是为了尽可能利用积分的优势,消除静态误差,也同时减少积分的副作用。使PID控制器的超调量减少,调节时间减少,系统更加稳定。

积分分离式PID

积分分离式PID主要是针对位置式PID的积分,引入判断误差大小条件,是否使用积分项。

优点:

判定误差比较大的时候,取消积分项的,使用PD或者P控制,没有I的控制,这样,超调量和调节时间都会同时减少。当误差比较小的时候,引入积分项,消除静态误差。

**缺点:**需要经验来确定判断误差的大小,在什么时候使用积分分离比较合适,也就是误差多大的时候取消积分。

C语言实现:PID位置式上面有,这里只需要添加一句判断语句和对积分处理。

无积分分离式的PID:


sptr->Error_sum+=sptr->iError;//积分累加
 iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D

积分分离式PID:

#include<math.h>
int index=0;//积分分离标志
   //积分分离处理 
  if(abs(sptr->iError)> 40) sptr->index=0; 
  else 
  {
      sptr->index=1;
    sptr->Error_sum+=sptr->iError;//积分累加
    }
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * (sptr->Error_sum * index)              // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D

变速积分PID

变积分PID我的理解是积分分离式PID的变体,积分分离式PID 积分的的权重是1或者0,而变积分PID积分的权重会动态变化。取决于偏差,偏差越大,积分越慢。

优缺点和积分分离式PID差不多,只不过,这个变速积分PID更加科学。

积分分离式PID:

#include<math.h>
#define I_MAX 40
#define I_MIN 5
int index=0;//积分分离标志
   //变积分处理 
  if(abs(sptr->iError)> I_MAX) index=0; 
  else if(abs(sptr->iError)< I_MIN) index=1;
  else   index=(I_MAX -  abs(sptr->iError) / (I_MAX -  I_MIN);
  if(index!=0) sptr->Error_sum+=sptr->iError;//积分累加
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * (sptr->Error_sum * index)              // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D
         
 //变速积分也可以这样处理:更加灵活
  if(fabs(sptr->iError)> I_MAX) index=0; 
  else if(fabs(sptr->iError)< I_MIN) index=1;
  else if(fabs(sptr->iError>10&&abs(sptr->iError)<20)) index=0.4;
  else if(fabs(sptr->iError>30&&abs(sptr->iError)<50)) index=0.8;
  else   index=(I_MAX -  abs(sptr->iError) / (I_MAX -  I_MIN); 

两种针对微分的PID:

不完全微分PID

这里针对微分的PID控制算法,是减少微分作用的副作用的一些算法,以便更好地利用微分作用的作用。我们知道,当系统引入微分作用得时候会,引进高频干扰。为了抑制这种干扰,便引入低通滤波器。

这种滤波器得优缺点

优点:
对周期性干扰具有良好的抑制作用
适用于波动频率较高的场合

缺点:
相位滞后,灵敏度低
滞后程度取决于a值大小
不能消除滤波频率高于采样频率的1/2的干扰信号

控制图:
在这里插入图片描述

位置式PID不完全微分:

公式:

在这里插入图片描述

增量式PID不完全微分:

公式:

在这里插入图片描述

//PID结构体
typedef struct
{
   volatile float    Proportion;             // 比例常数 Proportional Const
   volatile float    Integral;               // 积分常数 Integral Const
   volatile float    Derivative;             // 微分常数 Derivative Const
   volatile int      Error1;                 // Error[n-1]
   volatile int      Error2;                 // Error[n-2]
   volatile int      iError;                 // Error[n]
   volatile    int         Error_sum;
   volatile    float    thisdev;//前一拍时的微分项值
   volatile    float    lastdev ;//前一拍时的微分项值
   float              dev_per;//微分系数
   
} PID;

//位置式PID不完全微分
float PID_Postion (int iError,PID* sptr) 
{
    float    iIncpid=0;
  sptr->iError=iError;                                        // 计算当前误差
    sptr->Error_sum+=sptr->iError;//积分项
    //不完全微分    
    sptr->thisdev=sptr->Derivative*(1-sptr->dev_per)*(sptr->iError-sptr->Error1)+sptr->dev_per*sptr->lastdev;
    
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->thisdev; // D
   //更新值
  sptr->Error1=sptr->iError;                    
  sptr->lastdev=sptr->thisdev;//更新下次微分值
  
  return(iIncpid);          // 返回计算值
    
}

//增量式PID不完全微分
float PID_increase(int iError,PID* sptr) 
{
    
  float    iIncpid=0;
    sptr->iError=iError;//直接检测当前偏差
    
    sptr->thisdev=sptr->Derivative*(1-sptr->dev_per)*(sptr->iError-2*sptr->Error1+sptr->Error2)+sptr->dev_per*sptr->lastdev;
    
  iIncpid=sptr->Proportion * (sptr->iError-sptr->Error1)                  // P
         +sptr->Integral * sptr->iError                                   // I
         +sptr->thisdev;  // D    
   //更新         
  sptr->Error2=sptr->Error1;                          
  sptr->Error1=sptr->iError;
  sptr->lastdev=sptr->thisdev;//更新下次微分值    
  return(iIncpid);     // 返回增量值    
}

微分先行PID

微分的作用是预测未来,能够预知变化,做出调整。缺点是会有高频抖动。微分先行的控制算法比较适合于目标位置一般会发生阶跃改变的情况,能够使调整变得缓和。由于微分先行部分对仅对测量值起作用,能够消除设定值突变的影响。因此,可以针对设定值突变的情况进行判断,如果突变,就可以使用微分先行PID,不是就使用普通PID,减少高频抖动带来的影响,进行灵活使用。

图示:
在这里插入图片描述

对微分部分引入一阶惯性滤波器,进行离散化后的公式:

位置式:

在这里插入图片描述

    增量式:

在这里插入图片描述

C语言实现:

//PID结构体
//位置式
typedef struct
{
   volatile float   Proportion;             // 比例常数 Proportional Const
   volatile float   Integral;               // 积分常数 Integral Const
   volatile float   Derivative;             // 微分常数 Derivative Const
   volatile int     Error1;                 // Error[n-1]
   volatile int     Error2;                 // Error[n-2]
   volatile int     iError;                 // Error[n]
   volatile    int        Error_sum;   
   volatile     float  lastPv;             //前一拍的测量值
   volatile     float    gama;               //微分先行滤波系数
   volatile     float    derivative;//微分项
   
   
} PID;

//位置式微分先行PID
float PID_Postion (float set_point,float processValue,PID* sptr) 
{
    float    iIncpid=0;
    float    temp=0,c1,c2,c3;
  sptr->iError=set_point-processValue;                                        // 计算当前误差
    sptr->Error_sum+=sptr->iError;//积分项
    //微分先行
  temp=sptr-> gama * sptr-> Derivative+ sptr-> Proportion;//γKp+Kd
  c3=sptr-> Derivative/temp;//Kd/γKp+Kd
  c2=(sptr-> Derivative+ sptr-> Proportion)/temp;//Kd+Kp/γKp+Kd
  c1=c3*sptr-> gama;//γ(Kp/γKp+Kd)
  
  sptr-> derivative=c1* sptr-> derivative+c2*processValue-c3* sptr-> lastPv;
 
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->derivative; // D
   //更新值
  sptr->Error1=sptr->iError;                    
  sptr->lastPv=sptr->processValue;//更新下次微分值
  
  return(iIncpid);          // 返回计算值
    
}

//***********************************************************//
//                            增量式微分先行PID
//***********************************************************//
//增量式PID结构体
typedef struct
{
   volatile float   Proportion;             // 比例常数 Proportional Const
   volatile float   Integral;               // 积分常数 Integral Const
   volatile float   Derivative;             // 微分常数 Derivative Const
   volatile int     Error1;                 // Error[n-1]
   volatile int     iError;                 // Error[n]
   volatile     float  lastout;   //上一次的测量量  
   volatile     float  lastout_proinc;                   //前一拍的过程测量增量
   volatile     float    gama;                    //微分先行滤波系数
   volatile     float    out_proinc;               //过程测量增量
   volatile     float    derivative_inc;        //微分项
   
   
} PID;

//增量式PID不完全微分PID_increase
float PID_increase(float set_point,float processValue,PID* sptr) 
{
    float    iIncpid=0;
    float    temp=0,c1,c2,c3;
  sptr->iError=set_point-processValue;                                             // 计算当前误差
    //微分先行    
  out_proinc=processValue-lastout;//这次输出增量
        
  temp=sptr-> gama * sptr-> Derivative+ sptr-> Proportion;//γKp+Kd
  c3=sptr-> Derivative/temp;//Kd/γKp+Kd
  c2=(sptr-> Derivative+ sptr-> Proportion)/temp;//Kd+Kp/γKp+Kd
  c1=c3*sptr-> gama;//γ(Kp/γKp+Kd)
  
  sptr-> derivative_inc=c1* sptr-> derivative_inc+c2*out_proinc-c3* sptr-> lastout_proinc;
 
  iIncpid=sptr->Proportion * (sptr->iError-sptr->               Error1)// P
         +sptr->Integral * sptr->iError// I
         +sptr->derivative_inc; // D
   //更新值
  sptr->Error1=sptr->iError;
  sptr->lastout_proinc=sptr->out_proinc;//过程增量更新                    
  sptr->lastout=processValue;//上一次的测量量更新
  
  return(iIncpid);          // 返回增量值
}

带死区的PID

死区控制简单理解:

输出了量,但是执行机构没有动作,也就是输出的量不起作用。

在这里插入图片描述

优势:

可以抑制由于控制器输出量的量化造成的连续的较小的振荡,也就是消除稳定点附近的抖动。这是因为,现实中,总存在误差,一些较小误差难以消除。

缺点:

会造成一定的滞后。设定死区的值也是需要考虑,太小每作用,太大滞后严重。在零点附近时,若偏差很小,进入死去后,偏差置0会造成积分消失,如是系统存在静差将不能消除,所以需要人为处理这一点。

总结来说:PID调节器中设置死区,牺牲的是调节精度,换来的是稳定性。适用于精度不是很高的系统。

死区的输出为0时,pid控制器的比例部分和微分部分均为0,积分部分保持不变。虽然误差值在死区宽度设置的范围内变化,控制器的输出却保持不变。

C语言实现:

#define DEAD_BAND 50//死区控制值   
#define PID_OUT_MAX 200 //PID输出上限
#define PID_OUT_MAX 200 //PID输出上限

#include "math.h"           
//PID结构体
typedef struct
{
   volatile float    Proportion;             // 比例常数 Proportional Const
   volatile float    Integral;               // 积分常数 Integral Const
   volatile float    Derivative;             // 微分常数 Derivative Const
   volatile int      Error1;                 // Error[n-1]
   volatile int      Error2;                 // Error[n-2]
   volatile int      iError;                 // Error[n]
   volatile    int         Error_sum;
} PID

/****************************************************************************************/                                                                                                                //                                位置式PID
//                                        //pwm=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]    
/****************************************************************************************/
float PID_Postion (float set_point,,float now_point,PID* sptr) 
{
    float    iIncpid=0;
  sptr->iError=now_point-set_point; // 计算当前误差
  //死区控制算法                                       
  if(fabs(sptr->iError)>DEAD_BAND) 
  {
    sptr->Error_sum+=sptr->iError;//积分项            
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D
  sptr->Error1=sptr->iError ;                    // 存储误差,用于下次计算

  }    
  else 
  {
        iIncpid=0;
        //sptr->Error_sum+=sptr->iError;//积分项        
        sptr->Error1=sptr->iError;    // 存储误差,用于下次计算
  }    
  
    return(iIncpid);          // 返回计算值                                    
}

/****************************************************************************************/                                                                        //增量式PID                                                                                        
//                pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]                                                                            //
/****************************************************************************************/
float PID_increase(int iError,PID* sptr) 
{
    
  float    iIncpid=0;
    sptr->iError=iError;//直接检测当前偏差
  if(fabs(sptr->iError)>DEAD_BAND) 
  {
  iIncpid=sptr->Proportion * (sptr->iError-sptr->Error1)                  // P
         +sptr->Integral * sptr->iError                                   // I
         +sptr->Derivative * (sptr->iError-2*sptr->Error1+sptr->Error2);  // D             
  sptr->Error2=sptr->Error1;                          // 存储误差,用于下次计算
  sptr->Error1=sptr->iError;
 
}
else 
{
    iIncpid=0;//输出增量值为0
    sptr->Error2=sptr->Error1; // 存储误差,用于下次计算                       
    sptr->Error1=sptr->iError;
}
  return(iIncpid);     // 返回增量值    
}

PID梯形积分

积分会有余差,消除不了,为了减少余差,提高运算的精度。便有了PID梯形积分,也能抑制一些随机干扰。缺点便是,曲线到达设定值的时间会延长。

公式:

在这里插入图片描述

C语言实现:

typedef struct
{
   volatile float    Proportion;             // 比例常数 Proportional Const
   volatile float    Integral;               // 积分常数 Integral Const
   volatile float    Derivative;             // 微分常数 Derivative Const
   volatile int      Error1;                 // Error[n-1]
   volatile int      Error2;                 // Error[n-2]
   volatile int      iError;                 // Error[n]
   volatile    int         Error_sum;
} PID

/****************************************************************************************/                                                                                                                //                                位置式PID
//                                        //pwm=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]    
/****************************************************************************************/
float PID_Postion (float set_point,,float now_point,PID* sptr) 
{
    float    iIncpid=0;
  sptr->iError=now_point-set_point; // 计算当前误差
  //死区控制算法                                       
    sptr->Error_sum+=sptr->iError;//积分项            
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum/2                // 改变的只是这里,多除了2
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D
  sptr->Error1=sptr->iError ;                    // 存储误差,用于下次计算

  
    return(iIncpid);          // 返回计算值                                    
}

总结

PID的变体还有很多,除了上文,还有专家PID与模糊PID是本文不能承载,也是我不能输出,便作罢。

事物都有两面性,该怎么选择比较合适,怎么将PID的各种变体组合在一起合适自己的系统,这个是需要综合衡量和测试的,要知其然知其所以然