Differences between revisions 6 and 7
Revision 6 as of 2009-09-12 09:28:19
Size: 7312
Editor: RanaAdhikari
Comment:
Revision 7 as of 2012-01-03 23:02:39
Size: 7316
Editor: localhost
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));
}

Frequency_Stabilization_Servo/SLOW_PID_servo (last edited 2012-01-03 23:02:39 by localhost)