PID (Proportional Integral Derivative) controllers are often used in feedback loops where temperature, pressure, speed flow and other inert variables are involved. They provide a simple and reliable way to control the process of driving the target variable to a well known state.
PID controllers work by evaluating the error (target value - current value) of a controlled variable. They take into account the error itself (P), the rate at which the error is changing (D) and also the statistically calculated error (I), combining all three in a single control (feedback) value. The control value is added to the current value of the controlled variable and then the process repeats until the controlled variable reaches a predefined value. At this point the error becomes zero and the controlled variable stays unchanged until a new target value is specified. During the process the current value often oscillates around the target value while the calculations are constantly trying to minimize the error. To make the process efficient PID controllers rely on the gain. For each of the individual components (K, P and D) in the calculation there is a separate gain. Usually these are known as KP, KI and KD. They are just multipliers that affect the controller's output. And this is where the biggest downside of the PID controllers is - they require tuning. e.g. you need to find specific values for each of the gains that will satisfy your use case. A badly tuned controller may lead to ever increasing oscillations of the current value around the target value. Inevitably this leads to device malfunction.
In Process Simulate we can model this behavior with (you guessed it) a SCL script (FB).
FUNCTION_BLOCK "PID_CTL"
VERSION:1.0
On input we provide the gains as well as the target value and the current value of the controlled variable. Also we provide the sampling time and min/max values for the target/control value:
VAR_INPUT
KP : REAL := 18.45; // Proportional gain
KI : REAL := 3.56; // Integral gain
KD : REAL := 0.0; // Differential gain
SP : REAL := 0; // Setpoint (target) value
PV : REAL := 0; // Provisioned (current) value
MINSP : REAL := 0; // Minimal setpoint value
MAXSP : REAL := 200; // Maximal setpoint value
MINCV : REAL := 0; // Minimum control value
MAXCV : REAL := 10; // Maximum control value
T : REAL := 0.1; // Sampling time (sec) equal to the logic update rate
END_VAR
On output we have the control value and the current error.
VAR_OUTPUT
CV : REAL := 0; // Control value
ERROR : REAL := 0; // Error value
END_VAR
Internally the code uses a number of other variables to store the last/total error:
VAR
LastError : REAL := 0; // Last error
TotalError : REAL := 0; // Total error
CumulativeError : REAL := 0; // Sum of all errors
END_VAR
VAR_TEMP
Temp3 : REAL := 0;
ScaledError : REAL := 0;
END_VAR
Now let's take a look at the PID logic:
BEGIN
#ScaledError := #Temp3 * (#MAXCV - #MINCV) + #MINCV; // scale error
#TotalError := #ScaledError + #CumulativeError; // calculate total error
((#KD / #T) * (#ScaledError - #LastError)); //
elsif #CV < -#MAXCV then //
#CV := -#MAXCV; //
end_if; //
#CumulativeError := #TotalError; // update cumulative error
#LastError := #ScaledError; // update last error
END_FUNCTION_BLOCK
You can see that this is just a formula calculating the error and the control value. Pretty simple actually. Still it works pretty well for what it is.
Now. How do we use this?
Save the code to a PID_CTL.scl text file (UTF-8 encoded) and copy it to the SCL Vault (EMPower\Scripting\SCL\Vault). Then start Process Simulate and open SCL Editor on a resource and paste the code below. The resource should have a prismatic joint named 'j1'.
FUNCTION_BLOCK "MAIN"
VERSION:1.0
VAR_INPUT
sp : REAL; // target joint value
END_VAR
VAR
pid : "PID_CTL"; // PID controller instance
END_VAR
VAR_TEMP
cv : REAL; // control value
END_VAR
BEGIN
#pid(KP:=3.3, // proportional gain
KI:=15.14, // integral gain
KD:=0, // differential gain
MAXCV:=10, // max. control value
MINCV:=0, // min. control value
MAXSP:=200, // max. target value
MINSP:=0, // min. target value
SP:=#sp, // target value
PV:=JDS('j1'), // current joint value
CV=>#cv); // control value
if #cv <> 0 then
// move the joint based on the PID control value
JUMP_JOINT(NAME:='j1', VALUE:=JDS('j1') + #cv);
end_if;
END_FUNCTION_BLOCK
You need to wire a signal to the input parameter and then check the results in Robot Viewer during simulation. Once you get it running you can play with the proportional, integral and derivative gain to see the effect that they have on the PID output. Just modify the values and save the script.
Note: Here I am using a joint to visualize the PID output but any other variable can be used for the same. It is just easier with the joint.
Note: The implementation is not perfect and needs to be enhanced if needed. This is just the simplest code I could come up for this demo.
PID controllers can be found everywhere nowadays. From home appliances to the automotive industry. They are broadly applicable since they rely only on the response of the measured variable, not on specific knowledge or model of the underlying process.
What would you use it for?
Comments