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.

plotramp.m
function plotRampTimer = plotramp(serialObject, command, times, values, userData)
% PLOTRAMP runs repeated parameter ramps with different ramp rates
% plotramp(serialObject, command, times, values, userData) executes the
% command with the value at the corresponding time - i.e. it behaves like
% plot and is therefore extremely powerful.  Times are in seconds. userData
% is optional, and is used to fill serialObject.userData.  The first
% element of the userData is the ticker for the timer, the second is the
% object number and the object type (both automatically retrieved from the
% serial object), and should not be modified.

% e.g. plotramp(tempObj, 'tempobjwritesettemp', [40:0.1:300], [0:260] * 60)
% runs a temperature ramp from 40 to 300 C with 0.1 C increments over 260
% minutes.


% checks the number of arguments
error(nargchk(4, 5, nargin))

% error handling
if ~isserial(serialObject)
    % errors
    error('Serial object must be valid.')
    
elseif ~isstring(command)
    % errors
    error('Command must be a 1-row character string.')
    
elseif ~isvector(times) || any(~isnumberbetween(times, 0, Inf)) 
    % errors
	error('Times must be a scalar or vector array of real numbers greater than or equal to 0')
    
elseif ~isvector(times) || any(~isnumber(times))
    % errors
	error('Values must be a scalar or vector array of real numbers greater than 0')
    
elseif numel(times) ~= numel(values)
    % errores
    error('There must be a matching number of times and values')
end

% sets up timer - if the first time is 0, then the the first command is
% executed immediately. the first item of serial object userdata is the
% object number, and the second item is the object type.  It is essential
% that the busyMode is queue so that multiple timers can coexist without
% dropping commands
plotRampTimer = timer(  'TimerFcn', @plotRampTimerCallback,...
                        'StopFcn', {@plotRampTimerReset, serialObject, command, times, values},...
                        'ErrorFcn', @plotRampTimerError,...
                        'BusyMode', 'queue',...
                        'StartDelay', times(1),...
                        'Tag', 'rampTimer',...
                        'UserData', struct( 'ticker', 0,...
                                            'createdTime', now,...
                                            'lastStartTime', []));

% if the serial object has user data that is a structure...
if isstruct(serialObject.UserData)
    % add that in
    plotRampTimer.UserData = catstructs(serialObject.UserData, plotRampTimer.UserData);
end
                    
% if userData was supplied...
if nargin >= 5 && isstruct(userData)
    % modifies the user data (the default data takes precedence)
    plotRampTimer.UserData = catstructs(userData, plotRampTimer.UserData);
end


function plotRampTimerCallback(timerObject, eventdata)
% PLOTRAMPTIMERCALLBACK is a blank dummy function - the timer must be
% restarted immediately BEFORE the command is sent, so that fixedSpacing is
% correctly emulated.


function plotRampTimerReset(timerObject, eventdata, serialObject, command, times, values)
% PLOTRAMPTIMERRESET restarts the timer, sends the command sets the timer
% to the next time, and if it is the last run, resets the object

% to reset the timer, the ticker must be set to -1, and then the timer
% stopped - this will reset the timer to its resting state

% fetches UserData with the ticker in it and increments it
userData = timerObject.UserData;
userData.ticker = userData.ticker + 1;

% if its the first time round, update the lastStartTime field
if userData.ticker == 1
    % updates the field
    userData.lastStartTime = now;
end

% saves the user data, including the ticker
timerObject.UserData = userData;

% predefines the error
lastErrorDetails = '';

