PID算法

学了这么多年的PID,总结一下,准备面试。

公式

意义

  1. 比例环节 的作用是对偏差瞬间做出快速相应。偏差一旦产生,控制器立即产生控制作用,使控制量向偏差的方向变化。过大的$K_p$会导致系统震荡,破坏系统的稳定性。
  2. 积分环节 的作用是把偏差的积累作为输出。在控制过程中,只要偏差存在,积分环节的输出就会不断增大,直到偏差为零,输出的 $u(t)$才可能维持在某一常量。
    积分环节的调节作用虽然会消除静态误差,但也会降低系统的响应速度,增加系统的超调量。减小$K_i$会减慢静态误差的消除过程,但可以减少超调量,提高系统的稳定性。
  3. 微分环节 的作用是阻止偏差的变化。它根据偏差的变化趋势进行控制。偏差变化得越快,微分控制器的输出越大,并能在偏差值变大之前进行修正。
    微分作用的引入,将有助于减少超调量,客服震荡,使系统趋于稳定。但微分的作用对输入信号的噪声很敏感,对那些噪声大的系统一般不用微分,或在微分起作用之前先对输入信号进行滤波。

离散化

计算机控制是一种采样控制,它只能根据采样许可的偏差计算控制量,而不能像模拟控制那样连续输出控制量,进行连续控制,所以公式中的积分和微分项不能直接使用,必须进行离散化处理。通常依据控制器输出与执行机构的对应关系,将基本数字PID算法分为位置式PID增量式PID两种。

位置型PID

位置PID结构简单,但是由于有积分项,容易产生积分饱和的现象,而且它每次输出的都是全量,此全量均和过去的输出有关,易产生累计误差。需要对其进行改进,由此产生的改进型PID控制器——增量型PID控制器。其区别在于,控制器输出的不是全量,而只是增量,每次输出均与过去的所有状态无关,而且它没有积分项,运算量小,容易实现手动到自动的无冲击切换。

增量型PID

C++代码实现

参考大神csdn博客
首先创建一个PID类

class PID
{
private:
  float kp;     //比例系数
  float ki;     //积分系数
  float kd;     //微分系数
  float target; //目标值
  float actual; //实际值
  float e;      //本次误差
  float e_pre_1;    //上一次误差
  float e_pre_2;   //上上次误差
  float integral;   //积分项
  float T;          //周期,采样时间,默认为1

public:
  PID();
  ~PID(){};
  PID(float p,float i,float d);
  PID(float p,float i,float d, float T);
  float pid_position(float tar,float act);//执行位置型PID控制
  float pid_incremental(float tar,float act);//执行增量型PID控制
  void pid_show();//显示PID控制器的内部参数

}

接下来,对类中声明的方法进行定义;

//构造函数
PID::PID():kp(0),ki(0),kd(0),target(0),actual(0),integral(0)
{
  e = target-actual;
  e_pre_1 = e;
  e_pre_2 = e;
  T = 1;//周期默认为1
}
PID::PID(float p,float i,float d):kp(p),ki(i),kd(d),target(0),actual(0),integral(0)
{
  e = target-actual;
  e_pre_1 = e;
  e_pre_2 = e;
  T = 1;
}
PID::PID(float p,float i,float d,float t):kp(p),ki(i),kd(d),target(0),actual(0),integral(0)
{
  e = target-actual;
  e_pre_1 = e;
  e_pre_2 = e;
  T = t;
}

//显示函数
void PID::pid_show()
{
  using std::cout;
  using std::endl;
  cout<<"PID控制器的信息如下:"<<endl;
  cout<<" 比例系数="<<kp<<endl;
  cout<<" 积分系数="<<ki<<endl;
  cout<<" 微分系数="<<kd<<endl;
  cout<<" 积分值="<<integral<<endl;
  cout<<" 目标值="<<target<<endl;
  cout<<" 实际值="<<actual<<endl;
  cout<<" 本次误差="<<e<<endl;
  cout<<" 上次误差="<<e_pre_1<<endl;
  cout<<" 上上次误差="<<e_pre_2<<endl;
}

