diff --git a/@MyDpo/MyDpo.m b/@MyDpo/MyDpo.m index 4fb7dea..8db176a 100644 --- a/@MyDpo/MyDpo.m +++ b/@MyDpo/MyDpo.m @@ -1,159 +1,156 @@ % Class for controlling 4-channel Tektronix DPO scopes. % Tested with DPO4034, DPO3034 classdef MyDpo < MyScpiInstrument properties (SetAccess=protected, GetAccess=public) % List of the physical knobs, which can be rotated programmatically knob_list = {'GPKNOB1','GPKNOB2','HORZPos','HORZScale',... 'TRIGLevel','PANKNOB1','VERTPOS','VERTSCALE','ZOOM'}; end properties (Constant=true) N_CHANNELS = 4; % number of channels end methods (Access=public) function this=MyDpo(interface, address, varargin) this@MyScpiInstrument(interface, address, varargin{:}); - createCommandList(this); - createCommandParser(this); - connectDevice(this); % 2e7 is the maximum trace size of DPO4034-3034 %(10 mln point of 2-byte integers) this.Device.InputBufferSize = 2.1e7; %byte this.Trace.name_x='Time'; this.Trace.name_y='Voltage'; end function readTrace(this) %set data format to be signed integer, reversed byte order fprintf(this.Device,'DATA:ENCDG SRIbinary'); %2 bytes per measurement point fprintf(this.Device,'DATA:WIDTH 2'); % read the entire trace fprintf(this.Device,'DATA:START 1;STOP %i;',this.point_no); fprintf(this.Device,'CURVE?'); y_data = int16(binblockread(this.Device,'int16')); % Reading the relevant parameters from the scope readProperty(this,'unit_y','unit_x',... 'step_x','step_y','x_zero'); % Calculating the y data y = double(y_data)*this.step_y; n_points=length(y); % Calculating the x axis x = linspace(this.x_zero,... this.x_zero+this.step_x*(n_points-1),n_points); this.Trace.x = x; this.Trace.y = y; % Discard "" when assiging the Trace labels this.Trace.unit_x = this.unit_x(2:end-1); this.Trace.unit_y = this.unit_y(2:end-1); triggerNewData(this); end function acquireContinuous(this) openDevice(this); fprintf(this.Device,... 'ACQuire:STOPAfter RUNSTop;:ACQuire:STATE ON'); closeDevice(this); end function acquireSingle(this) openDevice(this); fprintf(this.Device,... 'ACQuire:STOPAfter SEQuence;:ACQuire:STATE ON'); closeDevice(this); end function turnKnob(this,knob,nturns) openDevice(this); fprintf(this.Device, sprintf('FPAnel:TURN %s,%i',knob,nturns)); closeDevice(this); end end methods (Access=private) function createCommandList(this) % channel from which the data is transferred addCommand(this,'channel','DATa:SOUrce','default',1,... 'str_spec','CH%i'); % currently selected in the scope display channel addCommand(this, 'ctrl_channel', 'SELect:CONTROl',... 'default',1, 'str_spec','CH%i'); % units and scale for x and y waveform data addCommand(this,'unit_x','WFMOutpre:XUNit','access','r',... 'classes',{'char'}); addCommand(this,'unit_y','WFMOutpre:YUNit','access','r',... 'classes',{'char'}); addCommand(this,'step_y','WFMOutpre:YMUlt','access','r',... 'classes',{'numeric'}); addCommand(this,'step_x','WFMOutpre:XINcr','access','r',... 'classes',{'numeric'}); addCommand(this,'x_zero','WFMOutpre:XZEro','access','r',... 'classes',{'numeric'}); % numbers of points addCommand(this, 'point_no','HORizontal:RECOrdlength',... 'default', 100000,... 'val_list', {1000, 10000, 100000, 1000000, 10000000},... 'str_spec','%i'); % time scale in s per div addCommand(this, 'time_scale','HORizontal:SCAle',... 'default',10E-3,... 'str_spec','%e'); % trigger level addCommand(this, 'trig_lev', 'TRIGger:A:LEVel',... 'default',1,... 'str_spec','%e'); % trigger slope addCommand(this, 'trig_slope', 'TRIGger:A:EDGE:SLOpe',... 'default', 'RISe', 'val_list',{'RISe','RIS','FALL'},... 'str_spec','%s'); % trigger source addCommand(this, 'trig_source', 'TRIGger:A:EDGE:SOUrce',... 'default', 'AUX', 'val_list', {'CH1','CH2','CH3','CH4',... 'AUX','EXT','LINE'},... 'str_spec','%s'); % trigger mode addCommand(this, 'trig_mode', 'TRIGger:A:MODe',... 'default', 'AUTO', 'val_list',{'AUTO','NORMal','NORM'},... 'str_spec','%s'); % state of the data acquisition by the scope addCommand(this, 'acq_state', 'ACQuire:STATE',... 'default',true, 'str_spec','%b'); % acquisition mode addCommand(this, 'acq_mode', 'ACQuire:MODe',... 'default', 'HIR', 'val_list',{'SAM','PEAK','HIR','AVE',... 'ENV','SAMple','PEAKdetect','HIRes','AVErage','ENVelope'},... 'str_spec','%s'); % Parametric commands for i = 1:this.N_CHANNELS i_str = num2str(i); % coupling, AC, DC or GND addCommand(this,... ['cpl',i_str],['CH',i_str,':COUP'],... 'default','DC', 'val_list', {'AC','DC','GND'},... 'str_spec','%s'); % impedances, 1MOhm or 50 Ohm addCommand(this,... ['imp',i_str],['CH',i_str,':IMPedance'],... 'default','MEG', 'val_list', {'FIFty','FIF','MEG'},... 'str_spec','%s'); % offset addCommand(this,... ['offset',i_str],['CH',i_str,':OFFSet'],'default',0,... 'str_spec','%e'); % scale, V/Div addCommand(this,... ['scale',i_str],['CH',i_str,':SCAle'],'default',1,... 'str_spec','%e'); % channel enabled addCommand(this,... ['enable',i_str],['SEL:CH',i_str],'default',true,... 'str_spec','%b'); end end end end \ No newline at end of file diff --git a/@MyDso/MyDso.m b/@MyDso/MyDso.m index a8e9333..8703824 100644 --- a/@MyDso/MyDso.m +++ b/@MyDso/MyDso.m @@ -1,170 +1,167 @@ % Class for controlling 4-channel Agilent DSO scopes. % Tested with DSO7034A classdef MyDso =0 && val <=3 bool = true; else warning(['Wrong heater range. Heater range for '... 'channels 1 or 2 can '... 'take only integer values between 0 and 3']) end case {3,4} if val>=0 && val <=1 bool = true; else warning(['Wrong heater range. Heater range for '... 'channels 3 or 4 can '... 'take only values 1 or 2.']) end end end function num = inChannelToNumber(~,in_channel) switch in_channel case 'A' num = int32(1); case 'B' num = int32(2); case 'C' num = int32(3); case 'D' num = int32(4); otherwise error('Input channel should be A, B, C or D.') end end end %% Set and get methods methods function str_cell = get.heater_rng_str(this) str_cell = {'','','',''}; % Channels 1-2 and 3-4 have different possible states for i=1:4 if ~isempty(this.heater_rng{i}) ind = int32(this.heater_rng{i}+1); else ind=0; end if i<=2 str_cell{i} = this.heater12_rng_list{ind}; else str_cell{i} = this.heater34_rng_list{ind}; end end end function str_cell = get.temp_str(this) str_cell = {'','','',''}; for i=1:4 if ~isempty(this.temp{i}) str_cell{i} = sprintf('%.3f %s', this.temp{i},... this.temp_unit); end end end function str_cell = get.out_mode_str(this) str_cell = {'','','',''}; try for i=1:4 ind = int32(this.out_mode{i}(1)+1); str_cell{i} = this.out_mode_list{ind}; end catch warning(['Output mode could not be interpreted ',... 'from code. Code should be between 0 and 5.']) end end function str_cell = get.cntl_inp_str(this) str_cell = {'','','',''}; try for i=1:4 ind = int32(this.out_mode{i}(2)+1); str_cell{i} = this.inp_list{ind}; end catch warning(['Input channel could not be interpreted ',... 'from index. Index should be between 0 and 4.']) end end function str_cell = get.powerup_en_str(this) str_cell = {'','','',''}; for i=1:4 if this.out_mode{i}(3) str_cell{i} = 'On'; else str_cell{i} = 'Off'; end end end function set.temp_unit(this, val) if strcmpi(val,'K')||strcmpi(val,'C') this.temp_unit = upper(val); else warning(['Temperature unit needs to be K or C, ',... 'value has not been changed']) end end end end diff --git a/@MyNa/MyNa.m b/@MyNa/MyNa.m index 088d9b4..8ad1de6 100644 --- a/@MyNa/MyNa.m +++ b/@MyNa/MyNa.m @@ -1,166 +1,163 @@ % The class for communication with Agilent E5061B Network Analyzer classdef MyNa < MyScpiInstrument properties (SetAccess=protected, GetAccess=public) active_trace = -1; % manipulating with active traces seems unavoidable % for selecting the data format. -1 stands for unknown % data formats for the traces 1-2, options: % 'PHAS', 'SLIN', 'SLOG', 'SCOM', 'SMIT', 'SADM', 'MLOG', 'MLIN', %'PLIN', 'PLOG', 'POL' form1 = 'MLOG'; form2 = 'PHAS'; Trace1 = MyTrace(); Trace2 = MyTrace(); end methods function this=MyNa(interface, address, varargin) this@MyScpiInstrument(interface, address, varargin{:}); - createCommandList(this); - createCommandParser(this); - connectDevice(this); %Tests if device is working. try openDevice(this); closeDevice(this); catch error(['Failed to open communications with device.',... ' Check that the address and interface is correct.',... ' Currently the address is %s and the interface is ',... '%s.'],this.address,this.interface) end this.Trace1.unit_x = 'Hz'; this.Trace1.name_x = 'Frequency'; this.Trace2.unit_x = 'Hz'; this.Trace2.name_x = 'Frequency'; end % Command attributes are {class, attributtes} accepted by % validateattributes() function createCommandList(this) addCommand(this,... 'cent_freq','SENS1:FREQ:CENT', 'default',1.5e6,... 'str_spec','d'); addCommand(this,... 'start_freq','SENS1:FREQ:START', 'default',1e6,... 'str_spec','d'); addCommand(this,... 'stop_freq','SENS1:FREQ:STOP', 'default',2e6,... 'str_spec','d'); addCommand(this,... 'span','SENS1:FREQ:SPAN', 'default',1e6,... 'str_spec','d'); % IF bandwidth addCommand(this,... 'ifbw','SENS1:BAND', 'default',100,... 'str_spec','d'); % number of points in the sweep addCommand(this,... 'point_no','SENS1:SWE:POIN', 'default',1000,... 'str_spec','i'); % number of averages addCommand(this,... 'average_no','SENS1:AVER:COUN', 'default',1,... 'str_spec','i'); % number of traces addCommand(this,... 'trace_no','CALC1:PAR:COUN', 'default',1,... 'str_spec','i'); % linear or log sweep addCommand(this,... 'sweep_type','SENS1:SWE:TYPE', 'default','LIN',... 'str_spec','s'); % switch the output signal on/off addCommand(this,... 'enable_out','OUTP', 'default',0,... 'str_spec','b'); % probe power [dB] addCommand(this,... 'power','SOUR:POW:LEV:IMM:AMPL', 'default',-10,... 'str_spec','d'); % windows arrangement at the display, e.g 'D1' addCommand(this,... 'disp_type','DISP:WIND1:SPL', 'default','D1',... 'str_spec','s'); % Continuous sweep triggering addCommand(this,... 'cont_trig',':INIT1:CONT', 'default', 0,... 'str_spec','b'); addCommand(this,... 'trig_source', ':TRIG:SOUR', 'default', 'BUS',... 'str_spec','s') % Parametric commands for traces, i can be extended to 4 for i = 1:2 % measurement parameters for the traces 1-2, e.g. 'S21' i_str = num2str(i); addCommand(this,... ['meas_par',i_str],['CALC1:PAR',i_str,':DEF'],... 'default','S21',... 'str_spec','s'); end end function data = readTrace(this, nTrace) this.writeActiveTrace(nTrace); freq_str = strsplit(query(this.Device,'SENS1:FREQ:DATA?'),','); data_str = strsplit(query(this.Device,'CALC1:DATA:FDAT?'),','); data = struct(); data.x = str2double(freq_str); % In the returned string there is in general 2 values for each % frequency point. In the Smith data format this can be used to % transfer magnitude and phase of the signal in one trace. With % MLOG, MLIN and PHAS format settings every 2-nd element should % be 0 data.y1 = str2double(data_str(1:2:end)); data.y2 = str2double(data_str(2:2:end)); % set the Trace properties trace_tag = sprintf('Trace%i', nTrace); this.(trace_tag).x = data.x; this.(trace_tag).y = data.y1; end function writeActiveTrace(this, nTrace) fprintf(this.Device, sprintf('CALC1:PAR%i:SEL',nTrace)); this.active_trace = nTrace; end function writeTraceFormat(this, nTrace, fmt) this.writeActiveTrace(nTrace); n_str = num2str(nTrace); this.(['form',n_str]) = fmt; fprintf(this.Device, sprintf('CALC1:FORM %s', fmt)); end function singleSweep(this) this.openDevice(); this.writeProperty('cont_trig', true); % Set the triger source to remote control this.writeProperty('trig_source', 'BUS'); % Start a sweep cycle fprintf(this.Device,':TRIG:SING'); % Wait for the sweep to finish (for the query to return 1) query(this.Device,'*OPC?'); this.closeDevice(); end function startContSweep(this) this.openDevice(); this.writeProperty('cont_trig', true); % Set the triger source to be internal this.writeProperty('trig_source', 'INT'); this.closeDevice(); end function abortSweep(this) this.openDevice(); this.writeProperty('trig_source', 'BUS'); fprintf(this.Device,':ABOR'); this.closeDevice(); end end end diff --git a/@MyRsa/MyRsa.m b/@MyRsa/MyRsa.m index 0e9543c..2f17a3e 100644 --- a/@MyRsa/MyRsa.m +++ b/@MyRsa/MyRsa.m @@ -1,110 +1,107 @@ % Class for controlling Tektronix RSA5103 and RSA5106 spectrum analyzers classdef MyRsa < MyScpiInstrument %% Constructor and destructor methods (Access=public) function this=MyRsa(interface, address, varargin) this@MyScpiInstrument(interface, address, varargin{:}); - createCommandList(this); - createCommandParser(this); - connectDevice(this); this.Trace.unit_x='Hz'; this.Trace.unit_y='$\mathrm{V}^2/\mathrm{Hz}$'; this.Trace.name_y='Power'; this.Trace.name_x='Frequency'; end end %% Private functions methods (Access=private) function createCommandList(this) % Resolution bandwidth (Hz) addCommand(this, 'rbw','DPX:BAND:RES',... 'default',1e3,'str_spec','%e'); % If the rbw is auto-set addCommand(this, 'auto_rbw','DPX:BAND:RES:AUTO',... 'default',true,'str_spec','%b'); addCommand(this, 'span', 'DPX:FREQ:SPAN',... 'default',1e6,'str_spec','%e'); addCommand(this, 'start_freq','DPX:FREQ:STAR',... 'default',1e6,'str_spec','%e'); addCommand(this, 'stop_freq','DPX:FREQ:STOP',... 'default',2e6,'str_spec','%e'); addCommand(this, 'cent_freq','DPX:FREQ:CENT',... 'default',1.5e6,'str_spec','%e'); % Initiate and abort data acquisition, don't take arguments addCommand(this, 'abort_acq','ABORt', 'access','w',... 'str_spec',''); addCommand(this, 'init_acq','INIT', 'access','w',... 'str_spec',''); % Continuous triggering addCommand(this, 'init_cont','INIT:CONT','default',true,... 'str_spec','%b'); % Number of points in trace addCommand(this, 'point_no','DPSA:POIN:COUN',... 'default',10401, 'val_list',{801,2401,4001,10401},... 'str_spec','P%i'); % Reference level (dB) addCommand(this, 'ref_level','INPut:RLEVel','default',0,... 'str_spec','%e'); % Display scale per division (dBm/div) addCommand(this, 'disp_y_scale','DISPlay:DPX:Y:PDIVision',... 'default',10,'str_spec','%e'); % Display vertical offset (dBm) addCommand(this, 'disp_y_offset','DISPLAY:DPX:Y:OFFSET',... 'default',0,'str_spec','%e'); % Parametric commands for i = 1:3 i_str = num2str(i); % Display trace addCommand(this, ['disp_trace',i_str],... ['TRAC',i_str,':DPX'],... 'default',false,'str_spec','%b'); % Trace Detection addCommand(this, ['det_trace',i_str],... ['TRAC',i_str,':DPX:DETection'],... 'val_list',{'AVER','AVERage','NEG','NEGative',... 'POS','POSitive'},... 'default','AVER','str_spec','%s'); % Trace Function addCommand(this, ['func_trace',i_str],... ['TRAC',i_str,':DPX:FUNCtion'],... 'val_list',{'AVER','AVERage','HOLD','NORM','NORMal'},... 'default','AVER','str_spec','%s'); % Number of averages addCommand(this, ['average_no',i_str],... ['TRAC',i_str,':DPX:AVER:COUN'],... 'default',1,'str_spec','%i'); % Count completed averages addCommand(this, ['cnt_trace',i_str],... ['TRACe',i_str,':DPX:COUNt:ENABle'],... 'default',false,'str_spec','%b'); end end end %% Public functions including callbacks methods (Access=public) function readSingle(this, n_trace) fetch_cmd = sprintf('fetch:dpsa:res:trace%i?', n_trace); fwrite(this.Device, fetch_cmd); data = binblockread(this.Device,'float'); readProperty(this, 'start_freq','stop_freq','point_no'); x_vec=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 power_spectrum = (10.^(data/10))/this.rbw*50*0.001; %Trace object is created containing the data and its units this.Trace.x = x_vec; this.Trace.y = power_spectrum; %Trigger acquired data event (inherited from MyInstrument) triggerNewData(this); end end end diff --git a/@MyScpiInstrument/MyScpiInstrument.m b/@MyScpiInstrument/MyScpiInstrument.m index 15696ff..e87b36b 100644 --- a/@MyScpiInstrument/MyScpiInstrument.m +++ b/@MyScpiInstrument/MyScpiInstrument.m @@ -1,334 +1,345 @@ % Class for instruments supporting SCPI, features specialized framework for % read/write commands classdef MyScpiInstrument < MyInstrument properties (SetAccess=protected, GetAccess=public) %Contains a list of the commands available for the instrument as %well as the default values and input requirements CommandList=struct(); %Parses commands using an inputParser object CommandParser; end properties (Dependent=true) command_names; command_no; write_commands; read_commands; end - methods (Access=public) + methods (Access=public) + %% Class constructor + function this=MyScpiInstrument(interface, address, varargin) + this@MyInstrument(interface, address, varargin{:}); + createCommandList(this); + createCommandParser(this); + end %% Read and write commands %Writes properties to device. Can take multiple inputs. With the %option all, the function writes default to all the %available writeable parameters. function writeProperty(this, varargin) %Parses the inputs using the CommandParser parse(this.CommandParser, varargin{:}); if this.CommandParser.Results.all % If the 'all' is true, write all the commands exec=this.write_commands; else % Check which commands were passed values ind_val=cellfun(@(x)... (~ismember(x, this.CommandParser.UsingDefaults)),... this.write_commands); exec=this.write_commands(ind_val); end for i=1:length(exec) %Creates the write command using the right string spec write_command=[this.CommandList.(exec{i}).command,... ' ',this.CommandList.(exec{i}).str_spec]; %Gets the value to write to the device this.(exec{i})=this.CommandParser.Results.(exec{i}); command=sprintf(write_command, this.(exec{i})); %Sends command to device fprintf(this.Device, command); end end % Wrapper for writeProperty that opens and closes the device function writePropertyHedged(this, varargin) openDevice(this); try writeProperty(this, varargin{:}); catch warning('Error while writing the properties:'); disp(varargin); end readProperty(this, 'all'); closeDevice(this); end function result=readProperty(this, varargin) result = struct(); read_all_flag = any(strcmp('all',varargin)); if read_all_flag % Read all the commands with read access exec=this.read_commands; else ind_r=ismember(varargin,this.read_commands); exec=varargin(ind_r); if any(~ind_r) % Issue warnings for commands not in the command_names warning('The following are not valid read commands:'); disp(varargin(~ind_r)); end end % concatenate all commands in one string read_command=join(cellfun(... @(cmd)this.CommandList.(cmd).command,exec,... 'UniformOutput',false),'?;:'); read_command=[read_command{1},'?;']; res_str = query(this.Device,read_command); % drop the end-of-the-string symbol and split res_str = split(res_str(1:end-1),';'); if length(exec)==length(res_str) for i=1:length(exec) result.(exec{i})=sscanf(res_str{i},... this.CommandList.(exec{i}).str_spec); %Assign the values to the MyInstrument properties this.(exec{i})=result.(exec{i}); end else warning(['Not all the properties could be read, ',... 'no instrument class values are not updated']); end end % Wrapper for readProperty that opens and closes the device function result=readPropertyHedged(this, varargin) openDevice(this); try result = readProperty(this, varargin{:}); catch warning('Error while reading the properties:'); disp(varargin); end closeDevice(this); end % Re-define readHeader function function HdrStruct=readHeader(this) Values=readPropertyHedged(this,'all'); for i=1:length(this.read_commands) HdrStruct.(this.read_commands{i}).value=... Values.(this.read_commands{i}); HdrStruct.(this.read_commands{i}).str_spec=... this.CommandList.(this.read_commands{i}).str_spec; end end %% Processing of the class variable values % Extend the property value based on val_list function std_val = standardizeValue(this, cmd, varargin) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val = ''; return end vlist = this.CommandList.(cmd).val_list; % The value to normalize can be explicitly passed as % varargin{1}, otherwise use this.cmd as value if isempty(varargin) val = this.(cmd); else val = varargin{1}; end % find matching commands ismatch = false(1,length(vlist)); for i=1:length(vlist) n = min([length(val), length(vlist{i})]); % compare first n symbols disregarding case ismatch(i) = strncmpi(val, vlist{i},n); end % out of matching names pick the longest if any(ismatch) mvlist = vlist(ismatch); %Finds the length of each element of mvlist n_el=cellfun(@(x) length(x), mvlist); %Sets std_val to the longest element std_val=mvlist{n_el==max(n_el)}; % sets the property if value was not given explicitly if isempty(varargin) this.(cmd) = std_val; end else warning(['The value %s is not in the val_list ',... 'of %s command'], val, cmd) std_val = val; end end % Return the list of long command values excluding abbreviations function std_val_list = stdValueList(this, cmd) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val_list = {}; return end vlist = this.CommandList.(cmd).val_list; % Select the commands, which appear only once in the beginnings % of the strings in val_list long_val_ind = cellfun(... @(x)(sum(startsWith(vlist,x,'IgnoreCase',true))==1),vlist); std_val_list = vlist(long_val_ind); end - %% addCommand + %% Command list handling %Adds a command to the CommandList function addCommand(this, tag, command, varargin) p=inputParser(); addRequired(p,'tag',@ischar); addRequired(p,'command',@ischar); addParameter(p,'default','placeholder'); addParameter(p,'classes',{},@iscell); addParameter(p,'attributes',{},@iscell); addParameter(p,'str_spec','%e',@ischar); % list of the values the variable can take, {} means no % restriction addParameter(p,'val_list',{},@iscell); addParameter(p,'access','rw',@ischar); parse(p,tag,command,varargin{:}); %Adds the command to be sent to the device this.CommandList.(tag).command=command; this.CommandList.(tag).access=p.Results.access; this.CommandList.(tag).write_flag=contains(p.Results.access,'w'); this.CommandList.(tag).read_flag=contains(p.Results.access,'r'); this.CommandList.(tag).default=p.Results.default; this.CommandList.(tag).val_list=p.Results.val_list; % Adds the string specifier to the list. if the format % specifier is not given explicitly, try to infer if ismember('str_spec', p.UsingDefaults) this.CommandList.(tag).str_spec=... formatSpecFromAttributes(this,p.Results.classes... ,p.Results.attributes); elseif strcmp(p.Results.str_spec,'%b') % b is a non-system specifier to represent the % logical type this.CommandList.(tag).str_spec='%i'; else this.CommandList.(tag).str_spec=p.Results.str_spec; end % Adds the attributes for the input to the command. If not % given explicitly, infer from the format specifier if ismember('classes',p.UsingDefaults) [this.CommandList.(tag).classes,... this.CommandList.(tag).attributes]=... attributesFromFormatSpec(this, p.Results.str_spec); else this.CommandList.(tag).classes=p.Results.classes; this.CommandList.(tag).attributes=p.Results.attributes; end % Adds a property to the class corresponding to the tag if ~isprop(this,tag) addprop(this,tag); end this.(tag)=p.Results.default; end %Creates inputParser using the command list function p = createCommandParser(this) %Use input parser %Requires input of the appropriate class p=inputParser; p.StructExpand=0; %Flag for whether the command should initialize the device with %defaults addParameter(p, 'all',false,@islogical); for i=1:length(this.write_commands) %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. tag=this.write_commands{i}; % Create validation function based on properties: % class, attributes and list of values if ~isempty(this.CommandList.(tag).val_list) if all(cellfun(@ischar, this.CommandList.(tag).val_list)) % for textual values use case insentice string comparison v_func = @(x) any(cellfun(@(y) strcmpi(y, x),... this.CommandList.(tag).val_list)); else % for everything else compare as it is v_func = @(x) any(cellfun(@(y) isequal(y, x),... this.CommandList.(tag).val_list)); end else v_func = @(x) validateattributes(x,... this.CommandList.(tag).classes,... this.CommandList.(tag).attributes); end addParameter(p, tag,... this.CommandList.(tag).default, v_func); end this.CommandParser=p; end + %Dummy empty function that needs to be redefined in a subclass and + %contain addCommand statements + function createCommandList(~) + end + %% Auxiliary functions for auto format assignment to commands function str_spec=formatSpecFromAttributes(~,classes,attributes) if ismember('char',classes) str_spec='%s'; elseif ismember('logical',classes)||... (ismember('numeric',classes)&&... ismember('integer',attributes)) str_spec='%i'; else %assign default value, i.e. double str_spec='%e'; end end function [class,attribute]=attributesFromFormatSpec(~, str_spec) % find index of the first letter after the % sign ind_p=strfind(str_spec,'%'); ind=ind_p+find(isletter(str_spec(ind_p:end)),1)-1; str_spec_letter=str_spec(ind); switch str_spec_letter case {'d','f','e','g'} class={'numeric'}; attribute={}; case 'i' class={'numeric'}; attribute={'integer'}; case 's' class={'char'}; attribute={}; case 'b' class={'logical'}; attribute={}; otherwise % Any of the above classes will pass class={'numeric','char','logical'}; attribute={}; end end end %% Get functions methods function command_names=get.command_names(this) command_names=fieldnames(this.CommandList); end function write_commands=get.write_commands(this) ind_w=structfun(@(x) x.write_flag, this.CommandList); write_commands=this.command_names(ind_w); end function read_commands=get.read_commands(this) ind_r=structfun(@(x) x.read_flag, this.CommandList); read_commands=this.command_names(ind_r); end function command_no=get.command_no(this) command_no=length(this.command_names); end end end \ No newline at end of file diff --git a/@MyTds/MyTds.m b/@MyTds/MyTds.m index 44a90cf..332daae 100644 --- a/@MyTds/MyTds.m +++ b/@MyTds/MyTds.m @@ -1,155 +1,153 @@ % Class for controlling 2-channel Tektronix TDS scopes. classdef MyTds % line feed ENQ = char(5); % enquiry ACK = char(6); % acknowledge NAK = char(21); % negative acknowledge end properties (SetAccess=protected, GetAccess=public) pressure1 = 0; % numeric values of pressure pressure2 = 0; stat1; stat2; gauge_id1; gauge_id2; pressure_unit = ''; end properties (Dependent=true) pressure_str1; % display string with measurement unit pressure_str2; end methods (Access=public) function this = MyTpg(interface, address, varargin) this@MyInstrument(interface, address, varargin{:}); - connectDevice(this); end % read pressure from a single channel or both channels at a time function p_arr = readPressure(this) query(this.Device,['PRX',this.CR,this.LF]); str = query(this.Device,this.ENQ); % Extract pressure and gauge status from reading. arr = sscanf(str,'%i,%e,%i,%e'); p_arr=transpose(arr(2:2:end)); this.pressure1 = p_arr(1); this.pressure2 = p_arr(2); % 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.stat1 = gaugeStatusFromCode(this, arr(1)); this.stat2 = gaugeStatusFromCode(this, arr(3)); end function pu = readPressureUnit(this) query(this.Device,['UNI',this.CR,this.LF]); str = query(this.Device,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); this.pressure_unit = pu; end function id_list = readGaugeId(this) query(this.Device,['TID',this.CR,this.LF]); str = query(this.Device,this.ENQ); id_list = deblank(strsplit(str,{','})); this.gauge_id1 = id_list{1}; this.gauge_id2 = id_list{2}; end function p_arr = readAllHedged(this) openDevice(this); try % Try opening device before each reading as unclarified % spontaneous closing of the device was observed p_arr = readPressure(this); readPressureUnit(this); readGaugeId(this); catch p_arr = [0,0]; warning('Error while communicating with gauge controller') end closeDevice(this); end % Re-define readHeader function function HdrStruct=readHeader(this) readAllHedged(this); HdrStruct = struct(); HdrStruct.pressure1.value = this.pressure1; HdrStruct.pressure1.str_spec = '%e'; HdrStruct.pressure2.value = this.pressure2; HdrStruct.pressure2.str_spec = '%e'; HdrStruct.stat1.value = this.stat1; HdrStruct.stat1.str_spec = '%s'; HdrStruct.stat2.value = this.stat2; HdrStruct.stat2.str_spec = '%s'; HdrStruct.gauge_id1.value = this.gauge_id1; HdrStruct.gauge_id1.str_spec = '%s'; HdrStruct.gauge_id2.value = this.gauge_id2; HdrStruct.gauge_id2.str_spec = '%s'; HdrStruct.pressure_unit.value = this.pressure_unit; HdrStruct.pressure_unit.str_spec = '%s'; end function code_list = turnGauge(this) query(this.Device,['SEN',char(1,1),this.CR,this.LF]); str = query(this.Device,this.ENQ); code_list = deblank(strsplit(str,{','})); 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 %% Get functions methods function p_str = get.pressure_str1(this) p_str = sprintf('%.2e %s', this.pressure1, this.pressure_unit); end function p_str = get.pressure_str2(this) p_str = sprintf('%.2e %s', this.pressure2, this.pressure_unit); end end end diff --git a/Utility functions/InstrumentManager.mlapp b/Utility functions/InstrumentManager.mlapp index 66f4a2f..a6b4bb1 100644 Binary files a/Utility functions/InstrumentManager.mlapp and b/Utility functions/InstrumentManager.mlapp differ