加载中…
个人资料
ParkerWu
ParkerWu
  • 博客等级:
  • 博客积分:0
  • 博客访问:28,065
  • 关注人气:44
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
正文 字体大小:

PLC的PID控制器代码

(2015-11-23 10:05:52)
标签:

pid

plc

增量式

位置式

     各PLC厂家都有自己的PID功能块或者指令,但为什么还要自己编写PID控制器呢?其目的主要是为了改进PID的控制,而各厂家提供的标准PID功能块是无法修改的。

      以下语言使用的是西门子的SCL语言编写,它是符合国际标准的语言,很容易转换成其他PLC语言,或者是计算机控制语言。

      首先给出PLC的增量式控制代码:

      (*增量式PID控制器*)
FUNCTION_BLOCK PID_INC
   VAR_INPUT                    // Input Parameters
      Manu:BOOL:=false;               //确实是手动控制输出,还是由PID自动控制
      Init:Bool:=false;               //参数初始化
      Start:Bool:=false;             //启动/停止PID或者强制手动输出
      IsSmooth:=false;               //确定是否平滑的启动
      MVH:Real:=100;                 //输出上限
      MVL:Real:=0;                   //输出下限
      MVSet:Real:=0;                 //手动设定值
      Emin:Real:=0;                  //死区设定
      PV:Real:=0;                    //当前的反馈值
   END_VAR

   VAR_IN_OUT                    // input/Output Parameters
   END_VAR

   VAR_OUTPUT                    // Output Parameters
      MV:Real:=0;                    //输出结果
   END_VAR

   VAR                    // Static Variables
      ek:Real:=0;                    //当前偏差值
      ek1:Real:=0;                    //前一次偏差值
      ek2:Real:=0;                    //前两次偏差值
      Kp:Real:=1;                    //比例系数
      Ti:Real:=32767;                //积分时间
      Td:Real:=0;                    //微分时间
      Ts:Real:=0;                    //数字PID运算的控制周期
      lastMV:Real:=0;               //上一次的输出值
      SP:Real:=0;                    //设定值
      SmoothFlag:Int:=0;             //平滑标识
   END_VAR

   VAR_TEMP                    // Temporary Variables
   END_VAR
   
   (*增量式PID计算公式为
     U(t)=Kp*{[ek(t)-ek(t-1)+Ts/Ti*ek(t)+Td/Ts*[ek(t)-2*ek(t-1)+ek(t-2)]]}+U(t-1)
     在手动模式下Manu=true,Start=true时,输出手动设定值;而当Manu=true,Start=false时,
     输出值维持不变;在Manu=true,Start=true时,启动PID的自动调节;
     该功能块放到PLC的定时中断程序中相当于设定了固定执行周期,也可以尝试放到循环扫描程序中,
     或者在循环扫描程序中用脉冲波控制Start参数
     本功能块包含死区限定和输出幅值限定*)
     
   IF Init THEN (*初始化PID参数,一般用于第一次执行PID时初始化*)
      ek1:=0.0;
      ek2:=0.0;
      lastMV:=0.0;
      SmoothFlag:=0;
      MV:=0;
      RETURN;
   END_IF;      
   
   IF Start THEN (*启动命令*)
       IF Manu THEN (*手动模式*)
           MV:=MVSet;
           lastMV:=MV;(*用于切换到自动时,输出的平滑*)
           ek1:=0.0;
           ek2:=0.0;
           SmoothFlag:=0;
       ELSE  (*自动模式*)
           ek:=SP-PV; (*计算偏差值*)
           IF abs(ek) <</span> Emin THEN (*位于控制死区*)
               MV:=lastMV;
           ELSE IF IsSmooth & SmoothFlag<</span>4 THEN (*处于平滑过渡期*)
               MV:=lastMV;
               SmoothFlag:=SmoothFlag+1;
           ELSE 
               MV:=Kp*(ek-ek1+Ts*ek/Ti+Td*(ek-ek1*2+ek2)/Ts)+lastMV;
               IF MV>MVH THEN (*限制输出幅值*)
                  MV:=MVH;
               ELSE IF  MV<</span>MVL THEN
                  MV:=MVL;
               END_IF;
               lastMV:=MV; (*储存前一次的输出值*)            
           END_IF; 
           ek2=ek1; (*储存前两次的误差值*)
           ek1=ek;  (*储存上一次的误差值*) 
       END_IF;
   ELSE (*停止命令*)
       SmoothFlag:=0;
   END_IF;
