公式
意义
- 比例环节 的作用是对偏差瞬间做出快速相应。偏差一旦产生,控制器立即产生控制作用,使控制量向偏差的方向变化。过大的$K_p$会导致系统震荡,破坏系统的稳定性。
- 积分环节 的作用是把偏差的积累作为输出。在控制过程中,只要偏差存在,积分环节的输出就会不断增大,直到偏差为零,输出的 $u(t)$才可能维持在某一常量。
积分环节的调节作用虽然会消除静态误差,但也会降低系统的响应速度,增加系统的超调量。减小$K_i$会减慢静态误差的消除过程,但可以减少超调量,提高系统的稳定性。 - 微分环节 的作用是阻止偏差的变化。它根据偏差的变化趋势进行控制。偏差变化得越快,微分控制器的输出越大,并能在偏差值变大之前进行修正。
微分作用的引入,将有助于减少超调量,客服震荡,使系统趋于稳定。但微分的作用对输入信号的噪声很敏感,对那些噪声大的系统一般不用微分,或在微分起作用之前先对输入信号进行滤波。
离散化
计算机控制是一种采样控制,它只能根据采样许可的偏差计算控制量,而不能像模拟控制那样连续输出控制量,进行连续控制,所以公式中的积分和微分项不能直接使用,必须进行离散化处理。通常依据控制器输出与执行机构的对应关系,将基本数字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的区别
参考博客
- 位置式PID是一种非递推算法,其输出直接控制执行机构,$u(k)$ 的值与执行机构一一对应。
增量式PID是一种递推算法,$u(k)$对应本次执行机构位置的增量。 - 位置式算法的缺点:当前采样时刻的输出$u(k)$与过去各个状态有关,计算时要对$e(k)$进行累加,运算量大,且控制器输出对应执行机构的实际位置,若计算机出现故障(例如数据溢出等),$u(k)$的大幅变化会引起执行机构的大幅变化。
增量式算法的优点:(1)算式中不需要累加,控制增量的确定仅与最近3次的采样值有关,容易通过加权处理获得较好的控制效果;(2)计算机每次只输出控制增量,机器发生故障时的影响范围小、不会严重影响生产过程;(3)手动-自动切换时冲击小。当控制从手动向自动切换时,可以做到无扰动切换。 - 增量式PID并无积分作用,该方法适用于执行机构带积分部件的对象,如步进电机等,而位置式PID适用于执行机构不带积分部件的对象,如电液伺服阀。
- 位置式的缺点就是积分饱和,也就是当控制量已经达到最大时,误差仍然积分作用下继续累加,一旦误差开始反向变化,则系统需要较长时间从饱和区退出。当$u(k)$达到最大和最小时,需要停止积分作用,否则进入饱和时,则难以对误差的变化有快速的反应。如采用增量式则可以次消除这个问题。
两者的唯一区别就是:位置式PI需要同时设置积分限幅和输出限幅,而增量式PI只需要输出限幅。