diff --git a/@MyCommCont/MyCommCont.m b/@MyCommCont/MyCommCont.m index 6337a3e..4a59427 100644 --- a/@MyCommCont/MyCommCont.m +++ b/@MyCommCont/MyCommCont.m @@ -1,176 +1,180 @@ % Communicator container. % This class provides extended functionality for communication using VISA, % tcpip and serial objects or any other objects that have a similar usage. classdef MyCommCont < handle properties (Access = public) - interface = 'serial' - address = 'placeholder' + interface = 'constructor' + address = '' % Communication object Comm end methods (Access = public) function this = MyCommCont(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); end function delete(this) % Close the connection to the device try closeComm(this); catch ME warning(['Connection could not be closed. Error: ' ... ME.message]); end % Delete the device object try delete(this.Comm); catch ME warning(['Communication object could not be deleted. ' ... 'Error: ' ME.message]); end end %% Set up communication % Create an interface object function connect(this) if ~isempty(this.Comm) % Delete the existing object before creating a new one. delete(this.Comm); end try + assert(~isempty(this.address), ... + 'The instrument address is empy.'); + switch lower(this.interface) % Use 'constructor' interface to create an object with % more that one parameter passed to the constructor case 'constructor' % In this case 'address' is a MATLAB command that % creates communication object when executed. % Such commands, for example, are returned by % instrhwinfo as ObjectConstructorName. this.Comm = eval(this.address); case 'visa' % visa brand is 'ni' by default this.Comm = visa('ni', this.address); case 'tcpip' % Works only with default socket. Use 'constructor' % if socket or other options need to be specified this.Comm = tcpip(this.address); case 'serial' this.Comm = serial(this.address); otherwise error(['Unknown interface ''' this.interface ... ''', a communication object is not created.'... ' Valid interfaces are ''constructor'', ' ... '''visa'', ''tcpip'' and ''serial''']) end configureCommDefault(this); catch ME - warning(ME.message); + warning([ME.message ... + ' Creating a dummy communication object.']); % Create a dummy this.Comm = serial('placeholder'); end end % Set larger buffer sizes and longer timeout than the MATLAB default function configureCommDefault(this) comm_props = properties(this.Comm); if ismember('OutputBufferSize',comm_props) this.Comm.OutputBufferSize = 1e7; % bytes end if ismember('InputBufferSize',comm_props) this.Comm.InputBufferSize = 1e7; % bytes end if ismember('Timeout',comm_props) this.Comm.Timeout = 10; % s end end function bool = isopen(this) try bool = strcmp(this.Comm.Status, 'open'); catch warning('Cannot access the communicator Status property'); bool = false; end end % Opens the device if it is not open. Does not throw error if % device is already open for communication with another object, but % tries to close existing connections instead. function openComm(this) try fopen(this.Comm); catch if isa(this.Comm, 'serial') % Try to find and close all the serial objects % connected to the same port instr_list = instrfind('Port', this.Comm.Port); else % Try to find and close all the devices with the same % VISA resource name instr_list = instrfind('RsrcName', this.Comm.RsrcName); end fclose(instr_list); fopen(this.Comm); warning(['Multiple instrument objects of ' ... 'address %s exist'], this.address); end end function closeComm(this) fclose(this.Comm); end %% Communication % Write textual command function writeString(this, str) try fprintf(this.Comm, str); catch ME try % Attempt re-opening communication openComm(this); fprintf(this.Comm, str); catch rethrow(ME); end end end % Query textual command function result = queryString(this, str) try result = query(this.Comm, str); catch ME try % Attempt re-opening communication openComm(this); result = query(this.Comm, str); catch rethrow(ME); end end end end end diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 7da80e7..f16f1f2 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,295 +1,291 @@ % Generic instrument superclass % % Undefined/dummy methods: % queryString(this, cmd) % % These methods are intentionally not introduced as abstract as under % some conditions they are not necessary classdef MyInstrument < dynamicprops & matlab.mixin.CustomDisplay properties (Access = public) % Synchronize all properties after setting new value to one auto_sync = true end properties (SetAccess = protected, GetAccess = public) CommandList = struct() % Identification string idn_str = '' end properties (Dependent = true) command_names end properties (Access = protected) % Copying existing metadata is much faster than creating a new one Metadata = MyMetadata.empty() % Logical variables that determine if writing to the instrument % takes place when property is assigned new value CommandWriteEnabled = struct() end methods (Access = public) % Read all parameters of the physical device function sync(this) read_ind = structfun(@(x) ~isempty(x.readFcn), ... this.CommandList); read_cns = this.command_names(read_ind); for i=1:length(read_cns) tag = read_cns{i}; read_value = this.CommandList.(tag).readFcn(); % Compare to the previous value and update if different. % Comparison prevents overhead for objects that listen to % the changes of property values. if ~isequal(this.CommandList.(tag).last_value, read_value) % Assign value without writing to the instrument this.CommandWriteEnabled.(tag) = false; this.(tag) = read_value; this.CommandWriteEnabled.(tag) = true; end end end function addCommand(this, tag, varargin) p = inputParser(); % Name of the command addRequired(p,'tag', @(x)isvarname(x)); % Functions for reading and writing the property value to the % instrument addParameter(p, 'readFcn', function_handle.empty(), ... @(x)isa(x, 'function_handle')); addParameter(p, 'writeFcn', function_handle.empty(), ... @(x)isa(x, 'function_handle')); % Function applied before writeFcn addParameter(p, 'validationFcn', function_handle.empty(), ... @(x)isa(x, 'function_handle')); % Function or list of functions executed after updating the % class property value addParameter(p, 'postSetFcn', function_handle.empty(), ... @(x)isa(x, 'function_handle')); addParameter(p, 'value_list', {}, @iscell); addParameter(p, 'default', 0); addParameter(p, 'info', '', @ischar); parse(p,tag,varargin{:}); assert(~isprop(this, tag), ['Property named ' tag ... ' already exists in the class.']); for fn = fieldnames(p.Results)' this.CommandList.(tag).(fn{1}) = p.Results.(fn{1}); end this.CommandList.(tag).info = ... toSingleLine(this.CommandList.(tag).info); vl = this.CommandList.(tag).value_list; if ~isempty(vl) && isempty(p.Results.validationFcn) this.CommandList.(tag).validationFcn = ... createListValidationFcn(this, vl); end % Assign default value from the list if not given explicitly if ~isempty(vl) && ismember('default', p.UsingDefaults) default = vl{1}; else default = p.Results.default; end % Create and configure a dynamic property H = addprop(this, tag); H.GetAccess = 'public'; H.SetObservable = true; H.SetMethod = createCommandSetFcn(this, tag); % Assign the default value with post processing but without % writing to the instrument this.CommandWriteEnabled.(tag) = false; this.(tag) = default; this.CommandWriteEnabled.(tag) = true; if ~isempty(this.CommandList.(tag).writeFcn) H.SetAccess = 'public'; else H.SetAccess = {'MyInstrument'}; end end % Identification - function [str, msg] = idn(this) + function str = idn(this) assert(ismethod(this, 'queryString'), ['The instrument ' ... 'class must define queryString method in order to ' ... 'attempt identification.']) - try - str = queryString(this,'*IDN?'); - catch ME - str = ''; - msg = ME.message; - end + str = queryString(this, '*IDN?'); + this.idn_str = str; end % Measurement header function Mdt = readSettings(this) if isempty(this.Metadata) createMetadata(this); end % Ensure that instrument parameters are up to data sync(this); % Exclude idn from parameter names param_names = setdiff(fieldnames(this.Metadata.ParamList), ... {'idn'}); for i = 1:length(param_names) tag = param_names{i}; this.Metadata.ParamList.(tag) = this.(tag); end Mdt = copy(this.Metadata); end % Write new settings to the physical instrument function writeSettings(this, Mdt) assert(isa(Mdt, 'MyMetadata'), ... 'Settings must be provided as MyMetadata object.'); % Synchronize the instrument object and write only the settings % which new values are different from present sync(this); param_names = setdiff(fieldnames(Mdt.ParamList), {'idn'}); for i = 1:length(param_names) tag = param_names{i}; new_val = Mdt.ParamList.(tag); if ismember(tag, this.command_names) % Set command value under the condition that it is % write accessible and that that the new value is % different from present if ~isempty(this.CommandList.(tag).writeFcn) && ... ~isequal(this.(tag), new_val) this.(tag) = new_val; end continue end if isprop(this, tag) && ~isequal(this.(tag), new_val) this.(tag) = new_val; end end end end methods (Access = protected) function createMetadata(this) this.Metadata = MyMetadata('title', class(this)); % Add identification string addParam(this.Metadata, 'idn', this.idn_str); for i = 1:length(this.command_names) cmd = this.command_names{i}; addObjProp(this.Metadata, this, cmd, ... 'comment', this.CommandList.(cmd).info); end end % Create set methods for dynamic properties function f = createCommandSetFcn(~, tag) function commandSetFcn(this, val) % Validate new value vFcn = this.CommandList.(tag).validationFcn; if ~isempty(vFcn) vFcn(val); end % Store the unprocessed value for quick reference in % the future and value change tracking this.CommandList.(tag).last_value = val; % Assign the value after post processing to the property pFcn = this.CommandList.(tag).postSetFcn; if ~isempty(pFcn) val = pFcn(val); end this.(tag) = val; if this.CommandWriteEnabled.(tag) % Write the new value to the instrument this.CommandList.(tag).writeFcn(this.(tag)); if this.auto_sync % Confirm the changes by reading the state sync(this); end end end f = @commandSetFcn; end function f = createListValidationFcn(~, value_list) function listValidationFcn(val) assert( ... any(cellfun(@(y) isequal(val, y), value_list)), ... ['Value must be one from the following list:', ... newline, var2str(value_list)]); end f = @listValidationFcn; end % Overload a method of matlab.mixin.CustomDisplay in order to % modify the display of object. This serves two purposes % a) separate commands from other properties % b) order commands in a systematic way function PrGroups = getPropertyGroups(this) cmds = this.command_names; % We separate the display of non-command properties from the % rest props = setdiff(properties(this), cmds); PrGroups = [matlab.mixin.util.PropertyGroup(props), ... matlab.mixin.util.PropertyGroup(cmds)]; end end %% Set and Get methods methods function val = get.command_names(this) val = fieldnames(this.CommandList); end function set.idn_str(this, str) this.idn_str = toSingleLine(str); end end end diff --git a/@MyScpiInstrument/MyScpiInstrument.m b/@MyScpiInstrument/MyScpiInstrument.m index 8c6c13b..70b4af1 100644 --- a/@MyScpiInstrument/MyScpiInstrument.m +++ b/@MyScpiInstrument/MyScpiInstrument.m @@ -1,352 +1,354 @@ % Class featuring a specialized framework for instruments supporting SCPI % % Undefined/dummy methods: % queryString(this, cmd) % writeString(this, cmd) classdef MyScpiInstrument < MyInstrument methods (Access = public) % Extend the functionality of base class method function addCommand(this, tag, command, varargin) p = inputParser(); p.KeepUnmatched = true; addRequired(p, 'command', @ischar); addParameter(p, 'access', 'rw', @ischar); addParameter(p, 'format', '%e', @ischar); addParameter(p, 'value_list', {}, @iscell); addParameter(p, 'validationFcn', function_handle.empty(), ... @(x)isa(x, 'function_handle')); addParameter(p, 'default', 0); % Command ending for reading addParameter(p, 'read_ending', '?', @ischar); % Command ending for writing, e.g. '%10e' addParameter(p, 'write_ending', '', @ischar); parse(p, command, varargin{:}); % Create a list of remaining parameters to be supplied to % the base class method sub_varargin = struct2namevalue(p.Unmatched); % Introduce variables for brevity format = p.Results.format; write_ending = p.Results.write_ending; if ismember('format', p.UsingDefaults) && ... ~ismember('write_ending', p.UsingDefaults) % Extract format specifier and symbol from the write ending [smb, format] = parseFormat(this, write_ending); else % Extract format symbol smb = parseFormat(this, format); end if ismember('b', smb) % '%b' is a non-MATLAB format specifier that is introduced % to designate logical variables format = replace(format, '%b', '%i'); write_ending = replace(write_ending, '%b', '%i'); end this.CommandList.(tag).format = format; % Add the full read form of the command, e.g. ':FREQ?' if contains(p.Results.access, 'r') read_command = [p.Results.command, p.Results.read_ending]; readFcn = ... @()sscanf(queryString(this, read_command), format); sub_varargin = [sub_varargin, {'readFcn', readFcn}]; else read_command = ''; end this.CommandList.(tag).read_command = read_command; % Add the full write form of the command, e.g. ':FREQ %e' if contains(p.Results.access,'w') if ismember('write_ending', p.UsingDefaults) write_command = [p.Results.command, ' ', format]; else write_command = [p.Results.command, write_ending]; end writeFcn = ... @(x)writeString(this, sprintf(write_command, x)); sub_varargin = [sub_varargin, {'writeFcn', writeFcn}]; else write_command = ''; end this.CommandList.(tag).write_command = write_command; % If the value list contains textual values, extend it with % short forms and add a postprocessing function value_list = p.Results.value_list; validationFcn = p.Results.validationFcn; if ~isempty(value_list) if any(cellfun(@ischar, value_list)) % Put only unique full-named values in the value list [long_vl, short_vl] = splitValueList(this, value_list); value_list = long_vl; % For validation, use an extended list made of full and % abbreviated name forms and case-insensitive % comparison validationFcn = createScpiListValidationFcn(this, ... [long_vl, short_vl]); postSetFcn = createToStdFormFcn(this, tag, long_vl); sub_varargin = [sub_varargin, ... {'postSetFcn', postSetFcn}]; end end % Assign validation function based on the value format if isempty(validationFcn) validationFcn = createArrayValidationFcn(this, smb); end sub_varargin = [sub_varargin, { ... 'value_list', value_list, ... 'validationFcn', validationFcn}]; % Assign default based on the format of value if ~ismember('default', p.UsingDefaults) default = p.Results.default; elseif ~isempty(value_list) default = value_list{1}; else default = createValidValue(this, smb); end sub_varargin = [sub_varargin, {'default', default}]; % Execute the base class method addCommand@MyInstrument(this, tag, sub_varargin{:}); end % Redefine the base class method to use a single read operation for % faster communication function sync(this) cns = this.command_names; ind_r = structfun(@(x) ~isempty(x.read_command), ... this.CommandList); read_cns = cns(ind_r); % List of names of readable commands read_commands = cellfun(... @(x) this.CommandList.(x).read_command, read_cns,... 'UniformOutput',false); res_list = queryStrings(this, read_commands{:}); if length(read_cns)==length(res_list) % Assign outputs to the class properties for i=1:length(read_cns) tag = read_cns{i}; val = sscanf(res_list{i}, ... this.CommandList.(tag).format); if ~isequal(this.CommandList.(tag).last_value, val) % Assign value without writing to the instrument this.CommandWriteEnabled.(tag) = false; this.(tag) = val; this.CommandWriteEnabled.(tag) = true; end end else - warning(['Not all the properties could be read, ',... - 'instrument class values are not updated.']); + warning(['Could not read %i out of %i parameters, ',... + 'no properties of %s object are updated.'], ... + length(read_commands)-length(res_list), ... + length(read_commands), class(this)); end end %% Write/query % These methods implement handling multiple SCPI commands. Unless % overloaded, they rely on write/readString methods for % communication with the device, which particular subclasses must % implement or inherit separately. % Write command strings listed in varargin function writeStrings(this, varargin) if ~isempty(varargin) % Concatenate commands and send to the device cmd_str = join(varargin,';'); cmd_str = cmd_str{1}; writeString(this, cmd_str); end end % Query commands and return the resut as cell array of strings function res_list = queryStrings(this, varargin) if ~isempty(varargin) % Concatenate commands and send to the device cmd_str = join(varargin,';'); cmd_str = cmd_str{1}; res_str = queryString(this, cmd_str); % Drop the end-of-the-string symbol and split res_list = split(deblank(res_str),';'); else res_list = {}; end end end methods (Access = protected) %% Misc utility methods % Split the list of string values into a full-form list and a % list of abbreviations, where the abbreviated forms are inferred % based on case. For example, the value that has the full name % 'AVErage' has the short form 'AVE'. function [long_vl, short_vl] = splitValueList(~, vl) long_str_vl = {}; % Full forms of string values short_vl = {}; % Abbreviated forms of string values num_vl = {}; % Numeric values % Iterate over the list of values for i = 1:length(vl) % Short forms exist only for string values if ischar(vl{i}) long_str_vl{end+1} = vl{i}; %#ok % Add short form idx = isstrprop(vl{i},'upper'); short_form = vl{i}(idx); if ~isequal(vl{i}, short_form) && ~isempty(short_form) short_vl{end+1} = short_form; %#ok end else num_vl{end+1} = vl{i}; %#ok end end % Remove duplicates short_vl = unique(lower(short_vl)); % Make the list of full forms long_vl = [num_vl, ... setdiff(lower(long_str_vl), short_vl, 'stable')]; end % Create a function that returns the long form of value from % value_list function f = createToStdFormFcn(this, cmd, value_list) function std_val = toStdForm(val) % Standardization is applicable to char-valued properties % which have value list if isempty(value_list) || ~ischar(val) std_val = val; return end % find matching values n = length(val); ismatch = cellfun(@(x) strncmpi(val, x, ... min([n, length(x)])), value_list); assert(any(ismatch), ... sprintf(['%s is not present in the list of values ' ... 'of command %s.'], val, cmd)); % out of the matching values pick the longest mvals = value_list(ismatch); n_el = cellfun(@(x) length(x), mvals); std_val = mvals{n_el==max(n_el)}; end assert(ismember(cmd, this.command_names), ['''' cmd ... ''' is not an instrument command.']) f = @toStdForm; end % Find the format specifier symbol and options function [smb, format] = parseFormat(~, fmt_spec) [start, stop, tok] = regexp(fmt_spec, '%([\d\.]*)([a-z])', ... 'start', 'end', 'tokens'); assert(~isempty(tok) && ~isempty(tok{1}{2}), ... ['Format symbol is not found in ' fmt_spec]); % The first cell index corresponds to different matches if % there are more than one specifier smb = cellfun(@(x)x{2}, tok); % Return a substring that includes all the specifiers format = fmt_spec(min(start):max(stop)); end % List validation function with case-insensitive comparison function f = createScpiListValidationFcn(~, value_list) function listValidationFcn(val) val = lower(val); assert( ... any(cellfun(@(y) isequal(val, y), value_list)), ... ['Value must be one from the following list:', ... newline, var2str(value_list)]); end f = @listValidationFcn; end % smb is an array of format specifier symbols function f = createArrayValidationFcn(~, smb) function validateNumeric(val) assert((length(val) == length(smb)) && isnumeric(val), ... sprintf(['Value must be a numeric array of length ' ... '%i.'], length(smb))) end function validateInteger(val) assert((length(val) == length(smb)) && ... all(floor(val) == val), sprintf(['Value must be ' ... 'an integer array of length %i.'], length(smb))) end function validateLogical(val) assert((length(val) == length(smb)) && ... all(val==1 | val==0), ['Value must be a logical ' ... 'array of length ' length(smb) '.']) end function valudateCharacterString(val) assert(ischar(val), 'Value must be a character string.'); end % Determine the type of validation function if all(smb == 's' | smb == 'c') f = @valudateCharacterString; elseif all(smb == 'b') f = @validateLogical; elseif all(smb == 'b' | smb == 'i') f = @validateInteger; else f = @validateNumeric; end end function val = createValidValue(~, smb) if all(smb == 's' | smb == 'c') val = ''; elseif all(smb == 'b') val = false(length(smb), 1); else val = zeros(length(smb), 1); end end end end diff --git a/Instrument classes/@MyPfeifferTpg/MyPfeifferTpg.m b/Instrument classes/@MyPfeifferTpg/MyPfeifferTpg.m index 744ad51..2f45388 100644 --- a/Instrument classes/@MyPfeifferTpg/MyPfeifferTpg.m +++ b/Instrument classes/@MyPfeifferTpg/MyPfeifferTpg.m @@ -1,183 +1,178 @@ % Class for communication with Pfeiffer TPG single and dual pressure gauge % controllers. % Use 'serial' communication objects instead of 'visa' with this instrument % Tested with TPG 262 and 362. classdef MyPfeifferTpg < MyInstrument & MyCommCont & MyGuiCont properties (Constant, Access = protected) % Named constants for communication ETX = char(3); % end of text CR = char(13); % carriage return \r LF = char(10); %#ok line feed \n ENQ = char(5); % enquiry ACK = char(6); % acknowledge NAK = char(21); % negative acknowledge end properties (SetAccess = protected, GetAccess = public, SetObservable) % Last measurement status gauge_stat = {'', ''}; end methods (Access = public) function this = MyPfeifferTpg(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); connect(this); createCommandList(this); end % read pressure from a single channel or both channels at a time function p_arr = readPressure(this) queryString(this, ['PRX', this.CR, this.LF]); str = queryString(this, this.ENQ); % Extract pressure and gauge status from reading. arr = sscanf(str,'%i,%e,%i,%e'); p_arr = transpose(arr(2:2:end)); % Status codes: % 0 –> Measurement data okay % 1 –> Underrange % 2 –> Overrange % 3 –> Sensor error % 4 –> Sensor off (IKR, PKR, IMR, PBR) % 5 –> No sensor (output: 5,2.0000E-2 [hPa]) % 6 –> Identification error this.gauge_stat = {gaugeStatusFromCode(this, arr(1)), ... gaugeStatusFromCode(this, arr(3))}; end function pu = readPressureUnit(this) queryString(this, ['UNI',this.CR,this.LF]); str = queryString(this, this.ENQ); % Pressure units correspondence table: % 0 –> mbar/bar % 1 –> Torr % 2 –> Pascal % 3 –> Micron % 4 –> hPascal (default) % 5 –> Volt pu_code = sscanf(str,'%i'); pu = pressureUnitFromCode(this, pu_code); end function id_list = readGaugeId(this) queryString(this, ['TID',this.CR,this.LF]); str = queryString(this, this.ENQ); id_list = deblank(strsplit(str,{','})); end function code_list = turnGauge(this) queryString(this, ['SEN',char(1,1),this.CR,this.LF]); str = queryString(this, this.ENQ); code_list = deblank(strsplit(str,{','})); end % Attempt communication and identification of the device - function [str, msg] = idn(this) - try - queryString(this, ['AYT', this.CR, this.LF]); - str = queryString(this, this.ENQ); - catch ME - str = ''; - msg = ME.message; - end + function str = idn(this) + queryString(this, ['AYT', this.CR, this.LF]); + str = queryString(this, this.ENQ); this.idn_str = toSingleLine(str); end % Create pressure logger function Lg = createLogger(this, varargin) function p = MeasPressure() % Sync the class properties which also will tirgger an % update of all the guis to which the instrument is linked sync(this); p = this.pressure; end Lg = MyLogger(varargin{:}, 'MeasFcn', @MeasPressure); pu = this.pressure_unit; if isempty(Lg.Record.data_headers) && ~isempty(pu) Lg.Record.data_headers = ... {['P ch1 (' pu ')'], ['P ch2 (' pu ')']}; end end end methods (Access = protected) function createCommandList(this) addCommand(this, 'pressure', ... 'readFcn', @this.readPressure, ... 'default', [0, 0]); addCommand(this, 'pressure_unit', ... 'readFcn', @this.readPressureUnit, ... 'default', 'mBar'); addCommand(this, 'gauge_id', ... 'readFcn', @this.readGaugeId, ... 'default', {'', ''}); end function createMetadata(this) createMetadata@MyInstrument(this); addObjProp(this.Metadata, this, 'gauge_stat', ... 'comment', 'Last measurement status'); end % Convert numerical code for gauge status to a string function str = gaugeStatusFromCode(~, code) switch int8(code) case 0 str = 'Measurement data ok'; case 1 str = 'Underrange'; case 2 str = 'Overrange'; case 3 str = 'Sensor error'; case 4 str = 'Sensor off'; case 5 str = 'No sensor'; case 6 str = 'Identification error'; otherwise str = ''; warning('Unknown gauge status code %i', code); end end % Convert numerical code for pressure unit to a string function str = pressureUnitFromCode(~, code) switch int8(code) case 0 str = 'mBar'; case 1 str = 'Torr'; case 2 str = 'Pa'; case 3 str = 'Micron'; case 4 str = 'hPa'; case 5 str = 'Volt'; otherwise str = ''; warning('unknown pressure unit, code=%i',pu_num) end end end end diff --git a/Instrument classes/@MyTekRsa/MyTekRsa.m b/Instrument classes/@MyTekRsa/MyTekRsa.m index 3500a24..6bca004 100644 --- a/Instrument classes/@MyTekRsa/MyTekRsa.m +++ b/Instrument classes/@MyTekRsa/MyTekRsa.m @@ -1,191 +1,200 @@ % Class for controlling Tektronix RSA5103 and RSA5106 spectrum analyzers classdef MyTekRsa < MyScpiInstrument & MyDataSource & MyCommCont ... & MyGuiCont properties (SetAccess = protected, GetAccess = public) acq_trace % The number of last read trace end methods (Access = public) function this = MyTekRsa(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); this.Trace.unit_x = 'Hz'; this.Trace.unit_y = '$\mathrm{V}^2/\mathrm{Hz}$'; this.Trace.name_y = 'Power'; this.Trace.name_x = 'Frequency'; % Create communication object connect(this); % Set up the list of communication commands createCommandList(this); end + + function str = idn(this) + str = idn@MyInstrument(this); + + % The instrument needs to be in DPX Spectrum mode + res = queryString(this, ':DISPlay:WINDow:ACTive:MEASurement?'); + assert(contains(lower(res), {'dpsa', 'dpx'}), ... + 'The spectrum analyzer must be in DPX Spectrum mode.'); + end end methods (Access = protected) function createCommandList(this) % We define commands for both the nominal and actual resolution % bandwidths as these two are useful in different % circumstances. The nominal one unlike the actual one takes % effect immediately after it is set to a new value, whereas % the actual one is the true rbw if the device does not follow % the nominal one (usually if the nominal rbw is is too small). addCommand(this, 'rbw', ':DPX:BANDwidth:RESolution', ... 'format', '%e', ... 'info', 'Nominal resolution bandwidth (Hz)'); addCommand(this, 'rbw_act', ':DPX:BANDwidth:ACTual', ... 'format', '%e', ... 'access', 'r', ... 'info', 'Actual resolution bandwidth (Hz)'); addCommand(this, 'auto_rbw', ':DPX:BAND:RES:AUTO', ... 'format', '%b'); addCommand(this, 'span', ':DPX:FREQ:SPAN', ... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'start_freq', ':DPX:FREQ:STAR',... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'stop_freq', ':DPX:FREQ:STOP',... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'cent_freq', ':DPX:FREQ:CENT',... 'format', '%e', ... 'info', '(Hz)'); % Continuous triggering addCommand(this, 'init_cont', ':INIT:CONT', ... 'format', '%b',... 'info', 'Continuous triggering on/off'); % Number of points in trace addCommand(this, 'point_no', ':DPSA:POIN:COUN', ... 'format', 'P%i', ... 'value_list', {801, 2401, 4001, 10401}); % Reference level (dB) addCommand(this, 'ref_level',':INPut:RLEVel', ... 'format', '%e',... 'info', '(dB)'); % Display scale per division (dBm/div) addCommand(this, 'disp_y_scale', ':DISPlay:DPX:Y:PDIVision',... 'format', '%e', ... 'info', '(dBm/div)'); % Display vertical offset (dBm) addCommand(this, 'disp_y_offset', ':DISPLAY:DPX:Y:OFFSET', ... 'format', '%e', ... 'info', '(dBm)'); % Parametric commands for i = 1:3 i_str = num2str(i); % Display trace addCommand(this, ['disp_trace',i_str], ... [':TRAC',i_str,':DPX'], ... 'format', '%b', ... 'info', 'on/off'); % Trace Detection addCommand(this, ['det_trace',i_str],... [':TRAC',i_str,':DPX:DETection'],... 'format', '%s', ... 'value_list', {'AVERage', 'NEGative', 'POSitive'}); % Trace Function addCommand(this, ['func_trace',i_str], ... [':TRAC',i_str,':DPX:FUNCtion'], ... 'format', '%s', ... 'value_list', {'AVERage', 'HOLD', 'NORMal'}); % Number of averages addCommand(this, ['average_no',i_str], ... [':TRAC',i_str,':DPX:AVER:COUN'], ... 'format', '%i'); % Count completed averages addCommand(this, ['cnt_trace',i_str], ... [':TRACe',i_str,':DPX:COUNt:ENABle'], ... 'format', '%b', ... 'info', 'Count completed averages'); end end end methods (Access = public) function readTrace(this, varargin) if ~isempty(varargin) n_trace = varargin{1}; else n_trace = this.acq_trace; end % Ensure that device parameters, especially those that will be % later used for the calculation of frequency axis, are up to % date sync(this); writeString(this, sprintf('fetch:dpsa:res:trace%i?', n_trace)); data = binblockread(this.Comm, 'float'); % Calculate the frequency axis this.Trace.x = linspace(this.start_freq, this.stop_freq,... this.point_no); % Calculates the power spectrum from the data, which is in dBm. % Output is in V^2/Hz this.Trace.y = (10.^(data/10))/this.rbw_act*50*0.001; this.acq_trace = n_trace; % Trigger acquired data event triggerNewData(this); end % Abort data acquisition function abortAcq(this) writeString(this, ':ABORt'); end % Initiate data acquisition function initAcq(this) writeString(this, ':INIT'); end % Wait for the current operation to be completed function val = opc(this) val = queryString(this, '*OPC?'); end % Extend readSettings function function Mdt = readSettings(this) %Call parent class method and then append parameters Mdt = readSettings@MyScpiInstrument(this); %Hdr should contain single field addParam(Mdt, 'acq_trace', this.acq_trace, ... 'comment', 'The number of last read trace'); end end methods function set.acq_trace(this, val) assert((val==1 || val==2 || val==3), ... 'Acquisition trace number must be 1, 2 or 3.'); this.acq_trace = val; end end end diff --git a/Utility functions/InstrumentManager.mlapp b/Utility functions/InstrumentManager.mlapp index ebb8cf2..ad4b228 100644 Binary files a/Utility functions/InstrumentManager.mlapp and b/Utility functions/InstrumentManager.mlapp differ