fireholder.github.io

伪文艺女青年,状高冷,话少爱热闹


Blog | Archive | About

PID 详解

07 Apr 2017 | 单片机, 算法

PID 算法概述

当今的自动控制技术都是基于反馈的概念。反馈理论的三要素包括:测量、比较和执行,测量关心的变量,与期望值相比较,用这个误差纠正调节控制系统的响应。

比例、积分、微分,构成控制变量 u(t) ,成为 比例、积分、微分控制,简称PID控制。

P

Proportion (比例),就是输入偏差乘以一个常数。

比例控制能迅速反应误差,从而减少稳态误差。除了系统控制输入为0和系统过程值等于期望值这两种情况,比例控制都能给出稳态误差。当期望值有一个变化时,系统过程值将产生一个稳态误差。但是,比例控制不能消除稳态误差。比例放大系数的加大,会引起系统的不稳定。

比例(P)控制器:

u(t)=Kp e(t)

I

Integral (积分),就是对输入偏差进行积分运算。

为了减小稳态误差,在控制器中加入积分项,积分项对误差取决于时间的积分,随着时间的增加,积分项会增大。这样,即使误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减少,直到等于零。

积分(I)和比例(P)通常一起使用,称为比例+积分(PI)控制器,可以使系统在进入稳态后无稳态误差。如果单独用积分(I)的话,由于积分输出随时间积累而逐渐增大,故调节动作缓慢,这样会造成调节不及时,使系统稳定裕度下降。

比例+积分(PI)控制器:

PI

D

Differential (微分),对输入偏差进行微分运算(其微分即误差变化率)。

其中,输入偏差=被调量-设定值。

由于自动控制系统有较大的惯性组件(环节)或有滞后(delay)组件,在调节过程中可能出现过冲甚至振荡。解决办法是引入微分 (D)控制,即在误差很大的时候,抑制误差的作用也很大;在误差接近零时,抑制误差的作用也应该是零。

比例+积分+微分(PID)控制器:

在PI的基础上再加

PID

其中,Kp是比例放大系数,Ti是积分时间,Td是微分时间。

总结

比例作用 P 只与偏差成正比;积分作用 I 是偏差对时间的积累;微分作用 D 是偏差的变化率。

PI 比 P 少了稳态误差,PID 比 PI 反应速度更快并且没有了过冲。

调参方法

1. 经验值法

变量 P I D
温度 20~60% 180~600s 3~180s
压力 30~70% 24~180s  
液位 20~80% 60~300s  
流量 40~100% T=6~60s  

2. Ziegler-Nichol 方法

Ziegler-Nichols 方法是基于系统稳定性分析的 PID 整定方法.在设计过程中无需考 虑任何特性要求,整定方法非常简单,但控制效果却比较理想。

方法如下:

  1. 先置 I 和 D 的增益为0,逐渐增加 KP 直到在输出得到一个持续的稳定的振荡。

  2. 记录下振荡时的 P 部分的临界增益 Kc,和振荡周期 Pc,代到下表中计算出 KP, Ti,Td。

controller Kp Ti Td Ki Kd
P 0.5*Kc        
PD 0.65*Kc   0.12*Pc   KpTd
PI 0.45*Kc 0.85*Pc   Kp/Ti  
PID 0.65*Kc 0.5*Pc 0.12*Pc Kp/Ti KpTd

两种PID控制方法

1. 位置式PID控制方法

公式如下:

其中,,

上述 PID 控制算法给出了全部控制量的大小,因此被称为全量式或位置式 PID 控制算法。

缺点

  1. 由于全量输出,所以每次输出均与过去状态有关,计算时要对 e(k)(k=0,1,…n)进行累加,工作量大。

  2. 因为计算机输出的 u(n)对应的是执行机构的实际位置,如果计算机出现故障,输出 u(n)将大幅度变化,会引起执行机构的大幅度变化,有可能因此造成严重的生产事故,这在实际生产中是不允许的。

2. 增量式PID控制方法

公式如下:

其中

位置式PID算法也可由增量式推出:

即现在广泛使用的数字递推 PID 控制算法。

