Chapter 5. The PID Emulator (emul.g)

Table of Contents

5.1. The PID Controller Class - PID_Controller
5.2. The Plant Model - change_procvar
5.3. Automatically Change SP Value - auto_control
5.4. Emulating the PID Loop - change_values
5.5. Complete program - emul.g

The PID Emulator creates a mathematically simulated PID loop. This chapter presents the code in several annotated sections. The complete code is shown in the section emul.g - The PID Emulator program in the View the Code chapter.

5.1. The PID Controller Class - PID_Controller

This class (created with the Gamma class statement) contains the variables and methods used to create the PID Controller, which controls the MV_001 datahub point corresponding to the MV: Control output display in the Monitor.

   /********************************************************
    *                PID Controller Class                  *
    ********************************************************/
   
   /*--------------------------------------------------------------------
    * Class:       PID_Controller
    * Description: Contains data and methods for creating and maintaining
    *              a simulated PID loop.
    *------------------------------------------------------------------*/
   class PID_Controller
   {
     name;
   
     errorPrev;
     errorInt = 0;
     errorDrv = 0;
     
     kProp;
     kInt;
     kDrv;
   }
   

Using a Gamma method statement, we create the .init method, which sets up the three datahub points corresponding to the proportional, integral, and derivative values (.kProp, .kInt, and .kDrv). With Gamma's symbol and string functions, it names these points by using the class name instance variable conjoined with a suffix (_Kp, _Ki, or _Kd). This allows for the creation of multiple controller classes, each with their own unique datahub points.

Here also is the first use of Gamma's local statement, which allows us to define variables in a local scope.

   /*--------------------------------------------------------------------
    * Method:      PID_Controller.init
    * Returns:     a name
    * Description: Initializes the PID control loop by creating three
    *              variables representing proportion, integration, and
    *              derivative.
    *------------------------------------------------------------------*/
   method PID_Controller.init (name)
   {
     local val;
     
     .name = name;
     
     .kProp = symbol(string(name, "_Kp"));
     val = register_point(.kProp);
     if (number_p(val) && (conf(val) != 0))
       set(.kProp, val);
     else
       write_point(.kProp, 0.05);
   

We use the Gamma function register_point to register the points with the datahub. Then we check to be sure that the value of the point is a number (using the Gamma number_p predicate function) and that it has a confidence greater than 0 (using Gamma's conf function). If so, it is OK to set our variable to the value of the point. In doing this, we need to evaluate .kProp, so we use Gamma's set function. If the value of the point is not a number or has a confidence of 0, we overwrite the point with a default value corresponding to Poor in the Monitor's PID Loop options, using the Gamma function write_point The other two points are done in similar fashion.

[Note]

The Gamma function conf is used to check the confidence of each of these points in the datahub, and if necessary write a default value whose confidence is 100. If we don't do this, when the program first starts and the datahub hasn't run yet, the confidence for these points will be 0, and their values won't appear in the Monitor.

     .kInt = symbol(string(name, "_Ki"));
     val = register_point(.kInt);
     if (number_p(val) && (conf(val) != 0))
       set(.kInt, val);
     else
       write_point(.kInt, 0.7);
     
     .kDrv = symbol(string(name, "_Kd"));
     val = register_point(.kDrv);
     if (number_p(val) && (conf(val) != 0))
       set(.kDrv, val);
     else
       write_point(.kDrv, 0.05);
     .name;
   }
   

This next method performs the calculations for changing the value of MV_001. It finds the difference between SP_001 and PV_001, approximates an integral and a derivative, and uses this information to create a new value for MV_001.

   /*--------------------------------------------------------------------
    * Method:      PID_Controller.calculate
    * Returns:     an approximate value for MV, as a real number
    * Description: Sets and writes a new MV value.
    *------------------------------------------------------------------*/
   method PID_Controller.calculate (sp, mv, pv)  
   {                                             
     local error, res;
     
     error = eval(sp) - eval(pv);
   
     /* Appproximate the integral. */
     .errorInt += error;       
   
     /* Prevent error "wind-up" effect. */
     if (.errorInt < 0)        
       .errorInt = 0;
   
     /* Approximate the derivative. */
     if (.errorPrev)           
       .errorDrv = error - .errorPrev;
     .errorPrev = error;
     
     res = (eval(.kProp) * error
            + eval(.kInt) * .errorInt
            + eval(.kDrv) * .errorDrv);
     res = floor (res * 100) / 100;
     

The names for the three points are passed in by the change_values function protected from evaluation. In order to get their current values, we use the Gamma eval function.

To best emulate a real-world situation, we set boundaries for our controller at 0 and 200. This gives us enough leeway to easily control the plant between values of 0 and 100, and it keeps all the values positive.

However, this has an effect on the integral error value (.errorInt). If SP_001 changes value suddenly from very high to very low, the error "winds up" to a very high negative value (such as -400) and it takes half a minute or so to return to a positive value. This causes a delay in the updating of MV_001 and PV_001. To prevent this effect, we set a lower limit of 0 on the integral error.

We also set the confidence of res to 100 so that the datahub point MV_001 will have a confidence of 100.

     /* Set boundary conditions. */
     if (res > 200)            
       res = 200;
     if (res <= 0)
       res = 0;  
     write_point (mv, res);
     res;
   }