//位置式PID控制函数
float PID::pid_position(float tar,float act)
{
  float u;
  target = tar;
  actual = act;
  //更新误差
  e_pre_2 = e_pre_1;
  e_pre_1 = e;
  e = target-actual;
  //计算积分
  integral += e;

  u = kp*e + ki*T*integral + kd*(e-e_pre_1)/T;
  return u;
}

//增量式PID控制函数
float PID::pid_incremental(float tar, float act)
{
  float u_incremental;
  target = tar;
  actual = act;
  //更新误差
  e_pre_2 = e_pre_1;
  e_pre_1 = e;
  e = target-actual;
  //计算系数
  float A = kp+ki*T+kd/T;
  float B = -kp - 2*kd/T;
  float C = kd/T;
  u_incremental = A*e + B*e_pre_1 + C*e_pre_2;
  return u_incremental;
}

下面是测试代码:

#include <iostream>
#include "PID_Controller.h"
using namespace std;

int main()
{
  PID pid1(0.35, 0.65, 0.005); //类,位置型
  PID pid2(0.35, 0.65, 0.005); //类,增量型
  float target = 1000.0;
  float actual_p = 0.0;//位置型
  float actual_i = 0.0;//增量型
  float pid_incremental = 0.0;
  int n = 30;

  pid1.pid_show();
  pid2.pid_show();

  //位置型控制
  cout<< "target=" << target << endl;
  for(int i = 1;i<=n;i++)
  {
    actual_p = pid1.pid_position(target, actual_p);
    cout<<"i="<<i<<"    actual_p = "<<actual_p<<endl;      
  }
  //增量型控制
  for(int i = 1;i<=n;i++)
  {
    pid_incremental = pid2.pid_incremental(target, actual_i);
    actual_i += pid_incremental;
    cout<<"i="<<i<<"    actual_i = "<<actual_i<<endl;      
  }

  return 0;
}

测试结果

位置型 增量型

结果显示:两种算法的结果是完全一样的。根据我自己的理解,模型存在误差的时候,结果才会有差别,这个后面需要继续研究。

位置型和增量型PID的区别

参考博客

  1. 位置式PID是一种非递推算法,其输出直接控制执行机构,$u(k)$ 的值与执行机构一一对应。
    增量式PID是一种递推算法,$u(k)$对应本次执行机构位置的增量。
  2. 位置式算法的缺点:当前采样时刻的输出$u(k)$与过去各个状态有关,计算时要对$e(k)$进行累加,运算量大,且控制器输出对应执行机构的实际位置,若计算机出现故障(例如数据溢出等),$u(k)$的大幅变化会引起执行机构的大幅变化。
    增量式算法的优点:(1)算式中不需要累加,控制增量的确定仅与最近3次的采样值有关,容易通过加权处理获得较好的控制效果;(2)计算机每次只输出控制增量,机器发生故障时的影响范围小、不会严重影响生产过程;(3)手动-自动切换时冲击小。当控制从手动向自动切换时,可以做到无扰动切换。
  3. 增量式PID并无积分作用,该方法适用于执行机构带积分部件的对象,如步进电机等,而位置式PID适用于执行机构不带积分部件的对象,如电液伺服阀。
  4. 位置式的缺点就是积分饱和,也就是当控制量已经达到最大时,误差仍然积分作用下继续累加,一旦误差开始反向变化,则系统需要较长时间从饱和区退出。当$u(k)$达到最大和最小时,需要停止积分作用,否则进入饱和时,则难以对误差的变化有快速的反应。如采用增量式则可以次消除这个问题。
    两者的唯一区别就是:位置式PI需要同时设置积分限幅和输出限幅,而增量式PI只需要输出限幅
您的支持将鼓励我继续创作!