PID调节
较好的参考
一般使用增量式PID算法,比位置式pid算法计算简单,内存消耗小,计算机输出的是控制机构的增量,即
实际控制量=上次控制量+PID算法输出值
以上才是有营养的东西
----------------
-----------------
以下全是废话,垃圾资料浪费人生
PID是什么
始于1936 年2 月17 日,不用考虑被控对象的数学模型就能调节控制被控对象的一种方法 。PID,就是对输入偏差进行比例积分微分运算,运算的叠加结果去控制执行机构。
什么是PID?P就是比例就是输入偏差乘以一个系数;I 就是积分,就是对输入偏差进行积分运算;D 就是微分,对输入偏差进行微分运算。浅白一点说,就是先把系统调为纯比例作用,然后增强比例作用让系统震荡,记录下刚出现震荡时的比例作用Km和震荡周期T(T=2π/ω),根据下面的式子就可以得到P参数、I参数、D参数(一般情况)
Kp = 0.6*Km
Kd = Kp*π/4*ω
Ki = Kp*ω/π
就像你走路不需要精确知道你每一步走多远,却可以走到目的地一样
基本的PID调节器有两个输入值,被调量和设定值。一般来说,被调量是随系统状态在变化的,设定值是人们期望系统达到的一个固定值。基本的PID调节器还有一个输出量去控制执行机构
在我看来PID调节器就是程序里的一个函数,所谓的调PID就是在调节P参数、I参数、D参数
P 纯比例作用分析
P是比例作用,把调节器的输入偏差乘以P参数,结果作为调节器的输出。
输出=偏差*Kp
所以输出有和偏差类似的形状,得出以下结论,即
对于正作用的调节系统,顶点、谷底均发生在同一时刻。对于负作用的调节系统,被调量的顶点就是输出的谷底,谷底就是输出的顶点。波动周期完全一致。只要被调量变化,输出就变化;被调量不变化,不管静态偏差有多大,输出也不
会变化。
I 纯积分作用分析
如何科学地整定PID?
收集数据画曲线:
* 1 设定值(一般是条直线)
* 2 被调量的波动曲线
* 3 PID的输出曲线
如果是串级调节系统,我们还要收集:
* 4 副调的被调量曲线
* 5 PID 输出曲线
为什么不收集副调的设定值了?因为主调的输出就是副调的设定啊。在一个比较复杂的
调节系统中,副调的被调量往往不只一个,那就有几个收集几个。
PID整定方法,主要是靠玄学的经验试凑法,耗费大量时间去根据经验猜、调
把P、I、D 隔离开来。先去掉积分、微分作用,让系统变为纯比例调节方式。然后
再考虑积分,然后再考虑微分。
整定比例作用比较笨的办法,先把系统设置为纯比例作用,也就是说积
分时间无穷大,微分增益为0。逐渐加大比例作用,一直到系统发生等幅震荡,然后在这
个基础上适当减小比例作用即可,或者把比例增益乘以0.6~0.8。怎么判断震荡呢?一般来说,对于一个简单的单回路调节系统,比例作用很强的时候,振荡周期是很有规律的,基本上呈正弦波形状。。
参数整定找最佳, 从小到大顺序查。
先是比例后积分, 最后再把微分加。
曲线振荡很频繁, 比例度盘要放大。
曲线漂浮绕大弯, 比例度盘往小扳。
曲线偏离回复慢, 积分时间往下降。
曲线波动周期长, 积分时间再加长。
曲线振荡频率快, 先把微分降下来。
动差大来波动慢, 微分时间应加长。
理想曲线两个波, 前高后低四比一。
一看二调多分析, 调节质量不会低。
衡量一个PID控制系统质量的好坏,主要是看在外界干扰产生后,被控量偏离给定值的情况,假如偏离了以后能很快的平稳的回复到给定值,就认为是好的。通常认为图2所示的过渡过程是最好的,并以此作为衡量PID控制系统的质量指标。选用这个曲线作为指标的理由是:因为它第一次回复到给定值较快,以后虽然又偏离了,但是偏离不大,并经过几次振荡就稳定下来了,定量的看:第一个波峰B的高度是第二个波峰B'高度的四倍,所以这种曲线又叫做4:1衰减曲线。在调节器的工程参数整定时,以能得到4:1的衰减过渡过程为最好,这时的PID控制参数可叫最佳参数。 “理想曲线两个波,前高后低4比1”就是指图2这样的曲线,也就是过渡过程振荡两次就能稳定下来,并且振荡两次后有约近于4:1的衰减比,它被认为是最好的过渡过程。
PID 算法示例
#include <reg52.h>
typedef unsigned char uChar8;
typedef unsigned int uInt16;
typedef unsigned long int uInt32;
sbit ConOut = P1^1; //加热丝接到P1.1口
typedef struct PID_Value
{
uInt32 liEkVal[3]; //差值保存,给定和反馈的差值
uChar8 uEkFlag[3]; //符号,1则对应的为负数,0为对应的为正数
uChar8 uKP_Coe; //比例系数
uChar8 uKI_Coe; //积分常数
uChar8 uKD_Coe; //微分常数
uInt16 iPriVal; //上一时刻值
uInt16 iSetVal; //设定值
uInt16 iCurVal; //实际值
}PID_ValueStr;
PID_ValueStr PID; //定义一个结构体,这个结构体用来存算法中要用到的各种数据
bit g_bPIDRunFlag = 0; //PID运行标志位,PID算法不是一直在运算。而是每隔一定时间,算一次。
/* ********************************************************
/* 函数名称: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; //同上,嘿嘿
}
/* ********************************************************
/* 函数名称:PID_Output()
/* 函数功能:PID输出控制
/* 入口参数:无(隐形输入,U(k))
/* 出口参数:无(控制端)
******************************************************** */
void PID_Output(void)
{
static uInt16 iTemp;
static uChar8 uCounter;
iTemp = PID.iPriVal;
if(iTemp == 0)
ConOut = 1; //不加热
else ConOut = 0; //加热
if(g_bPIDRunFlag) //定时中断为100ms(0.1S),加热周期10S(100份*0.1S)
{
g_bPIDRunFlag = 0;
if(iTemp) iTemp--; //只有iTemp>0,才有必要减“1”
uCounter++;
if(100 == uCounter)
{
PID_Operation(); //每过0.1*100S调用一次PID运算。
uCounter = 0;
}
}
}
/* ********************************************************
/* 函数名称:PID_Output()
/* 函数功能:PID输出控制
/* 入口参数:无(隐形输入,U(k))
/* 出口参数:无(控制端)
******************************************************** */
void Timer0Init(void)
{
TMOD |= 0x01; // 设置定时器0工作在模式1下
TH0 = 0xDC;
TL0 = 0x00; // 赋初始值
TR0 = 1; // 开定时器0
EA = 1; // 开总中断
ET0 = 1; // 开定时器中断
}
void main(void)
{
Timer0Init();
while(1)
{
PID_Output();
}
}
void Timer0_ISR(void) interrupt 1
{
static uInt16 uiCounter = 0;
TH0 = 0xDC;
TL0 = 0x00;
uiCounter++;
if(100 == uiCounter)
{
g_bPIDRunFlag = 1;
}
}