当执行机构需要的不是控制量的绝对值,而是控制量的增量(例如去驱动步进电动机)时,需要用 PID的“增量算法”。

增量式 PID 控制算法与位置式 PID 算法(2-4)相比,计算量小得多,因此在实际中得到广泛的应用。

代码示例

1. 位置式PID控制方法

typedef struct PID {
double SetPoint;
// 设定目标 Desired value
double Proportion;
// 比例常数 Proportional Const
double Integral;
// 积分常数 Integral Const
double Derivative;
// 微分常数 Derivative Const
double LastError;
// Error[-1]
double PrevError;
// Error[-2]
double SumError;
// Sums of Errors
} PID;

double PIDCalc( PID *pp, double NextPoint )
{
double dError, Error;
Error = pp->SetPoint - NextPoint;
// 偏差
pp->SumError += Error;
// 积分
dError = Error - pp->LastError;
// 当前微分
pp->PrevError = pp->LastError;
pp->LastError = Error;
return (pp->Proportion * Error // 比例项
+ pp->Integral * pp->SumError // 积分项
+ pp->Derivative * dError // 微分项
);
}

void PIDInit (PID *pp)
{
memset ( pp,0,sizeof(PID));
}

double sensor (void) // Dummy Sensor Function
{
return 100.0;
}
void actuator(double rDelta) // Dummy Actuator Function
{}
void main(void)
{
PID sPID; // PID Control Structure
double rOut; // PID Response (Output)
double rIn; // PID Feedback (Input)
PIDInit ( &sPID ); // Initialize Structure
sPID.Proportion = 0.5; // Set PID Coefficients
sPID.Integral = 0.5;
sPID.Derivative = 0.0;
sPID.SetPoint = 100.0; // Set PID Setpoint
for (;;) { // Mock Up of PID Processing
rIn = sensor (); // Read Input
rOut = PIDCalc ( &sPID,rIn ); // Perform PID Interation
actuator ( rOut ); // Effect Needed Changes
}

2. 数字递推 PID 控制算法

这里使用的单片机为STC15F5K60S2,此处采用的PID控制的运算公式:

U(k)+KP*[E(k)-E(k-1)]+KI*E(k)+KD*[E(k)-2E(k-1)+E(k-2)]

/* 函数名称:PID_Operation()
/* 函数功能:PID 运算
/* 入口参数:无(隐形输入,系数、设定值等)
/* 出口参数:无(隐形输出,U(k))
/* 函数说明:U(k)+KP*[E(k)-E(k-1)]+KI*E(k)+KD*[E(k)-2E(k-1)+E(k-2)]
******************************************************** */
void PID_Operation(void)
{
uInt32 Temp[3] = {0}; //中间临时变量
uInt32 PostSum = 0; //正数和
uInt32 NegSum = 0; //负数和
if(PID.iSetVal > PID.iCurVal)
//设定值大于实际值否?
{
if(PID.iSetVal - PID.iCurVal > 10) //偏差大于 10 否?
PID.iPriVal = 100;
//偏差大于 10 为上限幅值输出(全速加热)
else
//否则慢慢来
{
Temp[0] = PID.iSetVal - PID.iCurVal; //偏差<=10,计算 E(k)
PID.uEkFlag[1] = 0;
//E(k)为正数,因为设定值大于实际值
/* 数值进行移位,注意顺序,否则会覆盖掉前面的数值 */
PID.liEkVal[2] = PID.liEkVal[1];
PID.liEkVal[1] = PID.liEkVal[0];
PID.liEkVal[0] = Temp[0];
/* ============================================================ */
if(PID.liEkVal[0] > PID.liEkVal[1]) //E(k)>E(k-1)否?
{
Temp[0] = PID.liEkVal[0] - PID.liEkVal[1];
//E(k)>E(k-1)
PID.uEkFlag[0] = 0;
//E(k)-E(k-1)为正数
}
else
{
Temp[0] = PID.liEkVal[1] - PID.liEkVal[0];
//E(k)<E(k-1)
PID.uEkFlag[0] = 1;
//E(k)-E(k-1)为负数
}

/* =========================================================== */
Temp[2] = PID.liEkVal[1] * 2;
//2E(k-1)
if((PID.liEkVal[0] + PID.liEkVal[2]) > Temp[2])
//E(k-2)+E(k)>2E(k-1)否?
{
Temp[2] = (PID.liEkVal[0] + PID.liEkVal[2]) - Temp[2];
PID.uEkFlag[2]=0;
//E(k-2)+E(k)-2E(k-1)为正数
}
else
//E(k-2)+E(k)<2E(k-1)
{
Temp[2] = Temp[2] - (PID.liEkVal[0] + PID.liEkVal[2]);
PID.uEkFlag[2] = 1;
//E(k-2)+E(k)-2E(k-1)为负数
}
/* =========================================================== */
Temp[0] = (uInt32)PID.uKP_Coe * Temp[0];
//KP*[E(k)-E(k-1)]
Temp[1] = (uInt32)PID.uKI_Coe * PID.liEkVal[0]; //KI*E(k)
Temp[2] = (uInt32)PID.uKD_Coe * Temp[2];
//KD*[E(k-2)+E(k)-2E(k-1)]
/* 以下部分代码是讲所有的正数项叠加,负数项叠加 */
/* ========= 计算 KP*[E(k)-E(k-1)]的值 ========= */
if(PID.uEkFlag[0] == 0)
PostSum += Temp[0];
else //正数和
NegSum += Temp[0];
/* ========= 计算 KI*E(k)的值 ========= */
if(PID.uEkFlag[1] == 0) //负数和
PostSum += Temp[1];
else
//正数和
/* 空操作
就是因为 PID.iSetVal > PID.iCurVal(即 E(K)>0)才进
入 if 的,那么就没可能为负,所以打个转回去就是了 */
/* ======== 计算 KD*[E(k-2)+E(k)-2E(k-1)]的值 ======== */
if(PID.uEkFlag[2]==0)
;
PostSum += Temp[2];
else
//正数和
NegSum += Temp[2];
//负数和
/* ========= 计算 U(k) ========= */
PostSum += (uInt32)PID.iPriVal;
if(PostSum > NegSum)
//是否控制量为正数
{
Temp[0] = PostSum - NegSum;
if(Temp[0] < 100 )
//小于上限幅值则为计算值输出
PID.iPriVal = (uInt16)Temp[0];
else PID.iPriVal = 100;    //否则为上限幅值输出
}
else      //控制量输出为负数,则输出 0(下限幅值输出)
PID.iPriVal = 0;
}
}
else PID.iPriVal = 0;      //同上
}

3. 增量式PID算法

typedef struct PID
{
int SetPoint; //设定目标 Desired Value
long SumError; //误差累计
double Proportion; //比例常数 Proportional Const
double Integral; //积分常数 Integral Const
double Derivative; //微分常数 Derivative Const
int LastError; //Error[-1]
int PrevError; //Error[-2]
} PID;
static PID sPID;
static PID *sptr = &sPID;

void IncPIDInit(void)
{
sptr->SumError = 0;
sptr->LastError = 0; //Error[-1]
sptr->PrevError = 0; //Error[-2]
sptr->Proportion = 0; //比例常数 Proportional Const
sptr->Integral = 0; //积分常数 Integral Const
sptr->Derivative = 0; //微分常数 Derivative Const
sptr->SetPoint = 0;
}

int IncPIDCalc(int NextPoint)
{
register int iError, iIncpid;
//当前误差
iError = sptr->SetPoint - NextPoint;
//增量计算
iIncpid = sptr->Proportion * iError
//E[k]项
- sptr->Integral * sptr->LastError
//E[k-1]项
+ sptr->Derivative * sptr->PrevError;
//E[k-2]项
//存储误差,用于下次计算
sptr->PrevError = sptr->LastError;
sptr->LastError = iError;
//返回增量值
return(iIncpid);
}

参考资料 :圆点博士资料
《STC15单片机实战指南》

comments powered by Disqus

Older · View Archive (56)

ADC/DAC 详解

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”>

ADC/DAC 详解

Newer

心率计算R-R间期算法