#include "PID.h"
/*
将偏差的比例(Proportion)、积分(Integral) 和微分(Differential) 通过线性
组合构成控制量,用这一控制量对被控对象进行控制,这样的控制器称 PID 控制器。
常规的模拟 PID 控制系统原理框图如图所示:
*****************************************************************************
* ┌──> 比例 ─────┐ *
* │ +↓ *
* + ┌─┬─┐ │ + ┌─┬─┐ *
* r(t)────>├─┼─┤───e(t)───┼──> 积分 ──>├─┼─┤───u(t)───> 被控对象 ───y(t)───>*
* └─┴─┘ │ └─┴─┘ │ *
* ↑ │ +↑ │ *
* │- └──> 微分 ─────┘ │ *
* │ │ *
* │ │ *
* └───────────────────────────────────────────────────────┘ *
*****************************************************************************
该系统由模拟控制器和被控对象组成。
图中, r(t) 是给定值, y(t) 是系统的实际输出值,给定值与实际输出值构成控制偏差
e(t) = r(t) − y(t) (式 1-1)
e(t) 作为 PID 控制的输入, 作为 PID 控制器的输出和被控对象的输入。
所以模拟 PID 控制器的控制规律为:
┌ 1 ╭t de(t) ┐
u(t) = Kp*│ e(t) + ───* / e(t)dt + Td*───── │ (式 1-2)
└ Ti ╯ 0 dt ┘
其中:Kp -- 控制器的比例系数
Ti -- 控制器的积分时间,也称积分系数
Td -- 控制器的微分时间,也称微分系数
*** 位置式 PID 算法 *********************************************************
由于计算机控制是一种采样控制,它只能根据采样时刻的偏差计算控制量,而不能像
模拟控制那样连续输出控制量量,进行连续控制。由于这一特点(式 1-2)中的积分项
和微分项不能直接使用,必须进行离散化处理。离散化处理的方法为:以T作为采样周期,
k作为采样序号,则离散采样时间kT对应着连续时间t,用矩形法数值积分近似代替积分,
用一阶后向差分近似代替微分。为表示方便将类似于 e(kT)简化成 e(k) 等。
可作如下近似变换:
┌ t ≈ kT (k = 0,1,2,......)
│
│ ╭t k k
│ / e(t)dt ≈ T*∑e(jT) = T*∑e(j) (式 2-1)
│ ╯ 0 j=0 j=0
│
│ de(t) e(kT)-e[(k-1)T] e(k)-e(k-1)
│ ───── ≈ ─────────────── = ───────────
└ dt T T
┌ T k e(k)-e(k-1) ┐
u(k) = Kp*│ e(k) + ───* ∑e(j) + Td*──────────── │ (式 2-2)
└ Ti j=0 T ┘
或
k
u(k) = Kp*e(k) + Ki*∑e(j) + Kd*[e(k)-e(k-1)] (式 2-3)
j=0
其中:Ki ――积分系数, Ki=Kp*T/Ti ;
Kd ――微分系数, Kd=Kp*Td/T ;
k ―― 采样序号, k =0,1,2,……;
u(k) ―― 第 k 次采样时刻的计算机输出值;
e(k) ―― 第 k 次采样时刻输入的偏差值,
e(k-1) ―― 第 k -1 次采样时刻输入的偏差值;
如果采样周期足够小,则(式 2-2)或(式 2-3)的近似计算可以获得足够精确的结果,离
散控制过程与连续过程十分接近。
(式 2-2)或(式 2-3) 表示的控制算法式直接按(式 1-2) 所给出的 PID 控制规律定义进
行计算的,所以它给出了全部控制量的大小,因此被称为全量式或位置式 PID 控制算法。
这种算法的缺点是: 由于全量输出, 所以每次输出均与过去状态有关, 计算时要对e(k)进行累加,
工作量大; 并且,因为计算机输出的u(k)对应的是执行机构的实际位置, 如果计算机出现故障,输出
的u(k)将大幅度变化,会引起执行机构的大幅度变化,有可能因此造成严重的生产事故,这在实生产
际中是不允许的
增量式 PID 控制算法可以避免着重现象发生。
*** 增量式 PID 算法 *********************************************************
所谓增量式 PID 是指数字控制器的输出只是控制量的增量∆u(k) 。 当执行机构需要的控制量是增
量,而不是位置量的绝对数值时,可以使用增量式 PID 控制算法进行控制。
增量式 PID 控制算法可以通过(式 2-2)推导出。由(式 2-2)可以得到控制器的第 k-1
个采样时刻的输出值为:
┌ T k-1 e(k-1)-e(k-2) ┐
u(k-1) = Kp*│ e(k-1) + ───* ∑e(j) + Td*───────────── │ (式 2-4)
└ Ti j=0 T ┘
将(式 2-2)与(式 2-4)相减并整理,就可以得到增量式 PID 控制算法公式为:
┌ T e(k)-2e(k-1)+e(k-2) ┐
∆u(k) = u(k)-u(k-1) = Kp*│ e(k)-e(k-1) + ───e(k) + Td*─────────────────── │
└ Ti T ┘
T Td 2Td Td
= Kp*(1 + ─── + ───)*e(k) - Kp*(1 + ────)*e(k-1) + Kp*───*e(k-2) (式 2-5)
Ti T T T
=A*e(k) + B*e(k-1) + C*e(k-2)
T Td 2Td Td
其中:A = Kp*(1 + ─── + ───); B = Kp*(1 + ────); C = Kp*───
Ti T T T
由(式 2-5)可以看出,如果计算机控制系统采用恒定的采样周期T ,一旦确定 A、 B、 C,
只要使用前后三次测量的偏差值,就可以由(式 2-5)求出控制量。
增量式 PID 控制算法与位置式 PID 算法(式 2-2)相比,计算量小的多,因此在实际中得到
广泛的应用。
而位置式 PID 控制算法也可以通过增量式控制算法推出递推计算公式:
u(k) = u(k-1) + ∆u(k) (式 2-6)
(式 2-6)就是目前在计算机控制中广泛应用的数字递推 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;
/*
描述:PID 所用到的 RAM 清零
输入:无
输出:无
注意:请在设置参数前使用。
例子:PIDInit();
*/
void PIDInit(void)
{
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;
sptr->SumError = 0;
}
/*
描述:设置 PID 调节的目标值
输入:期望值
输出:无
注意:无
例子:PIDSetPoint(2000); //期望电动机的转速为 2000rpm
*/
void PIDSetPoint(int setpoint)
{
sptr->SetPoint = setpoint;
}
/*
描述:读取 PID 调节设置的目标值
输入:无
输出:所设置的期望值
注意:得到的期望值将和数值的是同一个数值
例子:uiSpeed = PIDSetPoint(); //读取所设置的期望电动机转速
*/
int PIDGetSetpoint(void)
{
return(sptr->SetPoint);
}
/*
描述:设置 PID 的 Kp 值
输入:Kp 数值
输出:无
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Kp 就是比例系数
增量式 PID 中 Kp 相当于 e(k) 的系数Kp(1+T/Ti+Td/T)
例子:PIDSetKp(0.257); //设置 Kp=0.257
*/
void PIDSetKp(double dKpp)
{
sptr->Proportion = dKpp;
}
/*
描述:读取 PID 中所设置的 Kp 值
输入:无
输出:Kp 数值
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Kp 就是比例系数
增量式 PID 中 Kp 相当于 e(k) 的系数Kp(1+T/Ti+Td/T)
例子:Kp = PIDSetKp();
*/
double PIDGetKp(void)
{
return(sptr->Proportion);
}
/*
描述:设置 PID 的 Ki 值
输入:Ki 数值
输出:无
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Ki 是积分系数Kp*(T/Ti)
增量式 PID 中 Ki 是 e(k-1) 的系数Kp*(1+2*Td/T)
例子:PIDSetKi(0.367); //设置 Ki=0.367
*/
void PIDSetKi(double dKii)
{
sptr->Integral = dKii;
}
/*
描述:读取 PID 中所设置的 Ki 值
输入:无
输出:Ki 数值
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Ki 是积分系数Kp*(T/Ti)
增量式 PID 中 Ki 是 e(k-1) 的系数Kp*(1+2*Td/T)
例子:dKi = PIDSetKi();
*/
double PIDGetKi(void)
{
return(sptr->Integral);
}
/*
描述:设置 PID 的 Kd 值
输入:Kd 数值
输出:无
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Kd 是积分系数Kp*(Td/T)
增量式 PID 中 Kd 是 e(k-2) 的系数Kp*(Td/T)
例子:PIDSetKd(0.157); //设置 Kd=0.157
*/
void PIDSetKd(double dKdd)
{
sptr->Derivative = dKdd;
}
/*
描述:读取 PID 中所设置的 Kd 值
输入:无
输出:Kd 数值
注意:这个参数在增量 PID 和位置 PID 的计算中代表着不同的意思。
位置式 PID 中 Kd 是积分系数Kp*(Td/T)
增量式 PID 中 Kd 是 e(k-2) 的系数Kp*(Td/T)
例子:dKd = PIDGetKd();
*/
double PIDGetKd(void)
{
return(sptr->Derivative);
}
/*
描述:增量式 PID 计算
输入:PID 调节当前采样值
输出:计算增量
注意:增量式 PID 算法的实现。
例子:uiGoalvalue += IncPIDCalc (1998); //位置式 PID 控制算法通过增量式控
制算法递推实现,当前采样得到转速 1998rpm。
*/
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);
}
/*
描述:位置式 PID 计算
输入:PID 调节当前采样值
输出:位置式 PID 计算出的绝对位置值
注意:位置式式 PID 算法的实现。
例子:uiGoalvalue = LocPIDCalc (1998); //位置式 PID 控制算法,当前采样得到
转速 1998rpm。
*/
unsigned int LocPIDCalc(int NextPoint)
{
register int iError,dError;
iError = sptr->SetPoint - NextPoint; // 偏差
sptr->SumError += iError; // 积分
dError = iError - sptr->LastError; // 微分
sptr->LastError = iError;
return(sptr->Proportion * iError // 比例项
+ sptr->Integral * sptr->SumError // 积分项
+ sptr->Derivative * dError); // 微分项
}
//=============================================//
// *END*
//=============================================//
#ifndef __PID_H
#define __PID_H
#include "stm32f0xx.h"
extern void PIDInit(void);
extern void PIDSetPoint(int setpoint);
extern int PIDGetSetpoint(void);
extern void PIDSetKp(double dKpp);
extern double PIDGetKp(void);
extern void PIDSetKi(double dKii);
extern double PIDGetKi(void);
extern void PIDSetKd(double dKdd);
extern double PIDGetKd(void);
extern int IncPIDCalc(int NextPoint);
extern unsigned int LocPIDCalc(int NextPoint);
#endif /* __PID_H */