% checks that there are some points left
if userData.ticker >= 1 && userData.ticker <= numel(times)
    % if its the last one, then set the start delay to 0 (this is done
    % before the command is run, in case the commands are long
    if userData.ticker < numel(times)
        % temporarily turns the sub-millisecond warning off (the round
        % * 1000 trick doesn't always seem to work)
        warning('off', 'MATLAB:TIMER:STARTDELAYPRECISION')
        
        % changes the 'StartDelay' to ... (also rounds it to the neares 0.001 so that MATLAB
        % doesn't warn about sub-millisecond precision)
        timerObject.StartDelay = max([times(userData.ticker + 1) - ((now - userData.lastStartTime) * 24 * 60 * 60), 0]);
        
        % turns it back on
        warning('on', 'MATLAB:TIMER:STARTDELAYPRECISION')
        
    else
        % sets the start time to 0 so it tidies up straight away
        timerObject.StartDelay = 0;
    end
    
    % restarts the timer
    start(timerObject)
    
    % runs command
    try
        % runs a different command if its a rheodyne
        if ~strcmp(command, 'rheodyne')
            % if the user data contains the main GUI handle, and it is a
            % valid handle
            if isfield(userData, 'mainGuiHandle') && ishandle(userData.mainGuiHandle)
                % defines the collect data flag
                collectDataFlag = sprintf('collectDataFlag%d', userData.objectNumber);

                % turns the collect data flag off
                setappdata(userData.mainGuiHandle, collectDataFlag, 0)
                
            else
                % sets the collect data flag to be blank (used for
                % determining whether the flag needs resetting or not)
                collectDataFlag = '';
            end
            
            % sends the command - issue - this will run for when the ticker
            % is larger than the number of data 
            feval(str2func(command), serialObject, values(min([userData.ticker, numel(values)])));
            
        else
            % set the collect data flag so it doesn't moan later
            collectDataFlag = [];
            
            % fetches the rheodyne timer from the main GUI
            rheodyneTimer = getappdata(userData.mainGuiHandle, 'rheodyneTimer');
            
            % changes the period
            %rheodyneobjwriteperiod(rheodyneTimer, values(userData.ticker))
            rheodyneobjwriteperiod(rheodyneTimer, values(min([userData.ticker, numel(values)])))
        end
        
    catch
        % displays a warning
        warning('rampTimer:commandNotSent', [datestr(now), ': ', timerObject.Name, ' has not sent command: ', command, '(serialObj, ', num2str(values(userData.ticker)), ') due to a communications error.'])
        
        % gets the last error
        lastErrorDetails = lasterror;
        
        % a common error (for the Thar BPR anyway) is that it cannot
        % reconnect after the fclose/fopen procedure
        if ~isfield(serialObject.UserData, 'realTermHandle') && strcmp(serialObject.Status, 'closed')
            % then display some more information
            disp('Serial object also appears to be closed - possible error with reconnection.')
        end
    end
    
    % if the collect data flag was defined and changed...
    if ~isempty(collectDataFlag)
        % turns the collect data flag back on again
        setappdata(userData.mainGuiHandle, collectDataFlag, 1)
    end
    
else
    % resets the variables for re-using the timer - does not restart the
    % timer
    
    % resets the ticker
    userData.ticker = 0;

    % resets the 'StartDelay'
    timerObject.StartDelay = times(1);
    
    % saves the userData again
    timerObject.UserData = userData;
    
    % if make safe is on...
    if isfield(userData, 'makeSafe') && userData.makeSafe
        % checks that the main gui handle is still acceptable
        if ishandle(userData.mainGuiHandle)
            % gets the main handles
            mainHandles = guidata(userData.mainGuiHandle);
            
            % activates the emergency stop feature
            CO2gui('emergencyStopButton_Callback', mainHandles.emergencyStopButton, eventdata, mainHandles)
            
        else
            % displays a warning to show that the thing wasn't made safe
            warning('plotRamp:makeSafeFailure', 'System was not made safe at the end of the timer run.')
        end
    end
end

% if it errored and it wasn't empty, display it
if ~isempty(lastErrorDetails)
    % DEBUG
    for m = 1:numel(lastErrorDetails.stack)
        % display
        disp(lastErrorDetails.stack(m))
    end
end


function plotRampTimerError(timerObject, eventdata)
% Runs when the timer gets stopped for a reason it shouldn't have.

% checks that the timer has actually stopped - this has activated before
% even when the timer had not actually stopped yet! - argh!
if ~isrunning(timerObject)
    % displays a warning
    warning('plotRamp:timerError', 'Ramp timer has stopped due to an error.')

    % DEBUG displays the userData for inspection
    %timerObject.userData
    
    % gets the stop function
    stopFunction = timerObject.StopFcn;
    
    % DEBUG displays part of that
    stopFunction{4}
end

Contact us