END_FUNCTION_BLOCK

      最开始研究PID公式时,很多网上的资料说增量式比位置式好,如是并不想搞位置式,后来了解深入后发现,增量式在一些步进控制,也就是仅输出递增量的场合,优势明显,但当输出全量:U(k)=U(k-1)+dU(k)时,和位置式相比并没有优点:
  1 全量递增式确实可以避免积分饱和,但可能出现所谓的比例/微分饱和,即比例/微分的控制量过大时,输出的限幅会将超过的量舍弃掉,累积下去最终会导致调节过慢,可使用"累积补偿"算法,但此算法由于涉及到累积,又会产生类似积分饱和的现象,相反位置式算法有抗积分饱和/积分分离等多种算法来避免饱和,并且算法很简单。
  2 对于位置式,还有不完全微分/微分先行/比例先行等很多改进算法,而增量式相应的算法很复杂或者没有,譬如不完全微分,针对增量式,三篇文章就有三个不同的公式,且较复杂,个人推导了一下,感觉有问题。
  下面给出了位置式的较为完整的PID算法,如有算法和公式上的问题,望指出:

   (*位置式PID控制器*)
FUNCTION_BLOCK PID_PosExpT
   TITLE = 'PID'
   VERSION:'1'
   AUTHOR:parker
   NAME: PID
   FAMILY: PID
   VAR_INPUT                    // Input Parameters
      Manu:BOOL:=false;               //确实是手动控制输出,还是由PID自动控制
      Init:Bool:=false;                  //参数初始化
      Start:Bool:=false;                 //启动/停止PID(维持信号)
      IsHSPID:Bool:=false;               //是否使用不完全微分(=1,采用)
      IsDSpeed:Bool:=false;              //是否使用变速积分(=1,采用)
      MVH:Real:=100;                    //输出上限
      MVL:Real:=0;                    //输出下限
      MVSet:Real:=0;                 //手动设定值
      Emin:Real:=0;                  //死区设定      
      PV:Real:=0;                    //当前的反馈值
   END_VAR

   VAR_IN_OUT                    // input/Output Parameters
   END_VAR

   VAR_OUTPUT                    // Output Parameters
      MV:Real:=0;                    //输出结果
   END_VAR

   VAR                    // Static Variables
      ek:Bool:=0;                    //当前偏差值
      ek1:Bool:=0;                    //前一次偏差值
      integral:Real:=0;               //积分值/偏差累计
      Kp:Real:=1;                    //比例系数
      Ti:Real:=32767;                //积分时间
      Td:Real:=0;                    //微分时间
      Ts:Real:=0;                    //数字PID运算的控制周期(采样周期)
      Tf:Real:=0;                    //不完全微分滤波器系数(a=Tf/(Ts+Tf))
      SP:Real:=0;                    //设定值
      IUse:Real:=100;                 //积分分离法中确定偏差值在所属范围内启用积分项
      IVar:Real:=100;                 //变积分控制参数
      lastUd:Real:=0;                 //上一次的微分项值
      lastMV:Real:=0;                //上一次的MV输出值
   END_VAR

   VAR_TEMP                    // Temporary Variables
       pterm:Real;     //比例项
       dterm:Real;     //微分项
       IsISum:bool;    //通过输出限幅确定是否累加积分项
   END_VAR

