|
Size: 7312
Comment:
|
← Revision 7 as of 2012-01-03 23:02:39 ⇥
Size: 7316
Comment: converted to 1.6 markup
|
| Deletions are marked like this. | Additions are marked like this. |
| Line 8: | Line 8: |
| attachment:slow.pl | [[attachment:slow.pl]] |
The FSS's SLOW PID Servo
The SLOW is implemented as a perl script and is controlled by the SLOW screen which is linked off the FSS screen. The PID parameters are set to give a quick response time and little overshoot. Its response is not very critical - it is only meant to work a DC offloader.
We used to use an EPICS State Code for this (written by Peter King). It is called slowpid.st and is still in the c1psl target area. It was buggy (froze up occasionally) and we had to log in to some special machine and use some compiler that only some special person could find to debug it. Since this effectively made it unmaintainable we removed this from the c1psl startup script and instead run the perl script on our scripts machine (op340m).
The Code
The below is the code as of 9/9/09 slow.pl
# PID Servo for PSL-FSS (Slow)
# Tobin Fricke 2007-01-09
use strict;
#use Scalar::Util qw(looks_like_number);
sub looks_like_number {
return ($_[0] =~ /^-?\d+\.?\d*$/); #FIXME
}
use EpicsTools;
# Parameters
my $process = 'C1:PSL-FSS_FAST';
my $actuator = 'C1:PSL-FSS_SLOWDC';
my $setpoint = 0;
my $blinkystatus = 0;
my ($KpParam, $KiParam, $KdParam) =
('C1:PSL-FSS_SLOWKP', 'C1:PSL-FSS_SLOWKI', 'C1:PSL-FSS_SLOWKD');
my $timestepParam = 'C1:PSL-FSS_TIMEOUT';
#($KpParam, $KiParam, $KdParam) = (-0.001, -0.0025, -0.005);
# Old values were Kp=-.15, Ki=0,Dk=-0.005
# More parameters -- these ones actually have to be numbers
my @hard_stops = (-5.0, 5.0); # Perl5 apparently doesn't have an Inf value
my $increment_limit = 0.01;
# Variables
my @u; # outputs to the actuator
my @e; # error signal
my $debug = 1;
print "Starting FSS Slow Servo\n";
# Shift some initial values into our registers
while ($#u < 2) {
unshift(@u, get_value($actuator));
unshift(@e, 0);
print("Current value of actuator = $u[0]\n");
}
# Start our controller
while (1) {
# Get the current time step
my $timestep = get_value($timestepParam);
# Sleep for the rest of the time interval
sleep($timestep);
# Blink the blinky light
if ($blinkystatus) {
$blinkystatus = 0;
} else {
$blinkystatus = 1;
}
if ($debug) {
print "\nSLOW_BEAT --> $blinkystatus\n";
}
epWrite('C1:PSL-FSS_SLOWBEAT', $blinkystatus);
# Make sure the "ENABLE" button is checked
if (get_value("C1:PSL-FSS_SLOWLOOP") == 0) {
printf("FSS_SLOWLOOP disabled -- control loop disabled.\n");
next;
}
# Make sure the loop is supposed to be active
if (get_value("C1:PSL-FSS_RCTRANSPD") < get_value("C1:PSL-FSS_LOCKEDLEVEL")) {
print("Reference Cavity not locked -- control loop disabled.\n");
next;
}
# Instead of using the actuation we requested in the previous step as the
# previous value of the actuation, we'll read the current actuation from
# EPICS. This prevents us from monopolizing the slider.
$u[0] = get_value($actuator);
if ($debug) {
print "\nActuator --> $u[0]\n";
}
# Make room for the present time
unshift(@e, undef);
unshift(@u, undef);
# Read the PID parameters in case they have changed
my ($Kp, $Ki, $Kd) = get_value($KpParam, $KiParam, $KdParam);
$Ki *= $timestep;
$Kd /= $timestep;
if ($debug) {
print("Kp = $Kp\tKi = $Ki\tKd = $Kd\n");
}
# Read the current value of the Process Variable and the Setpoint
my $p = &get_value($process);
my $s = &get_value($setpoint);
# The basic finite-difference PID approximation
$e[0] = $p - $s;
# $u[0] = $u[1] + ($Kp + $Ki + $Kd)*$e[0] - ($Kp + 2*$Kd)*$e[1] + $Kd * $e[2];
$u[0] = $u[1];
$u[0] = $u[0] + $Ki * ($e[0]);
$u[0] = $u[0] + $Kp * ($e[0] - $e[1]);
$u[0] = $u[0] + $Kd * ($e[0] - 2*$e[1] + $e[2]);
# Enforce hard stops and maximum |increment|
$u[0] = $u[1] + rail($u[0] - $u[1],$increment_limit);
$u[0] = rail($u[0], @hard_stops);
# Bullshit rounding to prevent epWrite from complaining about the
# number of digits in the $u[0] variable
my $tempn = int($u[0]*10000)/10000;
$u[0] = $tempn;
# Perform the actuation
if ($debug) {
print "Actuator <-- $u[0]\n";
}
epWrite($actuator, $u[0]);
# Discard samples sufficiently far in the past
pop(@e);
pop(@u);
}
# We need to distinguish between literal numbers and EPICS
# process variables. If we're given the name of a process
# variable, we'll use ezcaread to dereference it.
sub get_value {
my @results = ();
foreach (@_) {
if (looks_like_number($_)) {
push(@results, $_);
} else {
my @epResult = epRead($_);
pop @epResult or die "Could not read EPICS process variable.";
push(@results, pop(@epResult));
}
}
if ($#results == 0) { #FIXME: Is this necessary?
return $results[0];
} else {
return @results;
}
}
sub rail {
my $value = shift(@_);
my @limits = @_;
# If we're only given one value for the limits, we'll
# assume the limits are symmetric, i.e. lim ==> (-lim,lim)
if ($#limits == 0) {
unshift(@limits, -$limits[0]);
}
if ($limits[1] < $limits[0]) {
die "Upper limit is lower than lower limit!";
}
($value < $limits[0] ? $limits[0] :
($value > $limits[1] ? $limits[1] : $value));
}
