/*--------------------------------------------------------------------
* File: emul.g
*
* Description: A Gamma program that simulates a PID-controlled loop.
*
* Functions:
* PID_Controller.init
* PID_Controller.calculate
* change_procvar
* auto_control
* change_values
*------------------------------------------------------------------*/
/* Allow more than one emulator to run at a time, with the default being
("toolsdemo", "emul", "emulq") */
if (length(argv) > 1)
{
demodomain = cadr(argv);
demoname = string ("emul", demodomain);
demoqueue = string ("emulq", demodomain);
}
else
{
demodomain = "toolsdemo";
demoname = "emul";
demoqueue = "emulq";
}
/* Initialize interprocess communication for this process */
if (init_ipc(demoname, demoqueue, demodomain) == nil)
error("Could not initialize IPC.");
/* See if a DataHub is running in the toolsdemo domain. If not, start it. */
if (locate_task (string ("/dh/", demodomain), t) == nil)
system(string ("datahub -d ", demodomain));
/* We need to try to register something or the auth is refused. */
SP_001 = register_point(#SP_001); // The set point variable.
datahub = locate_task (string ("/dh/", demodomain), t);
result = send (datahub, #auth("writer", "writepass"));
/* Register the first points with the DataHub. */
SP_001 = register_point(#SP_001); // The set point variable.
MV_001 = register_point(#MV_001); // The manipulated variable.
PV_001 = register_point(#PV_001); // The process variable.
cycle_count = 0; // Counter for number of cycles
/* Ensure that the confidence values are set to 100, or the
* logger will not log the values that we produce. The computation
* of PV and MV are relative to previous values, so they preserve
* whatever confidence we initially set. */
SP_001 = set_conf (SP_001, 100);
MV_001 = set_conf (MV_001, 100);
PV_001 = set_conf (PV_001, 100);
/* Create a global variable for demonstrating gsend and lsend. */
demo_send = 1;
function force_point(pointname, value)
{
set (pointname, value);
if (!datahub)
{
datahub = locate_task (string ("/dh/", demodomain), t);
}
if (datahub)
{
if (!send (datahub, `force(@pointname, @value)))
{
datahub = nil;
force_point(pointname, value);
}
}
}
/********************************************************
* 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;
}
/*--------------------------------------------------------------------
* 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
force_point(.kProp, 0.05);
.kInt = symbol(string(name, "_Ki"));
val = register_point(.kInt);
if (number_p(val) && (conf(val) != 0))
set(.kInt, val);
else
force_point(.kInt, 0.7);
.kDrv = symbol(string(name, "_Kd"));
val = register_point(.kDrv);
if (number_p(val) && (conf(val) != 0))
set(.kDrv, val);
else
force_point(.kDrv, 0.05);
.name;
}
/*--------------------------------------------------------------------
* 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;
/* Set boundary conditions. */
if (res > 200)
res = 200;
if (res <= 0)
res = 0;
force_point (mv, res);
res;
}
/********************************************************
* The Plant Model *
********************************************************/
/* The proportion and integral for PV. */
PROP_001 = register_point(#PROP_001);
INT_001 = register_point(#INT_001);
/* Check and correct out-of-range values,
* by writing default "Poor" values.*/
if (PROP_001 < .01 || PROP_001 > 10)
force_point(#PROP_001, .7);
if (INT_001 <= 0 || INT_001 > 10)
force_point(#INT_001, 5);
/*--------------------------------------------------------------------
* Function: change_procvar
* Returns: t on success, nil on failure
* Description: Sets and writes a new PV value.
*------------------------------------------------------------------*/
function change_procvar (sptag, mvtag, pvtag) //
{
local diff = eval(mvtag) * PROP_001 - eval (pvtag),
newval = eval(pvtag) + diff / INT_001;
// princ ((long_p(eval(pvtag)) ? "long " : "float "),
// "pv: ", eval(pvtag), ":", conf(eval(pvtag)), "\n");
newval = floor (newval * 100) / 100;
newval = newval * demo_send;
force_point (pvtag, newval);
}
/********************************************************
* Automatically Change SP Value *
********************************************************/
/* Initialize a global variable for the automatic timer. */
AUTO_TIMER = nil;
AUTO_RESET_TIMER = nil;
/* Register points, exceptions, and echos with the DataHub.
* The FREQ_001 variable holds the delay for automatic change of SP. */
AUTO_001 = register_point(#AUTO_001);
add_exception_function(#AUTO_001, #(AUTO_TIMER = auto_control(FREQ_001)));
add_echo_function(#AUTO_001, #(AUTO_TIMER = auto_control(FREQ_001)));
FREQ_001 = register_point(#FREQ_001);
if (FREQ_001 < 1 || FREQ_001 > 20)
force_point(#FREQ_001, 8);
add_exception_function(#FREQ_001, #(AUTO_TIMER = auto_control(FREQ_001)));
function round2(x)
{
floor (x * 100) / 100;
}
/*--------------------------------------------------------------------
* Function: auto_control
* Returns: a timer number
* Description: Sets a timer to change the value of SP at a specified
* frequncy.
*------------------------------------------------------------------*/
function auto_control(freq)
{
if (AUTO_001 != 0)
{
/* Start a timer to move SP to random settings. */
if (AUTO_TIMER == nil)
{
if (45 < SP_001 && SP_001 < 55)
force_point(#SP_001, 80);
else
force_point(#SP_001, 50);
AUTO_TIMER = every(eval(freq),
`force_point(#SP_001, round2(random() * 100)));
}
else
{
/* Cancel an existing timer. */
cancel(AUTO_TIMER);
AUTO_TIMER = nil;
auto_control(FREQ_001);
}
}
else
{
/* Cancel the timer if the AUTO_001 button is not pressed. */
if (AUTO_TIMER != nil)
{
cancel(AUTO_TIMER);
AUTO_TIMER = nil;
}
if (AUTO_RESET_TIMER)
cancel (AUTO_RESET_TIMER);
AUTO_RESET_TIMER = after (60, #go_auto());
}
AUTO_TIMER;
}
/* Automatically revert to automatic setpoint change mode */
function go_auto ()
{
if (AUTO_001 == 0)
force_point (#AUTO_001, 1);
}
/* When the setpoint changes and the automatic setpoint mode is off,
it means that a user has changed the setpoint manually, so reset
the timer that throws us back into automatic setpoint mode for
another minute. */
function sp_changed ()
{
if (AUTO_001 == 0)
{
if (AUTO_RESET_TIMER)
cancel (AUTO_RESET_TIMER);
AUTO_RESET_TIMER = after (60, #go_auto());
}
}
add_exception_function (#SP_001, #sp_changed());
/********************************************************
* Emulating the PID Loop *
********************************************************/
/*--------------------------------------------------------------------
* Function: change_values
* Returns: function
* Description: Calls PID_Controller.calculate() and change_procvar()
*------------------------------------------------------------------*/
function change_values (controller)
{
controller.calculate(#SP_001, #MV_001, #PV_001);
change_procvar (#SP_001, #MV_001, #PV_001);
force_point (#CYCLE, ++cycle_count);
}
/* Create a controller. */
PID1 = new(PID_Controller);
PID1.init ("PID1");
/* Start the controller running. */
every (0.1, #change_values(PID1));
force_point(#AUTO_001, 1);
AUTO_TIMER = auto_control(#FREQ_001);
/* Loop continuously. */
while (t)
{
next_event();
}
Copyright © 1995-2010 by Cogent Real-Time Systems, Inc. All rights reserved.