PID算法是控制行业最经典、最简单、而又最能体现反馈控制思想的算法。PID算法的执行流程是非常简单的,即利用反馈来检测偏差信号,并通过偏差信号来控制被控量。而控制器本身就是比例、积分、微分三个环节的加和。其功能框图如下:
根据上图我们考虑在某个特定的时刻t,此时输入量为e(t),输出量为u(t),于是PID的基本控制规律就可以表示为如下公式:
u ( t ) = K p e ( t ) + K i ∫ 0 t e ( τ ) d τ + K d u(t)=K_pe(t)+K_i\int_{0}^{t}e(τ)dτ+K_d u(t)=Kpe(t)+Ki∫0te(τ)dτ+Kd d e ( t ) d t de(t)\over dt dtde(t)
K p K_p Kp是比例增益; K i K_i Ki是积分增益; K d K_d Kd是微分增益;
要在计算机上实现就必须将其离散化,接下来我们就说一说PID算法的离散化问题。在实现离散化之前,我们需要对比例、积分、微分的特性做一个简单的说明。
比例:就是用来对系统的偏差进行反应,所以只要存在偏差,比例就会起作用。因此比例作用相当于某一时刻的偏差(err)与比例系数 K p K_p Kp的乘积。
积分:主要是用来消除静差,所谓静差就是指系统稳定后输入输出之间依然存在的差值,而积分就是通过偏差的累计来抵消系统的静差。积分则是误差在一定时间内的和,满足以下公式;
K i ∫ 0 t e ( τ ) d τ K_i \int_0^t e(τ)dτ Ki∫0te(τ)dτ
微分:则是对偏差的变化趋势做出反应,根据偏差的变化趋势实现超前调节,提高反应速度。微分则是误差变化曲线某处的导数,或者说是某一点的斜率,因此这里需要引入微分;
K d d e ( t ) d t K_d \frac {de(t)}{dt} Kddtde(t)
在数字系统中进行PID算法控制,需要对上述算法进行离散化;假设系统采样时间为 △ t △t △t则将输入 e ( t ) e(t) e(t)序列化得到:
将输出 u ( t ) u(t) u(t)序列化得到:
比例项 : K p e ( t ) K_p e(t) Kpe(t) 离散化 K p e k K_p e_k Kpek
积分项: K i ∫ 0 t k e ( τ ) d τ K_i \int_0^{t_{k}} e(τ)dτ Ki∫0tke(τ)dτ 离散化 K i ∑ i = 1 k e ( i ) △ t K_i\sum_{i=1}^k e(i) △t Ki∑i=1ke(i)△t
微分项: K d K_d Kd d e ( t k ) d t {de(t_k)}\over{dt} dtde(tk)离散化 K d K_d Kd e ( k ) − e ( k − 1 ) △ t e(k)-e(k-1)\over△t △te(k)−e(k−1)
所以最终可以得到式①,也就是网上所说的位置式PID:
u ( k ) = K p e k + K i ∑ i = 1 k e ( i ) △ t + K d u(k)=K_pe_k+K_i\sum_{i=1}^{k}e(i)△t+K_d u(k)=Kpek+Ki∑i=1ke(i)△t+Kd e ( k ) − e ( k − 1 ) △ t e(k)-e(k-1)\over△t △te(k)−e(k−1)
将式①再做一下简化:
△ u ( k ) = u ( k ) − u ( k − 1 ) △u(k)=u(k)-u(k-1) △u(k)=u(k)−u(k−1)
最终得到增量式PID的离散公式如下:
△ u ( k ) = K p ( e ( k ) − e ( k − 1 ) ) + K i e ( k ) + K d ( e ( k ) − 2 e ( k − 1 ) + e ( k − 2 ) ) △u(k)=K_p(e(k)-e(k-1))+K_ie(k)+Kd(e(k)-2e(k-1)+e(k-2)) △u(k)=Kp(e(k)−e(k−1))+Kie(k)+Kd(e(k)−2e(k−1)+e(k−2))
previous_error := 0 //上一次偏差
integral := 0 //积分和
//循环
//采样周期为dt
loop:
//setpoint 设定值
//measured_value 反馈值
error := setpoint − measured_value //计算得到偏差
integral := integral + error × dt //计算得到积分累加和
derivative := (error − previous_error) / dt //计算得到微分
output := Kp × error + Ki × integral + Kd × derivative //计算得到PID输出
previous_error := error //保存当前偏差为下一次采样时所需要的历史偏差
wait(dt) //等待下一次采用
goto loop
#ifndef _PID_SOURCE_
#define _PID_SOURCE_
#include
#include
#include "pid.h"
using namespace std;
class PIDImpl
{
public:
PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki );
~PIDImpl();
double calculate( double setpoint, double pv );
private:
double _dt;
double _max;
double _min;
double _Kp;
double _Kd;
double _Ki;
double _pre_error;
double _integral;
};
PID::PID( double dt, double max, double min, double Kp, double Kd, double Ki )
{
pimpl = new PIDImpl(dt,max,min,Kp,Kd,Ki);
}
double PID::calculate( double setpoint, double pv )
{
return pimpl->calculate(setpoint,pv);
}
PID::~PID()
{
delete pimpl;
}
/**
* Implementation
*/
PIDImpl::PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ) :
_dt(dt),
_max(max),
_min(min),
_Kp(Kp),
_Kd(Kd),
_Ki(Ki),
_pre_error(0),
_integral(0)
{
}
double PIDImpl::calculate( double setpoint, double pv )
{
// Calculate error
double error = setpoint - pv;
// Proportional term
double Pout = _Kp * error;
// Integral term
_integral += error * _dt;
double Iout = _Ki * _integral;
// Derivative term
double derivative = (error - _pre_error) / _dt;
double Dout = _Kd * derivative;
// Calculate total output
double output = Pout + Iout + Dout;
// Restrict to max/min
if( output > _max )
output = _max;
else if( output < _min )
output = _min;
// Save error to previous error
_pre_error = error;
return output;
}
PIDImpl::~PIDImpl()
{
}
#endif
#ifndef _PID_H_
#define _PID_H_
class PIDImpl;
class PID
{
public:
// Kp - proportional gain
// Ki - Integral gain
// Kd - derivative gain
// dt - loop interval time
// max - maximum value of manipulated variable
// min - minimum value of manipulated variable
PID( double dt, double max, double min, double Kp, double Kd, double Ki );
// Returns the manipulated variable given a setpoint and current process value
double calculate( double setpoint, double pv );
~PID();
private:
PIDImpl *pimpl;
};
#endif
#include "pid.h"
#include
int main() {
PID pid = PID(0.1, 100, -100, 0.1, 0.01, 0.5);
double val = 20;
for (int i = 0; i < 100; i++) {
double inc = pid.calculate(0, val);
printf("val:% 7.3f inc:% 7.3f\n", val, inc);
val += inc;
}
return 0;
}
import matplotlib.pyplot as plt
class PID():
def __init__(self,dt,max,min,Kp,Kd,Ki):
self.dt=dt #采样周期
self.max=max #最大调整值
self.min = min #最小调整值
self.Kp = Kp #比例系数
self.Kd = Kd #积分系数
self.Ki =Ki #微分系数
self.integral=0 #积分和
self.previous_error=0 #上一次偏差
self.error=0
def calculate(self,setpoint,measured_value):
self.error =setpoint-measured_value # 计算得到偏差
P_out=self.Kp*self.error #计算出比例值
self.integral=self.integral+self.error*self.dt #计算得到积分累加和
I_out=self.Ki*self.integral #计算出积分值
derivative = (self.error-self.previous_error)/self.dt
D_out = self.Kd*derivative #计算出微分值
self.previous_error=self.error #保存上一次偏差
output=(P_out+I_out+D_out)
if output>self.max:
output =self.max
if output<self.min:
output = self.min
return output
list_x=list()
list_y=list()
testPID =PID(0.1, 100, -100, 0.1, 0.01, 0.5);
val=20
for point in range(0,200):
inc =testPID.calculate(0,val)
val += inc
list_x.append(point)
list_y.append(inc)
plt.scatter(list_x,list_y,alpha=0.6,edgecolors='white')
# plt.plot(list_x,list_y)
plt.legend()
plt.show()