Code covered by the BSD License  

Highlights from
CO2gui - lab control and automation

CO2gui - lab control and automation

by

 

06 Jan 2010 (Updated )

Software used for controlling and data logging lab equipment.

ramp.m
function rampTimer = ramp(serialObject, command, startParameter, endParameter, incrementParameter, time, varargin)
% RAMP Slowly changes the set parameter of a serial object
% ramp(serialObject, command, startParameter, endParameter,
% incrementParameter, time, <property-value pairs>) linearly changes the
% second input of a command to run on a serialObject.  startParameter and
% endParameter are the initial and final conditions, incrementParameter is
% the increment parameter and time is the number of seconds to run this
% over.  If the increment parameter cannot be exactly divided into the
% endParameter - startParameter (the range), then it will return a
% non-integer number of steps which the timer object will round DOWN -
% creating some unpredictability when attempting to synchronise timers -
% this should be avoided.  If property-value pairs are supplied at the end
% of the timer, these are applied to it.

% e.g. ramps(tempObj, @tempobjwritesettemp, 40, 300, 0.1, 180) increases
% the temperature from 40 to 300 C with 0.1 C increments over 3 hours.

% checks the number of arguments
error(nargchk(6, Inf, nargin))

% error handling
if ~isserial(serialObject) || ~isrunning(serialObject)
    % errors (not currently
    error('Serial object is not valid or not connected.')
    
elseif ~isa(command, 'function_handle')
    % errors
    error('command must be a function handle.')
    
elseif ~isnumeric(incrementParameter) || ~isscalar(incrementParameter) || ~isreal(incrementParameter) || isnan(incrementParameter) || isinf(incrementParameter)
    % errors
    error('Increment parameter must be a real scalar number.')
    
elseif ~isnumeric(time) || ~isscalar(time) || ~isreal(time) || isnan(time) || isinf(time)|| time < 0
    % errors
    error('Time must be a real positive number.')
    
elseif nargin > 6 && ~ispv(varargin{:})
    % errors
    error('Property-Value pairs must be valid.')
end

% calculates the parameter range (unsigned)
parameterRange = abs(endParameter - startParameter);

% calculates the required number of steps - steps is set to + 1 so that it
% includes the first temperature (e.g. 0-10 C requires 11 different
% temperatures NOT only 10).  The number also needs to be rounded since the
% TasksToExecute property rounds down for you, instead of off (i.e. fix()
% instead of round())
steps = round((parameterRange / incrementParameter) + 1);

% calculates the time required per step - rounded to 3 decimal places to avoid a MATLAB warning saying that
% sub-milisecond times for 'period' will be ignored
timePerStep = round((time / steps) * 1000) / 1000;

% if the time is too short (0.5 s) then error and prevent the function
% from going any further
if timePerStep <= 0.5
    % errors
    error('Time per step is %d - this must be larger than 0.5 s to avoid communications issues.', timePerStep)
end

% if the ramp is actually a reduction, changes the sign of the
% increment
if endParameter < startParameter
    % changes it
    incrementParameter = -incrementParameter;
end

% changes the end parameter so that its adjusted for a whole number of
% increments
endParameter = startParameter + ((steps - 1) * incrementParameter);

% initialises the ongoing variable to one increment less, since the
% variable will be incremented before the command is sent in the timer
nextParameter = startParameter - incrementParameter;

% sets up timer - UserData is used in the callback functions to
% store the previously sent parameter, so that it can be incremented
% and updated
rampTimer = timer(  'ExecutionMode', 'fixedRate',...
                    'TasksToExecute', Inf,...
                    'Period', timePerStep,...
                    'TimerFcn', {@incrementParameterCallback, serialObject, command, incrementParameter},...
                    'StartFcn', @startCallback,...
                    'StopFcn', @deletecallback,...
                    'Tag', 'simpleRampTimer',...
                    'UserData', struct( 'nextParameter', nextParameter,...
                                        'startParameter', startParameter,...
                                        'endParameter', endParameter,...
                                        'minParameter', min([startParameter, endParameter]),...
                                        'maxParameter', max([startParameter, endParameter]),...
                                        'incrementParameter', incrementParameter,...
                                        'command', command,...
                                        'serialObject', serialObject,...
                                        'timePerStep', timePerStep,...
                                        'steps', steps,...
                                        'parameterRange', parameterRange,...
                                        'reachedEnd', false));
                
% if extra properties were supplied...
if nargin > 6
    % tries
    try
        % apply them
        set(rampTimer, varargin{:})

    catch
        % it obviously didn't work - so clean up before erroring
        delete(rampTimer)

        % rethrow the error
        rethrow(lasterror)
    end
end


function startCallback(timerObject, eventdata)
% STARTPARAMETER resets the reached end field

% gets the user data DEBUG I suspect the user data has become a 2x1
% strcture somehow
userData = timerObject.UserData

% changes the reached end field
userData.reachedEnd = false;

% saves it back
timerObject.UserData = userData;


function incrementParameterCallback(timerObject, eventdata, serialObject, command, incrementParameter)
% INCREMENTPARAMETER callback function for timer to increment parameter

% gets the user data
userData = timerObject.UserData;

% calculates and stores the next set parameter
userData.nextParameter = userData.nextParameter + incrementParameter;

% writes set parameter to object only if it is not the final step
% (included to allow some time at that final parameter without sending
% additional commands)
if userData.nextParameter <= userData.maxParameter && userData.nextParameter >= userData.minParameter
    % stores the user data
    timerObject.UserData = userData;

    % tries to find the CO2gui handle (fetches it from the pico timer
    % if it exists, is empty otherwise)
    mainGuiHandle = getguihandles;

    % only disable the fields if its not empty
    if ~isempty(mainGuiHandle)
        % defines the collect data flag field
        collectDataFlag = sprintf('collectDataFlag%d', serialObject.UserData.objectNumber);
        
        % turns the collect data flag off
        setappdata(mainGuiHandle, collectDataFlag, false)
    end

    % try-catched for robustness
    try
        % sends the command
        feval(command, serialObject, userData.nextParameter)

    catch
        % displays a warning
        warning(['Could not send new ramp parameter: ', func2str(command), '(serialObject, %d)'], userData.nextParameter)
    end

    % turns the collect data flag back on if it can
    if ~isempty(mainGuiHandle)
        % turns the collect data flag on
        setappdata(mainGuiHandle, collectDataFlag, true)
    end
        
else
    % stops the timer
    stop(timerObject)
    
    % resets the user data back to its inital values
    userData.nextParameter = userData.startParameter - userData.incrementParameter;
    
    % says its reached the end (restarting the timer will reset this)
    userData.reachedEnd = true;
    
    % stores it
    timerObject.UserData = userData;
end

Contact us