标题:
PID算法代码
[打印本页]
作者:
look_w
时间:
2017-9-23 12:42
标题:
PID算法代码
本帖最后由 look_w 于 2017-9-23 16:18 编辑
六、在代码中理解PID:(好好看注释,很好理解的。注意结合下面PID的公式)
首先看PID的增量型公式:
PID=Uk+KP*【E(k)-E(k-1)】+KI*E(k)+KD*【E(k)-2E(k-1)+E(k-2)】
在单片机中运用PID,出于速度和RAM的考虑,一般不用浮点数,这里以整型变量为例来讲述PID在单片机中的运用。由于是用整型来做的,所以不是很精确。但是对于一般的场合来说,这个精度也够了,关于系数和温度在程序中都放大了10倍,所以精度不是很高,但是大部分的场合都够了,若不够,可以再放大10倍或者100倍处理,不超出整个数据类型的范围就可以了。一下程序包括PID计算和输出两部分。当偏差>10度时全速加热,偏差在10度以内时为PID计算输出。
程序说明:下面的程序,先看main函数。可知在对定时器0初始化后就一直在执行PID_Output()函数。在PID_Output()函数中先用iTemp变量来得到PID运算的结果,来决定是启动加热丝加热还是不启动加热丝。下面的if语句结合定时器来决定PID算法多久执行一次。PID_Operation()函数看似很复杂,其实就一直在做一件事:根据提供的数据,用PID公式把最终的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;
}
}
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0