(*位置式PID计算公式为
     U(t)=Kp*ek(t)+(integral(t-1)+Kp*ek(t)*Ts/Ti)+Kp*Td(ek(t)-ek(t-1))/Ts
     在手动模式下Manu=true,Start=true时,输出手动设定值;而当Manu=true,Start=false时,
     输出值维持不变;在Manu=true,Start=true时,启动PID的自动调节;
     该功能块放到PLC的定时中断程序中相当于设定了固定执行周期,也可以尝试放到循环扫描程序中,
     或者在循环扫描程序中用脉冲波控制Start参数
     /////////////////本功能块包含//////////////////////////////////////
     死区限定/输出幅值限定/抗积分饱和/积分分离法/变速积分/不完全微分
     这些功能可以彼此完美叠加使用,通过适当的参数设定,可以旁路部分功能,其中积分分离法在IUse
     设定的比较大时相当于屏蔽该功能,而变速积分可通过参数IsDSpeed控制,而不完全微分可通过参数
     IsHSPID进行控制,死区限定值Emin=0时,相当于屏蔽此功能,而抗积分饱和法则始终起作用,不能被屏蔽,
     当然把积分Ti设定很大时,相当于屏蔽积分功能,仅使用PD控制器,此时各种积分算法无意义,
     Td=0时相当于屏蔽微分功能,仅使用PI控制器
      ///////////////有关幅值的设定////////////////////////////
     1.对于单控制对象,譬如只有一个热水调节阀,输出幅值为 0-->+模拟量输出上限
     2.对于双控制对象,譬如含热水阀和冷水阀,输出幅值为 -模拟量输出上限-->+模拟量输出上限,并对
       这个PID的输出进行正负的分段处理。
       ////////////抗积分饱和法///////////////////////////////
       该方法的思路是在计算u(k)时,首先判断上一时刻的控制量u(k-1)是否已经超出了极限范围,
       如果u(k-1)>umax则只累加负偏差;如果u(k-1)
       ////////////积分分离法//////////////////////////////////
       当偏差ek
       相当于旁路积分分离功能
       ////////////变速积分///////////////////////////////////
       变速积分法的基本思想是在偏差较大时积分慢一些,而在偏差较小时积分快一些,以尽快消除静差
       如果使用了变速积分,总体上比不使用时的积分效果慢,其功能必须是在积分分离中接通了积分功能后,
       才会起作用。
      ////////////////不完全微分///////////////////////////////
       微分项公式为 Ud(k)=Kd(1-a)[e(k)-e(k-1)]+a*Ud(k-1),其中a=Tf/(Ts+Tf)      
       *)     
   IF Init THEN (*初始化PID参数,一般用于第一次执行PID时初始化*)
      ek1:=0.0;
      integral:=0.0;
      MV:=0;  
      lastMV:=0.0; 
      lastUd:=0.0;      
      RETURN;
   END_IF;
   IF Manu THEN 
      IF Start THEN (*设置手动输出,并清空误差*)
         ek1:=0.0;
         integral:=0.0;
         MV:=MVSet;
         lastMV:=MV;(*记录上一次的输出值*)
         lastUd:=0.0;
      END_IF;
    ELSE 
      IF Start THEN (*启动PID调节计算*)
         ek:=SP-PV; (*计算偏差值*)
         IF abs(ek) > Emin THEN (*位于死区外*)
            (*比列项*)
            pterm:=kp*ek;
            (*积分项*)                
            IF abs(ek)<</span>IUse THEN  (*积分分离法:差值在某个小区间才启用积分项*) 
               (*抗积分饱和:当上一次的输出达到饱和后,且偏差朝反方向变化,则可累加积分*)
               IsISum:=((lastMV<</span>MVH-0.01) & (lastMV>MVL+0.01))
                      OR ((lastMV>=MVH-0.01) & ek<=0)
                      OR ((lastMV<=MVL+0.01) & ek>=0);
               IF IsISum THEN (*抗积分饱和*)
                   IF IsDSpeed THEN (*使用变速积分*)
                       integral=integral+kp*Ts*ek*(1-abs(ek)/IVar)/Ti;
                   ELSE 
                       integral=integral+kp*Ts*ek/Ti;
                   END_IF;
               END_IF;
            ELSE 
               integral=0.0;
            END_IF;                
            
            (*微分项*)
            IF IsHSPID THEN (*采用不完全微分*)
                 dterm:=(ek-ek1)*Kp*Td/(Ts+Tf)+Tf*lastUd/(Ts+Tf);
            ELSE 
                 dterm:=(ek-ek1)*Kp*Td/Ts;
            END_IF;
            (*输出结果*)
            MV:=pterm+integral+dterm;
            IF MV>MVH THEN (*限制输出幅值*)
               MV:=MVH;
            ELSE IF  MV<</span>MVL THEN
               MV:=MVL;
            END_IF;
            lastMV:=MV;(*记录上一次的输出值*)
            lastUd:=dterm;(*储存上一次的微分项值*)
         ELSE (*位于控制死区*)
            MV:=lastMV;                 
         END_IF;
         ek1=ek;  (*储存上一次的误差值*)
      END_IF;      
   END_IF;  
   
END_FUNCTION_BLOCK


    

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有