本帖最后由 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;
- }
- }
|